我擁有服務器,並擁有客戶的可執行文件。我想在它們之間建立一個安全的TLS連接。使用openSSL驗證我的自簽名證書
我可以將任何我想要的東西嵌入到客戶端可執行文件中,但我不確定如何驗證我的客戶端從服務器連接收到的自分配證書,即來自SSL_get_peer_certificate
調用。
我讀過的證書只是公鑰和元數據部分用私鑰簽名。我可以以某種方式驗證服務器發送的證書是否確實通過將公鑰嵌入到我的客戶端應用程序中正確簽署了所有元數據?這是可能的(如果是,怎麼樣?)
我擁有服務器,並擁有客戶的可執行文件。我想在它們之間建立一個安全的TLS連接。使用openSSL驗證我的自簽名證書
我可以將任何我想要的東西嵌入到客戶端可執行文件中,但我不確定如何驗證我的客戶端從服務器連接收到的自分配證書,即來自SSL_get_peer_certificate
調用。
我讀過的證書只是公鑰和元數據部分用私鑰簽名。我可以以某種方式驗證服務器發送的證書是否確實通過將公鑰嵌入到我的客戶端應用程序中正確簽署了所有元數據?這是可能的(如果是,怎麼樣?)
我不知道如何驗證,從到服務器的連接接收我的客戶自分配的證書......
根據您使用的OpenSSL庫,您必須執行兩個或三個步驟進行驗證。這兩個版本在OpenSSL 1.1.0中平分。 OpenSSL 1.1.0及更高版本執行主機名驗證,因此只需要兩個步驟。 OpenSSL 1.0.2及以下版本不執行主機名驗證,因此需要三個步驟。
下面詳細介紹的步驟來自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名稱必須是存在於認證文件的主題備用名稱(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頁面上的示例。
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
由於其您自簽名的證書,你可以做得比上面還要好。你有主機的公鑰的先驗知識。您可以固定公鑰,只需使用證書來獲取公鑰或作爲演示文稿的詳細信息。
釘住公鑰,看到Public Key Pinning了在OWASP。
你也應該避免IETF的RFC 7469, Public Key Pinning Extension for HTTP with Overrides。 IETF的演繹允許攻擊者打破已知的良好pinset,以便攻擊者可以連接。他們還壓制報告問題,所以用戶代理變成了同謀掩護。
非常有趣,但不能攻擊,只是給我的權利證書的副本?我沒有驗證加密到證書中的私人數據可以用我的客戶端應用程序的公鑰解密,是嗎? – Dean
@Dean - 攻擊者可以發送實時服務器證書的複印件,但他/她不會有私鑰僞造的響應。 – jww