ソケット関数と 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 クライアントにてサーバ証明書を検証する
下記を参考にした。
SSL/TLS Client Verification
https://wiki.openssl.org/index.php/SSL/TLS_Client#VerificationOpenSSLでのサーバー証明書の検証
https://www.bit-hive.com/articles/20200407
前回の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 successfuldepth: 1
Subject: DigiCert TLS RSA SHA256 2020 CA1
Issuer: DigiCert Global Root CA
preverify successfuldepth: 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