OpenSSL APIを利用したSSL/TLS通信


ノンブロッキングのソケットでのOpenSSL APIを使用した、SSL/TLS通信。
SSL/TLSのハンドシェイクはサーバ認証+クライアント認証。

TCPコネクション確率後のサーバ側SSL/TLS接続処理。(※クライアント認証)
ノンブロッキングの場合、SSL_accept()をSSL_ERROR_NONEまで繰り返す。

server_accept.c
    SSL_CTX *ctx;
    SSL *ssl;
    /* SSL/TLS汎用でSSL_CTXオブジェクトを生成 */
    if (!(ctx = SSL_CTX_new(SSLv23_server_method())))
    {
        // エラー処理
    }
    /* SSLv2はセキュリティ的にNGなので除く*/
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
    // 証明書の登録
    if (1 != SSL_CTX_use_certificate_file(ctx, certificate, SSL_FILETYPE_PEM))
    {
        // エラー処理
    }

    // 秘密鍵の登録
    if (1 != SSL_CTX_use_PrivateKey_file(ctx, privatekey, SSL_FILETYPE_PEM))
    {
        // エラー処理
    }
    // CA証明書の登録とクライアント証明書の要求
    if (1 != SSL_CTX_load_verify_locations(ctx, ca_certificate, NULL))
    {
        // エラー処理
    }
    // 証明書検証機能の有効化
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
    // 証明書チェーンの深さ
    SSL_CTX_set_verify_depth(ctx,9);
    /* SSLオブジェクトを生成 */
    if (!(ssl = SSL_new(ctx)))
    {
        // エラー処理
    }
    /* SSLオブジェクトとファイルディスクリプタを接続 */
    if (!SSL_set_fd(ssl, fd))
    {
        // エラー処理
    }
    while (1)
    {
        /* SSL通信の開始 */
        sslret  = SSL_accept(ssl);
        ssl_eno = SSL_get_error(ssl, sslret);
        switch (ssl_eno)
        {
            case SSL_ERROR_NONE:
                break;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_SYSCALL:
                continue;
            default:
                // エラー処理
        }
        break;
    }

TCPコネクション確率後のクライアント側SSL/TLS接続処理。
ノンブロッキングの場合、SSL_connect()をSSL_ERROR_NONEまで繰り返す。

client_connect.c
    SSL_CTX *ctx;
    SSL *ssl;
    /* SSL/TLS汎用でSSL_CTXオブジェクトを生成 */
    if (!(ctx = SSL_CTX_new(SSLv23_client_method())))
    {
        // エラー処理
    }

    /* SSLv2はセキュリティ的にNGなので除く*/
    SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2);

    // 証明書の登録
    if (1 != SSL_CTX_use_certificate_file(ctx, certificate, SSL_FILETYPE_PEM))
    {
        // エラー処理
    }
    // 秘密鍵の登録
    if (1 != SSL_CTX_use_PrivateKey_file(ctx, privatekey, SSL_FILETYPE_PEM))
    {
        // エラー処理
    }

    // CA証明書の登録
    if (1 != SSL_CTX_load_verify_locations(ctx, ca_certificate, NULL))
    {
        // エラー処理
    }
    // 証明書検証機能の有効化
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
    // 証明書チェーンの深さ
    SSL_CTX_set_verify_depth(ctx,9);
    /* SSLオブジェクトを生成 */
    if (!(ssl = SSL_new(ctx)))
    {
        // エラー処理
    }
    /* SSLオブジェクトとファイルディスクリプタを接続 */
    if (!SSL_set_fd(ssl, fd))
    {
        // エラー処理
    }
    while (1)
    {
        /* SSL通信の開始 */
        sslret  = SSL_connect(ssl);
        ssl_eno = SSL_get_error(ssl, sslret);
        switch (ssl_eno)
        {
            case SSL_ERROR_NONE:
                break;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_SYSCALL:
                continue;
            default:
                // エラー処理
        }
        break;
    }

データ送受信は、SSL_write()/SSL_read()をSSL_ERROR_NONEまで繰り返す。
recv()/send()のイメージ。
SSL_write()/SSL_read()は、TCPの送受信両方が発生する可能性がある。
同じ引数でSSL_write()/SSL_read()は繰り返す。
どこまで読み書きが進んだかはオブジェクト側で管理。

send_data.c
    // epoll_waitでイベントまち

    while (1)
    {
        /* SSLデータ送信 */
        sslret  = SSL_write(ssl, data, size);
        ssl_eno = SSL_get_error(ssl, sslret);
        switch (ssl_eno)
        {
            case SSL_ERROR_NONE:
                break;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_SYSCALL:
                continue;
            default:
                // エラー処理
        }
        break;
    }
recv_data.c
    // epoll_waitでイベントまち

    while (1)
    {
        /* SSLデータ受信 */
        sslret  = SSL_read(ssl, data, size);
        ssl_eno = SSL_get_error(ssl, sslret);
        switch (ssl_eno)
        {
            case SSL_ERROR_NONE:
                break;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_SYSCALL:
                continue;
            default:
                // エラー処理
        }
        break;
    }

SSL/TLS切断処理は、SSL_shutdown()をSSL_ERROR_NONEまで繰り返す。

close.c
    while (1)
    {
        /* SSL通信の終了 */
        sslret  = SSL_shutdown(ssl);
        ssl_eno = SSL_get_error(ssl, sslret);
        switch (ssl_eno)
        {
            case SSL_ERROR_NONE:
                break;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_SYSCALL:
                continue;
            default:
                // エラー処理
        }
        break;
    }
    SSL_free(ssl); 
    SSL_CTX_free(ctx);

CRLのチェックは以下を試したが、正常動作しなかった・・・

client_connect.c
    
    // CRL検証の有効化
    X509_STORE *store = SSL_CTX_get_cert_store(ssl->ctx);
    X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
    X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
    //X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
    X509_STORE_set1_param(store, param);
    X509_VERIFY_PARAM_free(param);

    // 証明書検証機能の有効化
    SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, verify_callback);