【问题标题】: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

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

谢谢。

【问题讨论】:

标签: ssl openssl certificate ssl-certificate x509certificate


【解决方案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://stackoverflow.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】:

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

第一

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 现在

由于&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
            
            ➤
            

            【讨论】:

              猜你喜欢
              • 2023-01-18
              • 2012-10-19
              • 2015-09-07
              • 1970-01-01
              • 2020-11-13
              • 1970-01-01
              • 2021-02-28
              • 2011-09-05
              • 2021-08-13
              相关资源
              最近更新 更多