使用openSSL验证我的自签名证书 [英] Verifying my self-signed certificate with openSSL

查看:1737
本文介绍了使用openSSL验证我的自签名证书的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我拥有服务器,我拥有客户的可执行文件。我想在他们之间建立一个安全的TLS连接。



我可以嵌入任何我想要的客户端可执行文件,但我不知道如何验证自我,我的客户端从与服务器的连接(即从 SSL_get_peer_certificate 调用)接收到的证书。



证书只是具有用私钥签署的元数据部分的公钥。我能以某种方式验证服务器发送给我的证书确实所有元数据通过嵌入公钥到我的客户端应用程序正确签名吗?这是可能的(如果是,如何?)

解决方案


验证我的客户端从与服务器的连接中收到的自我分配的证书...


根据OpenSSL库使用,您必须执行两个或三个步骤进行验证。这两个版本在OpenSSL 1.1.0平分。 OpenSSL 1.1.0和更高版本执行主机名验证,所以它只需要两个步骤。 OpenSSL 1.0.2及以下版本不执行主机名验证,因此它需要三个步骤。



以下详细步骤来自 SSL / TLS客户端



服务器证书



OpenSSL 1.0.2和1.1.0都要求您检查证书是否存在。如果您使用ADH(匿名Diffie-Hellman),TLS-PSK(预共享密钥),TLS_SRP(安全远程密码),则可能没有要验证的服务器证书。



您可以通过 SSL_get_peer_certificate 获取服务器的证书。如果它返回非NULL,则证书存在。缺少证书可能是也可能不是失败的原因。



证书链 b
$ b

OpenSSL 1.0.2和1.1.0都要求您检查链验证的结果。链验证是路径构建的一部分,详情请参见 RFC 4158,认证路径构建



您可以使用 SSL_get_verify_result 获得路径验证的结果。



证书名称



OpenSSL 1.0.2 an要求您验证主机名是否与列出的名称匹配在证书中。它是一个大主题,但缺点是:任何主机名或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 ,看看它是否与您尝试连接的名称匹配。 / p>

以下是打印主题备用名称(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 {
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和ASN1_STRING大小不匹配(嵌入的null?):%d vs%d\ n,len2,len1);
}

/ *如果字符串长度有问题,那么* /
/ *我们跳过候选人并移动到下一个。 * /
/ *另一个策略是失败,因为它可能* /
/ *表示客户端受到攻击。 * /
if(utf8& len1& len2&&(len1 == len2)){
fprintf(stdout,%s:%s\\\
label,utf8);
success = 1;
}

if(utf8){
OPENSSL_free(utf8),utf8 = NULL;
}
}
else
{
fprintf(stderr,Unknown GENERAL_NAME type:%d\\\
,entry-> type);
}
}

} while(0);

if(names)
GENERAL_NAMES_free(names);

if(utf8)
OPENSSL_free(utf8);

if(!success)
fprintf(stdout,%s:< not available> \\\
,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\\\
,label,utf8);
success = 1;

} while(0);

if(utf8)
OPENSSL_free(utf8);

if(!success)
fprintf(stdout,%s:< not available> \\\
,label);
}







使用openSSL验证我的自签名证书


因为 自签名证书,你可以做得比上面更好。您可以先验知道主机的公钥。



要固定公钥,请参阅公开密钥固定。OWASP。



您应该也避免了IETF的 RFC 7469,覆盖的HTTP的公共密钥固定扩展。 IETF的翻译允许攻击者打破已知的良好的pinet,所以攻击者可以MitM连接。他们也禁止报告问题,所以用户代理变成同谋的掩护。


I own the server and I own the customer's executable. I'd like to establish a secure TLS connection between them.

I can embed whatever I want into the client executable but I'm not sure how to validate a self-assigned certificate that my client received from a connection to the server, i.e. from a SSL_get_peer_certificate call.

I read around that certificates are just public keys with metadata parts signed with the private key. Can I somehow verify that the certificate the server sent me has indeed all the metadata correctly signed by embedding the public key into my client application? Is this possible (and if it is, how?)

解决方案

I'm not sure how to validate a self-assigned certificate that my client received from a connection to the server ...

Depending on the OpenSSL library you are using, you have to perform two or three steps for verification. The two versions bisect at OpenSSL 1.1.0. OpenSSL 1.1.0 and above performs hostname validation so it only takes two steps. OpenSSL 1.0.2 and below does not perform hostname validation so it requires three steps.

The steps detailed below are from SSL/TLS Client on the OpenSSL wiki.

Server Certificate

Both OpenSSL 1.0.2 and 1.1.0 require you to check for the presence of a certificate. If you use ADH (Anonymous Diffie-Hellman), TLS-PSK (Preshared Key), TLS_SRP (Secure Remote Password), then there may not be a server certificate to verify.

You get the server's certificate with SSL_get_peer_certificate. If it returns non-NULL, then a certificate is present. Lack of a certificate may or may not be a reason to fail.

Certificate Chain

Both OpenSSL 1.0.2 and 1.1.0 require you to check the result of chain validation. Chain validation is part of path building, and its detailed in RFC 4158, Certification Path Building.

You get the result of path validation with SSL_get_verify_result.

Certificate Names

OpenSSL 1.0.2 an below requires you to verify the hostname matches a name listed in the certificate. Its a big topic, but the short of it is: any hostname or dns name needs to be present in the certifcate's Subject Alternative Name (SAN), and not the Common Name (CN). Also see How do you sign Certificate Signing Request with your Certification Authority and How to create a self-signed certificate with openssl? It provides a lot of background information on X.509 server certificates, how to present names, and where the various rules come from.

Effectively, you fetch the SANs with X509_get_ext_d2i(cert, NID_subject_alt_name, ...). Then you loop over the list and extract each name with sk_GENERAL_NAME_num. Then, you extract a GENERAL_NAME entry and ASN1_STRING_to_UTF8, and see if it matches the name you tried to connect to.

Below are the routines for printing the Subject Alternative Name (SAN) and the Common Name (CN). It came from the example on the OpenSSL wiki page.

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);
}


Verifying my self-signed certificate with openSSL

Because its your self-signed certificate, you can do even better than above. You have a priori knowledge of the host's public key. You can pin the public key, and just use the certificate to deliever the public key or as a presentation detail.

To pin the public key, see Public Key Pinning over at OWASP.

You should also avoid the IETF's RFC 7469, Public Key Pinning Extension for HTTP with Overrides. The IETF's rendition allows the attacker to break a known good pinset so the attacker can MitM the connection. They also suppress reporting the problem, so the user agent becomes complicit in the coverup.

这篇关于使用openSSL验证我的自签名证书的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆