LoginSignup
19
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-17

ノンブロッキングのソケットでの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);
    
19
15
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
19
15