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

Posted

技术标签:

【中文标题】如何显示证书的主题备用名称?【英文标题】:How to display the Subject Alternative Name of a certificate? 【发布时间】:2014-01-25 19:40:15 【问题描述】:

我找到的最接近的答案是使用“grep”。

> openssl x509 -text -noout -in cert.pem | grep DNS

有没有更好的方法来做到这一点?我只喜欢命令行。

谢谢。

【问题讨论】:

grep 有什么问题? Stack Overflow 是一个编程和开发问题的网站。这个问题似乎离题了,因为它与编程或开发无关。请参阅帮助中心的What topics can I ask about here。也许Super User 或Unix & Linux Stack Exchange 会是一个更好的提问地方。另见Where do I post questions about Dev Ops? 参见this answer 仅列出名称,不包括“DNS:”等。 【参考方案1】:

请注意,您可以通过添加以下选项将-text 的输出限制为仅扩展名:

-certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux

即:

openssl x509 -text -noout -in cert.pem \
  -certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux

但是,您仍然需要应用一些文本解析逻辑来获取 Subject Alternative Name

如果这还不够,我认为您需要编写一个小程序,使用 openssl 库来提取您要查找的特定字段。以下是一些示例程序,展示了如何解析证书,包括提取扩展字段,例如 Subject Alternative Name

https://zakird.com/2013/10/13/certificate-parsing-with-openssl

请注意,如果您走编程路线,则不必使用 openssl 和 C……您可以选择自己喜欢的语言和 ASN.1 解析器库,然后使用它。例如,在 Java 中,您可以使用 http://jac-asn1.sourceforge.net/ 等。

【讨论】:

谢谢。我会试试看的。【参考方案2】:

较新版本的 openssl 有一个“-ext”选项,允许您仅打印 subjectAltName 记录。我在 Debian 9.9 上使用“OpenSSL 1.1.1b”

openssl x509 -noout -ext subjectAltName -in cert.pem

虽然您仍然需要解析输出。

更改是在https://github.com/openssl/openssl/issues/3932中进行的

【讨论】:

这个答案应该被提升以反映大多数最新设置中的现实,包括面向稳定性的发行版中的版本,比如 Debian【参考方案3】:

获取证书数据

使用gnutlscerttool

$ gnutls-cli example.com -p 443 --print-cert < /dev/null | certtool -i | grep -C3 -i dns

openssl

取自https://***.com/a/13128918/1695680

$ openssl s_client -connect example.com:443 < /dev/null | openssl x509 -noout -text | grep -C3 -i dns

提取证书数据

| grep -C3 -i dns 适用于一个简单的案例,如果您手动查看这些数据确实足够好的话。但是证书数据是分层的,而不是面向行的(所以 greping 会很混乱,尤其是对于 ca 链)。

我不知道任何可以进行键值提取的 x509 命令行工具,我使用的大多数系统都有 python on-box 或附近所以这里是一个使用 python 的方法,x509 接口由cryptography on pypi 提供。使用cryptography 有点冗长,我不习惯将其压缩成一个单行器,但使用此脚本,您可以从传递给标准输入的证书中提取 dns 名称

#!/usr/bin/env python3

import sys

import cryptography.x509
import cryptography.hazmat.backends
import cryptography.hazmat.primitives

DEFAULT_FINGERPRINT_HASH = cryptography.hazmat.primitives.hashes.SHA256


def _x509_san_dns_names(certificate):
    """ Return a list of strings containing san dns names
    """
    crt_san_data = certificate.extensions.get_extension_for_oid(
        cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
    )

    dns_names = crt_san_data.value.get_values_for_type(
        cryptography.x509.DNSName
    )

    return dns_names


def _find_certificate_pem(stream):
    """ Yield hunks of pem certificates
    """
    certificate_pem = []
    begin_certificate = False
    for line in stream:
        if line == b'-----END CERTIFICATE-----\n':
            begin_certificate = False
            certificate_pem.append(line)
            yield b''.join(certificate_pem)
            certificate_pem = []

        if line == b'-----BEGIN CERTIFICATE-----\n':
            begin_certificate = True

        if begin_certificate:
            certificate_pem.append(line)


def _dump_stdincert_san_dnsnames():
    """ Print line-oriented certificate fingerprint and san dns name
    """
    for certificate_pem in _find_certificate_pem(sys.stdin.buffer):
        certificate = cryptography.x509.load_pem_x509_certificate(
            certificate_pem,
            cryptography.hazmat.backends.default_backend()
        )
        certificate_fingerprint = certificate.fingerprint(
            DEFAULT_FINGERPRINT_HASH(),
        )
        certificate_fingerprint_str = ':'.join(
            ':02x'.format(i) for i in certificate_fingerprint
        )
        try:
            for dns_name in _x509_san_dns_names(certificate):
                sys.stdout.write(' \n'.format(certificate_fingerprint_str, dns_name))

        except cryptography.x509.extensions.ExtensionNotFound:
            sys.stderr.write(' Certificate has no extension SubjectAlternativeName\n'.format(certificate_fingerprint_str))


def main():
    _dump_stdincert_san_dnsnames()


if __name__ == '__main__':
    main()

#### 例子
$ true | openssl s_client -connect localhost:8443 | openssl x509 -noout -text | grep DNS:
depth=2 C = US, ST = NC, L = SomeCity, O = SomeCompany Security, OU = SomeOU, CN = SomeCN
verify error:num=19:self signed certificate in certificate chain
DONE
                DNS:localhost, DNS:127.0.0.1, DNS:servername1.somedom.com, DNS:servername2.somedom.local

【讨论】:

我想知道与 gnutls-cli 相关的等价物 这应该是公认的答案。工作很轻松。 避免无用的分叉!!而不是true | tool ...,你可以写tool &lt;&lt;&lt;'' ...【参考方案4】:

有我的解决方案(使用openssl 和sed):

bash第一

sed -ne '
    s/^\( *\)Subject:/\1/p;
    /X509v3 Subject Alternative Name/
        N;
        s/^.*\n//;
      :a;
        s/^\( *\)\(.*\), /\1\2\n\1/;
        ta;
        p;
        q;
    ' < <(openssl x509 -in cert.pem -noout -text)

可以写成:

sed -ne 's/^\( *\)Subject:/\1/p;/X509v3 Subject Alternative Name/
    N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; ' < <(
    openssl x509 -in cert.pem -noout -text )

并且可以呈现如下内容:

         CN=www.example.com
                DNS:il0001.sample.com
                DNS:example.com
                DNS:demodomain.com
                DNS:testsite.com
                DNS:www.il0001.sample.com
                DNS:www.il0001.sample.com.vsite.il0001.sample.com
                DNS:www.example.com
                DNS:www.example.com.vsite.il0001.sample.com
                DNS:www.demodomain.com
                DNS:www.demodomain.com.vsite.il0001.sample.com
                DNS:www.testsite.com
                DNS:www.testsite.com.vsite.il0001.sample.com

直播服务器也一样

sed -ne 's/^\( *\)Subject:/\1/p;/X509v3 Subject Alternative Name/
    N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; ' < <(
    openssl x509 -noout -text -in <(
        openssl s_client -ign_eof 2>/dev/null <<<$'HEAD / HTTP/1.0\r\n\r' \
            -connect google.com:443 ) )

可能输出:

         C=US, ST=California, L=Mountain View, O=Google Inc, CN=*.google.com
                DNS:*.google.com
                DNS:*.android.com
                DNS:*.appengine.google.com
                DNS:*.cloud.google.com
                DNS:*.gcp.gvt2.com
                DNS:*.google-analytics.com
                DNS:*.google.ca
                DNS:*.google.cl
                DNS:*.google.co.in
                DNS:*.google.co.jp
                DNS:*.google.co.uk
                DNS:*.google.com.ar
                DNS:*.google.com.au
                DNS:*.google.com.br
                DNS:*.google.com.co
                DNS:*.google.com.mx
                DNS:*.google.com.tr
                DNS:*.google.com.vn
                DNS:*.google.de
                DNS:*.google.es
                DNS:*.google.fr
                DNS:*.google.hu
                DNS:*.google.it
                DNS:*.google.nl
                DNS:*.google.pl
                DNS:*.google.pt
                DNS:*.googleadapis.com
                DNS:*.googleapis.cn
                DNS:*.googlecommerce.com
                DNS:*.googlevideo.com
                DNS:*.gstatic.cn
                DNS:*.gstatic.com
                DNS:*.gvt1.com
                DNS:*.gvt2.com
                DNS:*.metric.gstatic.com
                DNS:*.urchin.com
                DNS:*.url.google.com
                DNS:*.youtube-nocookie.com
                DNS:*.youtube.com
                DNS:*.youtubeeducation.com
                DNS:*.ytimg.com
                DNS:android.clients.google.com
                DNS:android.com
                DNS:developer.android.google.cn
                DNS:g.co
                DNS:goo.gl
                DNS:google-analytics.com
                DNS:google.com
                DNS:googlecommerce.com
                DNS:urchin.com
                DNS:www.goo.gl
                DNS:youtu.be
                DNS:youtube.com
                DNS:youtubeeducation.com

POSIX shell 现在

由于&lt; &lt;(...) 是一个bashism,所以必须编写相同的命令:

openssl x509 -in cert.pem -noout -text | sed -ne '
  s/^\( *\)Subject:/\1/p;
  /X509v3 Subject Alternative Name/
      N;
      s/^.*\n//;
    :a;
      s/^\( *\)\(.*\), /\1\2\n\1/;
      ta;
      p;
      q;
  '

printf 'HEAD / HTTP/1.0\r\n\r\n' |
    openssl s_client -ign_eof 2>/dev/null -connect google.com:443 |
    openssl x509 -noout -text |
    sed -ne 's/^\( *\)Subject:/\1/p;/X509v3 Subject Alternative Name/
        N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; '

【讨论】:

【参考方案5】:

使用 grep 的非常简单的解决方案

openssl x509 -in /path/to/x509/cert -noout -text|grep -oP '(?<=DNS:|IP Address:)[^,]+'|sort -uV

对于谷歌证书,输出:

android.clients.google.com
android.com
developer.android.google.cn
g.co
goo.gl
google.com
googlecommerce.com
google-analytics.com
hin.com
urchin.com
www.goo.gl
youtu.be
youtube.com
youtubeeducation.com
*.android.com
*.appengine.google.com
*.cloud.google.com
*.gcp.gvt2.com
*.googleadapis.com
*.googleapis.cn
*.googlecommerce.com
*.googlevideo.com
*.google.ca
*.google.cl
*.google.com
*.google.com.ar
*.google.com.au
*.google.com.br
*.google.com.co
*.google.com.mx
*.google.com.tr
*.google.com.vn
*.google.co.in
*.google.co.jp
*.google.co.uk
*.google.de
*.google.es
*.google.fr
*.google.hu
*.google.it
*.google.nl
*.google.pl
*.google.pt
*.gstatic.cn
*.gstatic.com
*.gvt1.com
*.gvt2.com
*.metric.gstatic.com
*.urchin.com
*.url.google.com
*.youtubeeducation.com
*.youtube.com
*.ytimg.com
*.google-analytics.com
*.youtube-nocookie.com

【讨论】:

欢迎来到 Stack Overflow!感谢您提供此代码 sn-p,它可能会提供一些有限的短期帮助。一个正确的解释would greatly improve 它的长期价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。【参考方案6】:

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

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


【讨论】:

【参考方案7】:

您可以使用awk 更接近SAN,将上述选项通过管道传递到awk 语句中:

openssl x509 -in mycertfile.crt -text -noout \
  -certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_subject,no_issuer,no_pubkey,no_sigdump,no_aux \
 | awk '/X509v3 Subject Alternative Name/','/X509v3 Basic Constraints/'

【讨论】:

我不明白您所说的“上述选项”是什么意思。【参考方案8】:

改进的基于 awk 的解决方案(帽子提示:@RandomW):

openssl x509 -in certfile -text -noout \
  -certopt no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux \
| awk '/X509v3 Subject Alternative Name:/ san=1;next 
      san && /^ *X509v3/ exit 
      san  sub(/DNS:/,"",$1);print $1'

这会打印出一个列表,grepsed 解决方案也可以在这里找到。不同之处在于对信息的查找位置进行了更严格的控制。如果输出格式发生变化,这个版本会更加健壮,并且会更好地处理这些变化。只有“Subject Alternative Name”和下一个“X509v3”部分之间的文本被打印出来,所有可选的“DNS:”前面的文本都被删除。

android.clients.google.com
android.com
developer.android.google.cn
g.co
goo.gl
...

【讨论】:

【参考方案9】:

添加一个 python 替代品。 先决条件是您有一个带有“DNS:”记录的字符串。

获取证书详细信息(子进程、OpenSSL 模块等) dnsstring 包含“openssl”输出的“DNS:”行。 如何从文本输出中获取 DNS 名称字符串的示例 证书。

for idx, line in enumerate(certoutput.split()):
    if ' X509v3 Authority Key Identifier:' in line:
        dnsstring = certoutput.split()[idx + 1]

# Get a list
[x.replace('DNS:', '').replace(',', '') for x in dnsstring]

# Format to a comma separated string
', '.join([x.replace('DNS:', '').replace(',', '') for x in dnsstring])

命令行示例:

true | \
  openssl s_client -showcerts -connect google.com:443 2>/dev/null | \
  openssl x509 -noout -text 2>/dev/null | grep " DNS:" | \
  python -c"import sys; print ', '.join([x.replace('DNS:', '').replace(',', '') for x in sys.stdin.readlines()[0].split()])"

输出:

*.google.com, *.android.com, <etc>

【讨论】:

【参考方案10】:

也许这就够了:

openssl x509 -in cert.pem -noout -text -certopt ca_default,no_sigdump

【讨论】:

【参考方案11】:

以下是我们如何通过awk 执行此操作。

'/Subject: C=/printf $NF"\n" 匹配任何具有/Subject: C= 模式的行,printf $NF"\n" 只是用换行符打印最后一个字段。

/DNS:/x=gsub(/ *DNS:/, ""); printf "SANS=" $0"\n" 匹配模式 DNS: 的行。 gsub 用于在每个 fqdn 之前替换不需要的 DNS:printf "SANS="$0"\n" 用换行符打印整行。

➤ echo | openssl s_client -connect google.com:443 2>&1 | openssl x509 -noout -text |  awk '/Subject: C=/printf $NF"\n" /DNS:/x=gsub(/ *DNS:/, ""); printf "SANS=" $0"\n"'
CN=*.google.com
SANS=*.google.com,*.android.com,*.appengine.google.com,*.cloud.google.com,*.crowdsource.google.com,*.g.co,*.gcp.gvt2.com,*.gcpcdn.gvt1.com,*.ggpht.cn,*.gkecnapps.cn,*.google-analytics.com,*.google.ca,*.google.cl,*.google.co.in,*.google.co.jp,*.google.co.uk,*.google.com.ar,*.google.com.au,*.google.com.br,*.google.com.co,*.google.com.mx,*.google.com.tr,*.google.com.vn,*.google.de,*.google.es,*.google.fr,*.google.hu,*.google.it,*.google.nl,*.google.pl,*.google.pt,*.googleadapis.com,*.googleapis.cn,*.googlecnapps.cn,*.googlecommerce.com,*.googlevideo.com,*.gstatic.cn,*.gstatic.com,*.gstaticcnapps.cn,*.gvt1.com,*.gvt2.com,*.metric.gstatic.com,*.urchin.com,*.url.google.com,*.wear.gkecnapps.cn,*.youtube-nocookie.com,*.youtube.com,*.youtubeeducation.com,*.youtubekids.com,*.yt.be,*.ytimg.com,android.clients.google.com,android.com,developer.android.google.cn,developers.android.google.cn,g.co,ggpht.cn,gkecnapps.cn,goo.gl,google-analytics.com,google.com,googlecnapps.cn,googlecommerce.com,source.android.google.cn,urchin.com,www.goo.gl,youtu.be,youtube.com,youtubeeducation.com,youtubekids.com,yt.be

➤

【讨论】:

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

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

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

无法使用主题备用名称证书为 SSL/TLS 安全通道建立信任关系

显示名称证书OID - Windows

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

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