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

98

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

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

有更好的方法吗?我只喜欢使用命令行。

谢谢。


5
grep有什么问题? - Raman
3
Stack Overflow是一个面向编程和开发问题的网站。这个问题似乎与编程或开发无关,因此不适合在此提问。请查看帮助中心中的我可以在这里询问哪些主题。也许Super UserUnix & Linux Stack Exchange更适合您提出问题。另请参阅我在哪里发布有关Dev Ops的问题? - jww
请参考此答案,仅列出名称,不包括“DNS:”等内容。 - mivk
13个回答

66

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

较新版的 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。 - Bernard Rosset

65

请注意,您可以通过添加以下选项来仅限制 -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/等等。


谢谢。我会尝试的。 - user180574

40

获取证书数据

使用 gnutlscerttool

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

使用 openssl

来源:https://dev59.com/9mcs5IYBdhLWcg3wHwNH#13128918

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

提取证书数据

| grep -C3 -i dns 对于简单情况有效,如果您手动审查数据,则足够好用。但是,证书数据是分层的,而不是面向行的(因此使用grep会很混乱,特别是对于ca链)。我不知道有哪些x509命令行工具可以进行键值提取,我使用的大多数系统都在附近或者安装了python,因此这里介绍一种使用python、由cryptography 在pypi上提供的x509接口的方法。使用cryptography 有点冗长,我不舒服将其缩成一行,但是通过此脚本,可以从传递给stdin的证书中提取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实现相同的功能。 - ThorSummoner
2
这应该是被接受的答案。很容易就解决了。 - slm
1
避免无用的分支!你可以写 tool <<<'' ... 而不是 true | tool ... - F. Hauri - Give Up GitHub

14

以下是我的解决方案(使用 ):

首先使用

sed -ne '
    s/^\( *\)[Ss]ubject[:=] */  \1/p;
    /X509v3 Subject Alternative Name/{
        N;
        s/^.*\n//;
      :a;
        s/^\( *\)\(.*\), /\1\2\n\1/;
        ta;
        p;
        q;
    }' < <(openssl x509 -in cert.pem -noout -subject -ext subjectAltName)

可以写成:

sed -ne 's/^\( *\)[Ss]ubject[=:] */  \1/p;/X509v3 Subj.*Alt.*Name/{
    N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }' < <(
    openssl x509 -in cert.pem -noout -subject -ext subjectAltName)

并可能渲染类似以下内容:

         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/^\( *\)[Ss]ubject[=:] */  \1/p;/X509v3 Subject Alternative Name/{
    N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }' < <(
    openssl x509 -noout -subject -ext subjectAltName -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

由于< <(...)是一个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 -subject -ext subjectAltName |
    sed -ne 's/^\( *\)[Ss]ubject[=:] */  \1/p;/X509v3 Subj.*Alt.*Name/{
        N;s/^.*\n//;:a;s/^\( *\)\(.*\), /\1\2\n\1/;ta;p;q; }'

另一个答案上有完整的Bash脚本

请查看如何从PEM编码的证书中确定SSL证书过期日期?底部的内容!


解析x509数据:如何从PEM编码的证书中确定SSL证书到期日期? - F. Hauri - Give Up GitHub

7

使用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!感谢您提供这段代码片段,它可能会在短期内提供一些有限的帮助。通过展示为什么这是一个好的问题解决方案,适当的解释将会极大地提高其长期价值,并使其对未来遇到类似问题的读者更有用。请编辑您的答案以添加一些解释,包括您的假设。 - Toby Speight

3
如何显示证书的主题备用名称?
X509证书中可能有多个备用名称。以下内容来自OpenSSL维基上的 SSL / TLS客户端。它循环遍历名称并打印它们。
您可以从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);

}

1
你可以使用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/'

我不明白你所说的“上述选项”是什么意思。 - thomasd

1
使用openssl-ext选项,参考这里获取openssl x509命令选项和这里获取可用的-extensions标志。
示例输出:
$ openssl x509 -noout -ext subjectAltName -in /etc/core/.pki/kong.pem 
X509v3 Subject Alternative Name: 
        DNS:localhost, DNS:dr.dev.local, DNS:pnpserver.dev.local, DNS:kong, DNS:kong.core-system, DNS:kong.core-system.svc, DNS:kong.core-system.svc.cluster, DNS:kong.core-system.svc.cluster.local, DNS:kong-frontend, DNS:kong-frontend.core-system, DNS:kong-frontend.core-system.svc, DNS:kong-frontend.core-system.svc.cluster, DNS:kong-frontend.core-system.svc.cluster.local, IP Address:196.196.196.101, IP Address:10.23.214.43, IP Address:192.168.101.99, IP Address:192.168.101.100, IP Address:196.196.196.100, IP Address:10.23.214.44

dd


0

解析为数组

如果有人更喜欢的话,还有一种选项。这将捕获任意数量的Alt名称,并将每个名称“push”到Bash数组中。在解析代码下面,我将解释如何检索任何单个条目、所有条目、条目计数,并逐个解释命令管道。

假设我们有一个包含以下内容的证书:

[...]
X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Subject Alternative Name:
                IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
[...]

(当然,我们的目标是)IP地址:127.0.0.1,IP地址:10.0.2.2,DNS:localhost

解析代码

$ ALT_NAMES=($(openssl x509 -in cert.pem -noout -text | grep -A 1 "Subject Alternative Name" | tail -1 | sed -E "s/ //g" | tr "," "\n" | sed -E "s/^.*:(\S*)/\1/g" | tr "\n" " "))

$ # Let's see what we got
$ echo "${ALT_NAMES[*]}"
  127.0.0.1 10.0.2.2 localhost

$ # Hey, great! How about just the second one?
$ echo "${ALT_NAMES[1]}"     # (Zero-indexed array)
  10.0.2.2

$ # Okay... total count?
$ echo "${#ALT_NAMES[*]}"
  3

那个东西是怎么回事来着?

# Defining an array ( VAR=(<space separated values>) )...
ALT_NAMES=([...]

   # ... expand the following expression into the contents of said array...
         $(

   # ... the OpenSSL command we're using to read the cert...
         openssl x509 -in cert.pem -noout -text | 

   # ... grep for the Alt Names. The "-A 1" (read: -A(fter) 1) gets the next line after our match, too...
         grep -A 1 "Subject Alternative Name" | 
#result: "            X509v3 Subject Alternative Name:
                         IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost"

   # ... (that extra line being the only one we actually wanted anyway)...
         tail -1 | 
#result: "               IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost"

   # ... strip all whitespace...
         sed -E "s/ //g" | 
#result: "IPAddress:127.0.0.1,IPAddress:10.0.2.2,DNS:localhost"

   # ... Text Replace the commas with new lines...
         tr "," "\n" | 
#result: "IPAddress:127.0.0.1
          IPAddress:10.0.2.2
          DNS:localhost"

   # ... so we can discard all the text from start of each line to ":"...
         sed -E "s/^.*:(\S*)/\1/g" | 
#result: "127.0.0.1
          10.0.2.2
          localhost"

   # ... then knock them all back to a single, space-delimited line...
         tr "\n" " "
#result: "127.0.0.1 10.0.2.2 localhost"

   # ... before terminating our expansion...
         )
#result: "127.0.0.1 10.0.2.2 localhost"

# ... and finally conclude by ending the array declaration.
)
#result: "ALT_NAMES=(127.0.0.1 10.0.2.2 localhost)"

简而言之

$ ALT_NAMES=($(openssl x509 -in cert.pem -noout -text | grep -A 1 "Subject Alternative Name" | tail -1 | sed -E "s/ //g" | tr "," "\n" | sed -E "s/^.*:(\S*)/\1/g" | tr "\n" " "))
$ printf "Number of entries: ${#ALT_NAMES[*]}\nAll entry values: ${ALT_NAMES[*]}\nSingle entry: ${ALT_NAMES[1]}"

# Outputs:
    Number of entries: 3
    All entry values: 127.0.0.1 10.0.2.2 localhost
    Single entry: 10.0.2.2

我知道这已经晚了很多...但也许它会在将来帮助到有类似需求的人。 <3

(编辑:嘿,所以...我不是Bash大师...我只是经常使用它。如果有人有更好的方法来执行或简化/改进它,请在下面留言?我喜欢学习新技术!)


0

以下是如何使用 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

➤

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