VC++, libcurl, OpenSSLで無料のタイムスタンプサービスに接続する

## タイムスタンプサービス

無料でタイムスタンプトークンを発行するサービス、[Free TSA Project](http://eswg.jnsa.org/sandbox/freetsa/) に接続します。タイムスタンプリクエスト、タイムスタンプレスポンス、タイムスタンプトークンの生成、解析には[OpenSSL](https://www.openssl.org/)を利用しました。HTTP通信には、[libcurl](https://curl.se/)を利用しました。

## タイムスタンププロトコル

タイムスタンプに関わる仕様は、[RFC3161](https://datatracker.ietf.org/doc/html/rfc3161)、[RFC5816](https://datatracker.ietf.org/doc/html/rfc5816) で 定義されています。

HTTP通信で行う際の仕様が [RFC3161 の 3.4 Time-Stamp Protocol via HTTP](https://datatracker.ietf.org/doc/html/rfc3161#section-3.4) に記載されています。

## 開発に必要なもの

* OpenSSL 3.0系
* curl 
* Visual Stuido 2022 Commnunity

## OpenSSLのインストール

[Win32/Win64 OpenSSL](https://slproweb.com/products/Win32OpenSSL.html)より Windows版 OpenSSLをダウンロードして、インストールします。64bit版のサンプルを作成するので、「Win64 OpenSSL v3.0.7」をインストールしました。

## curl のインストール

curlのソースコードをダウンロードして、ビルドします。ビルド方法は、[libcurlをWindowsでビルドする](https://qiita.com/etaka/items/f7a5aca7d53e3de9220a) を参考にしてください。

## プロジェクトの設定

サンプルのプロジェクトのプロパティの以下の項目に値を追加します。


| 項目 | 設定する値 |
|-------------------|----------------|
| 追加のインクルードディレクトリ | C:\Program Files\OpenSSL-Win64\include <br> curl-7.87.0\builds\libcurl-vc-x64-release-dll-ipv6-sspi-schannel\include|
| 追加のライブラリディレクトリ | C:\Program Files\OpenSSL-Win64\lib\VC <br> curl-7.87.0\builds\libcurl-vc-x64-release-dll-ipv6-sspi-schannel\lib |
| 追加のライブラリファイル | libcrypto64MD.lib <br> libcurl.lib |


## サンプルプログラム

```c++
#include <iostream>
#include <fstream>
#include <vector>
#include <string>

#include <openssl/ts.h>
#include <openssl/sha.h>

#include <curl/curl.h>



//タイムスタンプリクエストの作成
int createTimestampReq(std::vector<unsigned char>& sha256_data, std::vector<unsigned char>& ts_req_encode) {

    TS_REQ* ts_req = NULL;
    TS_MSG_IMPRINT* msg_imprint = NULL;
    X509_ALGOR* algo = NULL;
    const EVP_MD* md = NULL;

    do {
        //タイムスタンプリクエストの作成
        if ((ts_req = TS_REQ_new()) == NULL)
            break;
        if (!TS_REQ_set_version(ts_req, 1))
            break;

        //MessageImprintの作成
        if ((msg_imprint = TS_MSG_IMPRINT_new()) == NULL)
            break;

        if ((algo = X509_ALGOR_new()) == NULL)
            break;

        if ((md = EVP_get_digestbyname("sha256")) == NULL)
            break;

        if ((algo->algorithm = OBJ_nid2obj(EVP_MD_get_type(md))) == NULL)
            break;

        if ((algo->parameter = ASN1_TYPE_new()) == NULL)
            break;
        algo->parameter->type = V_ASN1_NULL;

        if (!TS_MSG_IMPRINT_set_algo(msg_imprint, algo))
            break;

        if (!TS_MSG_IMPRINT_set_msg(msg_imprint, sha256_data.data(), sha256_data.size()))
            break;

        if (!TS_REQ_set_msg_imprint(ts_req, msg_imprint))
            break;

        if (!TS_REQ_set_cert_req(ts_req, 1))
            break;

    } while (0);

    int len = i2d_TS_REQ(ts_req, 0);

    std::vector<unsigned char> ts_req_data;
    ts_req_data.resize(len);

    unsigned char* p = ts_req_data.data();
    i2d_TS_REQ(ts_req, &p);

    ts_req_encode.resize(ts_req_data.size());
    copy(ts_req_data.begin(), ts_req_data.end(), ts_req_encode.begin());

    return 0;
}


struct ResponseBody {
    std::vector<unsigned char>data;
};

//コールバック関数
//HTTPレスポンスのメッセージ本体が格納されます。
size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) {

    ResponseBody* response = (ResponseBody*)userdata;
   
    unsigned char* p = (unsigned char*)ptr;
    for (size_t i = 0; i < nmemb; i++) {
        response->data.push_back(*p);
        p++;
    }
    return size * nmemb;
}

int httpTimeStampClient(std::string url, std::vector<unsigned char> &request, std::vector<unsigned char> &response) {
    
    //curlを使って、タイムスタンプサーバーへ接続
    CURL* curl;

    /* In windows, this will init the winsock stuff */
    curl_global_init(CURL_GLOBAL_ALL);

    /* get a curl handle */
    curl = curl_easy_init();

    ResponseBody responsebody;

    if (curl) {

        struct curl_slist* headers = NULL;

        headers = curl_slist_append(headers, "Content-Type: application/timestamp-query");

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.size());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.data());
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&responsebody);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);

        CURLcode res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
             std::cerr << "Error: curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
            return 1;
        }

        long http_code = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
        if (http_code != 200) {
            std::cerr << "Error: status code = " << http_code << std::endl;
            return 1;
        }

        response.resize(responsebody.data.size());
        std::copy(responsebody.data.begin(), responsebody.data.end(), response.begin());

        curl_easy_cleanup(curl);

    }
    curl_global_cleanup();
    
    return 0;

}

int main()
{
    std::string str("Hello World");

    //ハッシュ値の作成
    unsigned char* p;
    std::vector<unsigned char> sha256_data;
    sha256_data.resize(32);
    p = sha256_data.data();
    
    SHA256((const unsigned char*)str.data(), str.size(), p);    
    std::vector<unsigned char> timestamp_req_data;
    
    //タイムスタンプリクエストの作成
    createTimestampReq(sha256_data, timestamp_req_data);

    std::string url("http://eswg.jnsa.org/freetsa");
    std::vector<unsigned char> timestamp_response_data;
    
    //タイムスタンプサービスへ接続
    httpTimeStampClient(url, timestamp_req_data, timestamp_response_data);

    TS_RESP* ts_resp = NULL;
    const unsigned char* p2 = NULL;
    p2 = timestamp_response_data.data();
    ts_resp = d2i_TS_RESP(NULL, &p2, timestamp_response_data.size());

    //タイムスタンプレスポンスからタイムスタンプトークンを取り出す
    PKCS7* ts_token = NULL;
    ts_token = TS_RESP_get_token(ts_resp);

    //タイムスタンプリクエストをファイルへ出力
    std::ofstream ofs("HelloWorld.txt.tsq", std::ios_base::out | std::ios_base::binary);
    if (ofs.fail()) {
        std::cerr << "File open error: " << "HelloWorld.txt.tsq" << "\n";
        std::quick_exit(0);
    }

    ofs.write((char*)timestamp_req_data.data(), timestamp_req_data.size());
    if (ofs.fail()) {
        std::cerr << "File write error: " << "HelloWorld.txt.tsq" << "\n";
        std::quick_exit(0);
    }

    ofs.close();
    if (ofs.fail()) {
        std::cerr << "File close error: " << "HelloWorld.txt.tsq" << "\n";
        std::quick_exit(0);
    }

    //タイムスタンプレスポンスをファイルへ出力
    std::ofstream ofs2("HelloWorld.txt.tsr", std::ios_base::out | std::ios_base::binary);
    if (ofs2.fail()) {
        std::cerr << "File open error: " << "HelloWorld.txt.tsr" << "\n";
        std::quick_exit(0);
    }

    ofs2.write((char*)timestamp_response_data.data(), timestamp_response_data.size());
    if (ofs2.fail()) {
        std::cerr << "File write error: " << "HelloWorld.txt.tsr" << "\n";
        std::quick_exit(0);
    }

    ofs2.close();
    if (ofs2.fail()) {
        std::cerr << "File close error: " << "HelloWorld.txt.tsr" << "\n";
        std::quick_exit(0);
    }

    //タイムスタンプトークンをファイルへ出力
    BIO* out = NULL;
    if ((out = BIO_new_file("HelloWorld.txt.tst", "wb")) == NULL) {
        std::cerr << "File close error: " << "HelloWorld.txt.tst" << "\n";
        std::quick_exit(0);
    }
    i2d_PKCS7_bio(out, ts_token);


    if (ts_token != NULL) {
        PKCS7_free(ts_token);
    }

    /*
    if (response != NULL) {
        TS_RESP_free(response);
    }
    */
    return 0;

}

```