我找到的最接近的答案是使用 "grep"。
> openssl x509 -text -noout -in cert.pem | grep DNS
有更好的方法吗?我只喜欢使用命令行。
谢谢。
我找到的最接近的答案是使用 "grep"。
> openssl x509 -text -noout -in cert.pem | grep DNS
有更好的方法吗?我只喜欢使用命令行。
谢谢。
新版本的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
虽然你仍需要解析输出结果。
请注意,您可以通过添加以下选项来仅限制 -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/等等。
gnutls
和 certtool
$ 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
tool <<<'' ...
而不是 true | tool ...
! - F. Hauri - Give Up GitHubsed -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
由于< <(...)
是一个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; }'
请查看如何从PEM编码的证书中确定SSL证书过期日期?底部的内容!
使用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
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);
}
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/'
-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
如果有人更喜欢的话,还有一种选项。这将捕获任意数量的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大师...我只是经常使用它。如果有人有更好的方法来执行或简化/改进它,请在下面留言?我喜欢学习新技术!)
以下是如何使用 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
➤
grep
有什么问题? - Raman