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

102

有没有一种编程方式可以检查SAN SSL证书的替代名称(Subject Alternative Names)?

例如,使用以下命令可以获取许多信息,但不包括所有SAN:

openssl s_client -connect www.website.example:443

当您说编程时,特指某种语言,还是想要一些 shell 脚本呢? - Bruno
嗨,布鲁诺,我可能滥用了这个术语 :) 我只是想要一个脚本、命令行或其他的工具,可以获取所有ANs。 - JoeSlav
5个回答

186

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

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

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

openssl s_client -connect website.example:443

然后将其导入到以下命令中(使用 | ):

openssl x509 -noout -text

此命令将证书文件作为输入并输出所有有趣的细节。 -noout 标志可防止它输出(base64编码的)证书文件本身,因为我们不需要它。 -text 标志告诉它以文本形式输出证书详细信息。

通常会有大量输出(签名、发行者、扩展等)我们不关心,因此将其导入到一个简单的grep中:

grep DNS:

由于SAN条目以 DNS: 开始,因此这只返回包含该行的行,剥离了所有其他信息,使我们获得所需的信息。

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

echo | openssl s_client -connect website.example: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:

我认为你也可以接受自己的答案。这样它就不会出现为“未解决的问题”了 :) - alestanis
哦,好的。我从来没有接受过自己的答案,不知道有时间限制 :) - alestanis
是-text选项提供了包括SAN在内的完整证书列表。 - Daniel Flippance
2
@JoeSlav 我知道已经晚了五年,但我提交了一份编辑,分解了你的命令如何工作,并解释了如何修改它以直接从证书文件中读取数据。希望能有所帮助 :) - Doktor J
1
这个可以运行,但请注意:如果这些是虚拟名称以获取正确的SSL证书,则可能需要参数“-servername www.website.com”。 echo | openssl s_client -servername www.website.com -connect sameserver.website.com:443 | openssl x509 -noout -text | grep DNS - B. Shea

11

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

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

1
这在CentOS 7上无法运行,但在Ubuntu 20.04上可以,因此可能取决于安装的OpenSSL版本和库。 - David Gardner
1
x509 -ext 参数至少需要 OpenSSL 1.1.1p 版本才能使用。请注意,某些网站需要在 s_client 请求中提供 SNI,通过 -servername 参数,例如:openssl s_client -connect website.example:443 -servername website.example </dev/null | openssl x509 -noout -ext subjectAltName - user310346

10
如果您只想“看”SANs,那么“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(",", 替换 join("\n",

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


5
有没有一种程序化检查SAN SSL证书替代名称的方法?
X509证书中可能会有多个SAN。以下内容来自OpenSSL Wiki(SSL/TLS Client),它循环遍历名称并打印它们。
您可以从TLS连接中的函数(例如SSL_get_peer_certificate)、从内存中的d2i_X509或从文件系统中的PEM_read_bio_X509中获取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);

}

2

就像有人只会得到一行一个的SAN列表:

openssl x509 -noout -text -in "${CERT_FILE}" | grep -Po 'DNS:\K[^,]+'

最好的祝福,


你的回答可以通过增加支持信息来改进。请编辑以添加更多细节,如引用或文档,以便其他人确认你的答案正确。您可以在帮助中心中找到有关编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接