0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

タイムスタンプサービス

無料でタイムスタンプトークンを発行するサービス、Free TSA Project に接続します。タイムスタンプリクエスト、タイムスタンプレスポンス、タイムスタンプトークンの生成、解析にはOpenSSLを利用しました。HTTP通信には、MFC WinInetを利用しました。

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

タイムスタンプに関わる仕様は、RFC3161RFC5816 で 定義されています。

HTTP通信で行う際の仕様が RFC3161 の 3.4 Time-Stamp Protocol via HTTP に記載されています。

開発に必要なもの

  • OpenSSL 3.0系
  • Visual Stuido 2022 Commnunity

OpenSSLのインストール

Win32/Win64 OpenSSLより Windows版 OpenSSLをダウンロードして、インストールします。64bit版のサンプルを作成するので、「Win64 OpenSSL v3.0.7」をインストールしました。

Visual studio 2022 Commnunityのインストール

Visual studio 2022 CommnunityよりVisual studio 2022 Commnunity版をダウンロードします。ダウンロードした VisualStudioSetup.exe を実行して、インストールします。

MFC のインストール

「Visual Studio 2022 Community」をデフォルトインストールした場合、 MFC(Microsoft Foundation Class)は含まれていないので、追加でインストールします。

Visual Studio Instller を起動します。

VisualStudioInstaller.png

Visual Studio Community 2022 の 変更ボタンを押下します。

VisualStudioInstaller_MFC追加.png

C++ によるデスクトップ開発を選択します。インストールの詳細で「最新のv143ビルドツール用 C++ MFC (x86およびx64)」をチェックします。
変更ボタンを押して、インストールします。

プロジェクトの設定

プロジェクトを作成するとき、「Windows デスクトップウィザード」を選択し、次へボタンを押下します。

NewProject.png

作成ボタンを押下します。

MakeProject.png

アプリケーションの種類で「コンソールアプリケーション(.exe)」を選択し、「MFC アプリケーション」をチェックし、OKボタンを押下し、プロジェクトを作成します。

ConsoleApp.png

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

項目 設定する値
追加のインクルードディレクトリ C:\Program Files\OpenSSL-Win64\include
追加のライブラリディレクトリ C:\Program Files\OpenSSL-Win64\lib\VC
追加のライブラリファイル libcrypto64MD.lib

サンプルプログラム

#include "pch.h"
#include "framework.h"
#include "Project1.h"
#include <afxinet.h>

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

#include <string>
#include <fstream>
#include <vector>


#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 唯一のアプリケーション オブジェクトです

CWinApp theApp;

//タイムスタンプリクエストの作成
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;
}

int httpTimeStampClient(std::string url, std::vector<unsigned char>& request, std::vector<unsigned char>& response) {

    //URLの解析
    CString strServerName;
    INTERNET_PORT nPort;
    CString strObject;
    DWORD dwServiceType;
    DWORD dwAccessType = INTERNET_OPEN_TYPE_DIRECT;

    std::string appName("HelloWorld");

    if (!AfxParseURL(CString(url.data()), dwServiceType, strServerName, strObject, nPort) ||
        (dwServiceType != AFX_INET_SERVICE_HTTP && dwServiceType != AFX_INET_SERVICE_HTTPS)) {
        std::cerr << "AfxParseURL Error" << std::endl;
        return 1;
    }

    CInternetSession* m_session = new CInternetSession(CString(appName.data()), 0, dwAccessType, 0, NULL, INTERNET_FLAG_DONT_CACHE);
    if (m_session == NULL) {
        std::cerr << "CInternetSession Error" << std::endl;
        return 1;
    }

    BOOL brtn;
    HTTP_VERSION_INFO httpverinfo;
    httpverinfo.dwMajorVersion = 1;
    httpverinfo.dwMinorVersion = 1;
    brtn = m_session->SetOption(INTERNET_OPTION_HTTP_VERSION, &httpverinfo, sizeof(httpverinfo));
    if (brtn == FALSE) {
        std::cerr << "SetOption Error" << std::endl;
        return 1;
    }

    //HTTP接続の確立
    CHttpConnection* pServer = m_session->GetHttpConnection(strServerName, nPort);
    if (pServer == NULL) {
        std::cerr << "GetHttpConnection Error" << std::endl;
        return 1;
    }

    //HTTPリクエスト要求の作成
    DWORD dwFlag = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_NO_AUTO_REDIRECT;

    if (dwServiceType == AFX_INET_SERVICE_HTTPS) {
        dwFlag |= INTERNET_FLAG_SECURE;
    }
    CHttpFile* pFile = pServer->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject, NULL, 1, NULL, CString("HTTP/1.1"), dwFlag);
    if (pFile == NULL) {
        std::cerr << "OpenRequest Error" << std::endl;
        return 1;
    }

    CString contentType("Content-Type: application/timestamp-query");
    brtn = pFile->SendRequest(contentType, (LPVOID)request.data(), (DWORD)request.size());
    if (brtn == FALSE) {
        std::cerr << "SendRequest Error" << std::endl;
        return 1;
    }

    //要求イベントの結果受信
    DWORD dwRet;
    brtn = pFile->QueryInfoStatusCode(dwRet);
    if (brtn == FALSE) {
        std::cerr << "QueryInfoStatusCode Error" << std::endl;
        return 1;
    }

    DWORD dwbodysize = 10000;
    DWORD dwbodysizesize = sizeof(dwbodysize);

    //エラーで返ってくる
    brtn = pFile->QueryInfo(HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, &dwbodysize, &dwbodysizesize);
    if (brtn == FALSE) {
        std::cerr << "QueryInfo Error" << std::endl;
    }

    UINT rsize = 0;
    std::vector<unsigned char> responsebody;

    unsigned char tmpbody[10000];
    memset(tmpbody, '\0', sizeof(tmpbody));

    while ((rsize = pFile->Read(tmpbody, 10000)) > 0) {
        for (UINT i = 0; i < rsize; i++) {
            responsebody.push_back(tmpbody[i]);
        }
    }

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

    return 0;
}

int main()
{
    int nRetCode = 0;

    HMODULE hModule = ::GetModuleHandle(nullptr);
    if (hModule == NULL) {
        wprintf(L"致命的なエラー: GetModuleHandle が失敗しました\n");
        nRetCode = 1;
        return nRetCode;
    }


    // MFC を初期化して、エラーの場合は結果を出力する
    if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0)) {
        wprintf(L"致命的なエラー: MFC の初期化が失敗しました\n");
        nRetCode = 1;
        return nRetCode;
    }

    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::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::exit(EXIT_FAILURE);
    }

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

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

    std::string url("http://eswg.jnsa.org/freetsa");
    std::vector<unsigned char> responseBody;
    httpTimeStampClient(url, timestamp_req_data, responseBody);

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

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

    //タイムスタンプレスポンスをファイルへ出力
    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::exit(EXIT_FAILURE);
    }

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

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

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

    return nRetCode;
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?