[英]Verifying my self-signed certificate with openSSL
我擁有服務器,我擁有客戶的可執行文件。 我想在它們之間建立安全的 TLS 連接。
我可以將我想要的任何內容嵌入到客戶端可執行文件中,但我不確定如何驗證我的客戶端從服務器連接收到的自分配證書,即從SSL_get_peer_certificate
調用。
我讀到證書只是帶有用私鑰簽名的元數據部分的公鑰。 我可以通過將公鑰嵌入我的客戶端應用程序以某種方式驗證服務器發送給我的證書確實具有所有元數據正確簽名嗎? 這是可能的(如果是,如何?)
我不確定如何驗證我的客戶端從服務器連接收到的自分配證書......
根據您使用的 OpenSSL 庫,您必須執行兩到三個步驟進行驗證。 這兩個版本在 OpenSSL 1.1.0 處一分為二。 OpenSSL 1.1.0 及更高版本執行主機名驗證,因此只需兩個步驟。 OpenSSL 1.0.2 及以下版本不執行主機名驗證,因此需要三個步驟。
下面詳述的步驟來自 OpenSSL wiki 上的SSL/TLS 客戶端。
服務器證書
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,認證路徑構建中有詳細說明。
您可以使用SSL_get_verify_result
獲得路徑驗證的結果。
證書名稱
OpenSSL 1.0.2 以下版本要求您驗證主機名是否與證書中列出的名稱匹配。 這是一個很大的話題,但它的缺點是:任何主機名或 dns 名稱都需要出現在證書的主題備用名稱 (SAN) 中,而不是通用名稱 (CN) 中。 另請參閱如何使用證書頒發機構簽署證書簽名請求以及如何使用 openssl 創建自簽名證書? 它提供了大量有關 X.509 服務器證書、如何顯示名稱以及各種規則來自何處的背景信息。
實際上,您可以使用X509_get_ext_d2i(cert, NID_subject_alt_name, ...)
獲取 SAN。 然后遍歷列表並使用sk_GENERAL_NAME_num
提取每個名稱。 然后,您提取GENERAL_NAME
條目和ASN1_STRING_to_UTF8
,並查看它是否與您嘗試連接的名稱匹配。
下面是打印主題備用名稱 (SAN)和通用名稱 (CN)的例程。 它來自 OpenSSL wiki 頁面上的示例。
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);
}
使用 openSSL 驗證我的自簽名證書
因為它是您的自簽名證書,所以您可以做得比上面更好。 您對主機的公鑰有先驗知識。 您可以固定公鑰,然后僅使用證書來傳遞公鑰或作為演示細節。
要固定公鑰,請參閱 OWASP 的公鑰固定。
您還應該避免使用 IETF 的RFC 7469, Public Key Pinning Extension for HTTP with Overrides 。 IETF 的版本允許攻擊者破壞已知良好的 pinset,以便攻擊者可以中間人連接。 他們還抑制報告問題,因此用戶代理成為掩蓋的同謀。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.