C言語にて openssl ライブラリを使って HTTPS クライアントが HTTPS サーバに接続したときに サーバ証明書を検証する

ソケット関数と openssl ライブラリを使って HTTPS クライアントを作成する
の続きです。

サーバ証明書の検証

サーバー証明書とは、「通信の暗号化」「Webサイトの運営者・運営組織の実在証明」の2つの役割をもつ電子証明書です。 認証局が発行します。

参考 JPRS サーバー証明書とは
https://jprs.jp/pubcert/about/

HTTPS クライアントは、証明書が認証局が発行したものかどうかを検証してから、処理を行う。

サーバ証明書の検証を行うには、サーバ証明書を発行した認証局の証明書が必要です。

ウェブブラウザでは、アプリに同封されている。

この記事では、モジラが公開している下記のファイルを使用する。 これは 主要な認証局ルート証明書を1つにしたもの。

mozilla : certdata.txt
https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt

HTTPS クライアントにてサーバ証明書を検証する

下記を参考にした。

前回のHTTPSクライアントに対して、 HTTPS サーバに接続したときに サーバ証明書を検証するように設定を追加する

下記のコードを追加する

// CA証明書を設定する
SSL_CTX_load_verify_locations( ctx, file_ca, NULL );

// 検証のためのコールバック関数を指定する
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);

// SNI のためのホスト名を設定する
SSL_set_tlsext_host_name(ssl, (char *)host );

// 検証のためのホスト名を設定する
X509_VERIFY_PARAM_set1_host(param, host, 0);

wikipedia ; SNI(Server Name Indication) https://ja.wikipedia.org/wiki/Server_Name_Indication

コールバック関数の中で、 エラーコードを取得し、検証の合格不合格を判定する

エラーコードは下記を参照のこと
X509_STORE_CTX_get_error
https://www.openssl.org/docs/manmaster/man3/X509_STORE_CTX_get_error.html

コールバック関数は、下記のようなコードになる。

int verify_callback( int preverify, X509_STORE_CTX* x509_ctx )
{ 
// preverify : 1のとき検証合格 0のとき不合格

// 証明書の内容を表示する
print_info(ctx);

if( preverify == 1 ){
    printf("preverify OK \n");
} else { 

// エラーコードを取得する   
    int err = X509_STORE_CTX_get_error(x509_ctx);

    if( err == X509_V_OK )
        printf("verify OK \n");
    } else {
        printf("verify Error: %d \n", err);
    }

}

// 1 を返すと、検証プロセスは次に進む
// 0 を返すと、「検証不合格」となり停止する。
    return 1;
}


// 証明書の内容を表示する
void print_info(X509_STORE_CTX* x509_ctx)
{
// 証明書チェーンの深さを取得する
int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
 
// 証明書を取得する   
X509* cert = X509_STORE_CTX_get_current_cert( x509_ctx );

// Subject名を取得する
X509_NAME *sname = X509_get_subject_name(cert);
char *subject = get_x509_cmmon_name( sname );

// Issure名を取得する
X509_NAME *sname = X509_get_issure_name(cert);
char *issure = get_x509_cmmon_name( iname ); 

printf("depth: %d \n", depth);
printf("Subject: %s \n", subject);
printf("Issuer: %s \n", issuer);

}


// X509_NAMEからコモンネームを取得する
char* get_x509_cmmon_name( X509_NAME *name )
{

int idx = -1;

unsigned char *utf8 = NULL;

idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
        
X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, idx);

ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);

ASN1_STRING_to_UTF8(&utf8, data);

    return (char *)utf8;
}

例として サーバに www.example.com を指定して実行すると 下記のように表示される

depth: 2
Subject: DigiCert Global Root CA
Issuer: DigiCert Global Root CA
preverify successful

depth: 1
Subject: DigiCert TLS RSA SHA256 2020 CA1
Issuer: DigiCert Global Root CA
preverify successful

depth: 0
Subject: www.example.org
Issuer: DigiCert TLS RSA SHA256 2020 CA1
preverify successful

Github にコードを公開した。

https://github.com/ohwada/MAC_cpp_Samples/tree/master/openssl/https