我拥有服务器,并拥有客户的可执行文件。我想在它们之间建立一个安全的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