私は...私のクライアントがサーバへの接続から受信した自己割り当てられた証明書を検証するかどうかはわかりません
使用しているOpenSSLライブラリによっては、検証のために2つまたは3つの手順を実行する必要があります。 2つのバージョンはOpenSSL 1.1.0で二等分します。 OpenSSL 1.1.0以上ではホスト名の検証が行われるので、2つのステップしか必要としません。 OpenSSL 1.0.2以下はホスト名の検証を行わないため、3つの手順が必要です。
以下に示す手順は、OpenSSL wikiのSSL/TLS Clientです。
サーバー証明書
のOpenSSL 1.0.2と1.1.0の両方が証明書の有無を確認する必要が。 ADH(匿名Diffie-Hellman)、TLS-PSK(事前共有キー)、TLS_SRP(セキュアリモートパスワード)を使用する場合は、検証するサーバー証明書がない可能性があります。
サーバーの証明書はSSL_get_peer_certificate
です。非NULLを返した場合は、証明書が存在します。証明書の不足は、失敗する理由かもしれません。
証明書チェーン
は、OpenSSL 1.0.2と1.1.0の両方がチェーンの検証の結果を確認する必要が。チェーン検証は経路構築の一部であり、その詳細はRFC 4158, Certification Path Buildingです。
パス検証の結果はSSL_get_verify_result
です。
証明書の名前
のOpenSSL 1.0.2以下のホスト名が証明書に記載されている名前と一致して検証する必要があります。任意のホスト名またはDNS名がcertifcateのサブジェクトの別名(SAN)に存在することが必要であり、ない共通名(CN):その大きな話題は、それの短いです。 How do you sign Certificate Signing Request with your Certification AuthorityとHow to create a self-signed certificate with openssl?を参照してください。X.509サーバー証明書、名前の提示方法、およびさまざまな規則の出所に関する多くの背景情報を提供します。
効果的には、でSANをフェッチします。次に、リストをループし、各名前をsk_GENERAL_NAME_num
で抽出します。次に、GENERAL_NAME
エントリとASN1_STRING_to_UTF8
を抽出し、接続しようとした名前と一致するかどうかを確認します。
は以下サブジェクトの別名(SAN)と共通名(CN)を印刷するためのルーチンです。これは、OpenSSL wikiページの例に由来します。そのあなた自己署名証明書は、あなたが上記よりもさらに良く行うことができますので
openSSLの
と私の自己署名証明書の検証
void print_san_name(const char* label, X509* const cert)
{
int success = 0;
GENERAL_NAMES* names = NULL;
unsigned char* utf8 = NULL;
do
{
if(!cert) break; /* failed */
names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
if(!names) break;
int i = 0, count = sk_GENERAL_NAME_num(names);
if(!count) break; /* failed */
for(i = 0; i < count; ++i)
{
GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
if(!entry) continue;
if(GEN_DNS == entry->type)
{
int len1 = 0, len2 = -1;
len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
if(utf8) {
len2 = (int)strlen((const char*)utf8);
}
if(len1 != len2) {
fprintf(stderr, " Strlen and ASN1_STRING size do not match (embedded null?): %d vs %d\n", len2, len1);
}
/* If there's a problem with string lengths, then */
/* we skip the candidate and move on to the next. */
/* Another policy would be to fails since it probably */
/* indicates the client is under attack. */
if(utf8 && len1 && len2 && (len1 == len2)) {
fprintf(stdout, " %s: %s\n", label, utf8);
success = 1;
}
if(utf8) {
OPENSSL_free(utf8), utf8 = NULL;
}
}
else
{
fprintf(stderr, " Unknown GENERAL_NAME type: %d\n", entry->type);
}
}
} while (0);
if(names)
GENERAL_NAMES_free(names);
if(utf8)
OPENSSL_free(utf8);
if(!success)
fprintf(stdout, " %s: <not available>\n", label);
}
void print_cn_name(const char* label, X509_NAME* const name)
{
int idx = -1, success = 0;
unsigned char *utf8 = NULL;
do
{
if(!name) break; /* failed */
idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
if(!(idx > -1)) break; /* failed */
X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, idx);
if(!entry) break; /* failed */
ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
if(!data) break; /* failed */
int length = ASN1_STRING_to_UTF8(&utf8, data);
if(!utf8 || !(length > 0)) break; /* failed */
fprintf(stdout, " %s: %s\n", label, utf8);
success = 1;
} while (0);
if(utf8)
OPENSSL_free(utf8);
if(!success)
fprintf(stdout, " %s: <not available>\n", label);
}
。あなたはを事前に持っていますホストの公開鍵の知識。公開鍵をピン留めし、証明書を使用して公開鍵を引き出したり、プレゼンテーションの詳細にすることができます。
公開鍵を固定するには、OWASPのPublic Key Pinningを参照してください。
また、IETFのRFC 7469, Public Key Pinning Extension for HTTP with Overridesも避ける必要があります。 IETFの演出は、攻撃者が既知の良いピンセットを破り、攻撃者が接続をMitMできるようにします。彼らはまた、問題の報告を抑止するので、ユーザエージェントは覆い隠された状態になります。
非常に興味深いですが、攻撃者は私に正しい証明書のコピーを送ってもらえませんか?私は証明書に暗号化された私的なデータが私のクライアントアプリケーションの公開鍵で復号化できることを確認していませんか? – Dean
@Dean - 攻撃者は実サーバーの証明書のコピーを送信できますが、応答を偽造するための秘密鍵は持っていません。 – jww