如何检查 SSL/TLS 证书的主题备用名称?

Posted

技术标签:

【中文标题】如何检查 SSL/TLS 证书的主题备用名称?【英文标题】:How to Check Subject Alternative Names for a SSL/TLS Certificate? 【发布时间】:2012-10-19 02:31:43 【问题描述】:

有没有办法以编程方式检查 SAN SSL 证书的主题备用名称?

例如,使用以下命令,我可以获得很多信息,但不是所有的 SAN:

openssl s_client -connect www.website.com:443 

非常感谢!

【问题讨论】:

当您以编程方式说,特别是任何语言,或者您可能在使用一些 shell 脚本时? 嗨 Bruno,我可能滥用了这个词 :) 我只是一个脚本或命令行或 .. 允许获取所有 AN。 【参考方案1】:

要获取证书的主题备用名称 (SAN),请使用以下命令:

openssl s_client -connect website.com:443 </dev/null 2>/dev/null | openssl x509 -noout -text | grep DNS:

首先,此命令连接到我们想要的站点(website.com,用于 SSL 的端口 443):

openssl s_client -connect website.com:443

然后将 (|) 输入此命令:

openssl x509 -noout -text

这将获取证书文件并输出其所有详细信息。 -noout 标志阻止它输出我们不需要的(base64 编码的)证书文件本身。 -text 标志告诉它以文本形式输出证书详细信息。

通常有很多我们不关心的输出(签名、颁发者、扩展等),所以我们将 输入到一个简单的 grep 中:

grep DNS:

由于 SAN 条目以 DNS: 开头,因此只返回包含该条目的行,去除所有其他信息,并为我们留下所需的信息。

您可能会注意到该命令没有干净地退出; openssl s_client 实际上充当客户端并保持连接打开,等待输入。如果您希望它立即退出(例如,在 shell 脚本中解析输出),只需将 echo 输入它:

echo | openssl s_client -connect website.com:443 | openssl x509 -noout -text | grep DNS:

如何直接从文件中获取 SAN?

为此,您不需要openssl s_client 命令。只需在 openssl x509 命令上添加 -in MyCertificate.crt 并再次通过 grep 管道,例如:

openssl x509 -noout -text -in MyCertificate.crt | grep DNS:

【讨论】:

我认为您也可以接受自己的答案。这样它就不会对其他人显示为“开放式问题”:) 哦,好的。我从来没有接受自己的答案,不知道有时间限制:) 这是 -text 选项,它提供了包括 SAN 在内的证书的完整列表 @JoeSlav 我知道这已经晚了五年,但我已经提交了一个编辑,分解了您的命令的工作原理,并解释了如何修改它以直接从证书文件中读取数据。希望它有所帮助:) 这行得通,但注意:如果这些是虚拟名称,您可能需要参数-servername www.website.com 来获取正确的 SSL 证书。 echo | openssl s_client -servername www.website.com -connect sameserver.website.com:443 | openssl x509 -noout -text | grep DNS【参考方案2】:

如果您只想查看 SAN,grep DNS: 是显而易见的解决方案。

如果你想有一个更清晰的列表来进一步处理,你可以使用这个 Perl 正则表达式来提取名称:@names=/\sDNS:([^\s,]+)/g

例如:

true | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -text \
| perl -l -0777 -ne '@names=/\bDNS:([^\s,]+)/g; print join("\n", sort @names);'

哪个会输出这个:

example.com
example.edu
example.net
example.org
www.example.com
www.example.edu
www.example.net
www.example.org

所以你可以通过管道将其发送到 while read name; do echo "do stuff with $name"; done 等。

或者对于一行以逗号分隔的列表,将join("\n", 替换为join(",",

(perl 的-0777 开关使其一次读取整个输入,而不是逐行读取)

【讨论】:

【参考方案3】:

也可以使用捆绑的 openssl 功能:

openssl s_client -connect website.com:443 </dev/null | openssl x509 -noout -ext subjectAltName

【讨论】:

【参考方案4】:

有没有办法以编程方式检查 SAN SSL 证书的备用名称?

X509 证书中可能有多个 SAN。以下内容来自 OpenSSL wiki SSL/TLS Client。它循环遍历名称并打印它们。

您可以从函数中获取X509*,例如来自 TLS 连接的 SSL_get_peer_certificate、来自内存的 d2i_X509 或来自文件系统的 PEM_read_bio_X509

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


【讨论】:

以上是关于如何检查 SSL/TLS 证书的主题备用名称?的主要内容,如果未能解决你的问题,请参考以下文章

如何显示证书的主题备用名称?

具有主题备用名称的 OpenSSL 证书(版本 3)

通用名称 (CN) 和主题备用名称 (SAN) 如何协同工作?

如何使用 Apache mod_ssl 变量验证 URI 格式的主题备用名称的内容?

CA签名X509证书包含X509v3扩展名“主题备用名称”两次

自 SSL/TLS 证书更新以来,Django 应用程序未连接到 RDS