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

问题描述 投票:0回答:5

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

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

openssl s_client -connect www.website.example:443
openssl certificate ssl
5个回答
173
投票

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

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

首先,此命令连接到我们想要的站点(

website.example
,用于 SSL 的端口 443):

openssl s_client -connect website.example:443

然后通过管道 (

|
) 将其输入此命令:

openssl x509 -noout -text

这将获取证书文件并输出其所有有趣的细节。

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

通常会有很多我们不关心的输出(签名、发行者、扩展等),所以我们将 that 输送到一个简单的 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
命令。只需在
-in MyCertificate.crt
命令上添加
openssl x509
并再次通过 grep 管道,例如:

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

10
投票

如果您只想see 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
开关使它一次读取整个输入而不是逐行读取)


10
投票

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

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

5
投票

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

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

你从像

X509*
这样的函数中获得
SSL_get_peer_certificate
来自TLS连接,
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);

}

0
投票

因为有人只会得到 SAN 列表——每行一个:

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

最佳,

© www.soinside.com 2019 - 2024. All rights reserved.