How to display the Subject Alternative Name of a certificate?
SslOpensslCertificateSsl CertificateX509certificateSsl Problem Overview
The closest answer that I found is using "grep".
> openssl x509 -text -noout -in cert.pem | grep DNS
Is there better way to do this? I only prefer command line.
Thanks.
Ssl Solutions
Solution 1 - Ssl
Note that you can limit the output of -text
to just the extensions by adding the following option:
-certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux
i.e.:
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
However, you'll still need to apply some text parsing logic to get just the Subject Alternative Name
.
If that isn't sufficient, I think you'll need to write a small program that uses the openssl library to extract the specific field you are looking for. Here are some example programs that show how to parse a cert, including extracting extension fields such as Subject Alternative Name
:
https://zakird.com/2013/10/13/certificate-parsing-with-openssl
Note that you don't have to use openssl and C if you go the programming route... you can pick your favorite language and ASN.1
parser library, and use that. For example, in Java, you could use http://jac-asn1.sourceforge.net/, and many others.
Solution 2 - Ssl
Newer versions of openssl have an '-ext' option that allows you to print only the subjectAltName record. Am using 'OpenSSL 1.1.1b' on Debian 9.9
openssl x509 -noout -ext subjectAltName -in cert.pem
Though you'll still need to parse the output.
The change was made in https://github.com/openssl/openssl/issues/3932
Solution 3 - Ssl
Fetch Certificate Data
gnutls
and certtool
With $ gnutls-cli example.com -p 443 --print-cert < /dev/null | certtool -i | grep -C3 -i dns
openssl
With Taken from https://stackoverflow.com/a/13128918/1695680
$ openssl s_client -connect example.com:443 < /dev/null | openssl x509 -noout -text | grep -C3 -i dns
Extracting Certificate Data
| grep -C3 -i dns
works for a simple-case, if your reviewing this data by hand sure works well enough. However certificate data is hierarchical, not line-oriented (so greping will be messy, particularly for ca chains).
I don't know of any x509 command line tools that can do key-value extraction, most systems I work with have python on-box or nearby so here is an approach using python, x509 interface provided by cryptography
on pypi. Using cryptography
is a little verbose, I didn't feel comfortable condensing this into a oneliner, but with this script you can extract dns names from certificates passed to stdin
#!/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()
Example
$ 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
Solution 4 - Ssl
There is my solution (using [tag:openssl] and [tag:sed]):
[tag:bash] first
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)
could be written:
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 )
and could render something like:
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
Same for live server
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 ) )
May output:
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 [tag:shell] now
As < <(...)
is a bashism, same command have to be written:
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;
}'
and
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; }'
Solution 5 - Ssl
Very simple solution using grep
openssl x509 -in /path/to/x509/cert -noout -text|grep -oP '(?<=DNS:|IP Address:)[^,]+'|sort -uV
For the google certificate, this outputs:
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
Solution 6 - Ssl
> How to display the Subject Alternative Name of a certificate?
There could be multiple SANs in a X509 certificate. The following is from the OpenSSL wiki at SSL/TLS Client. It loops over the names and prints them.
You get the X509*
from a function like SSL_get_peer_certificate
from a TLS connection, d2i_X509
from memory or PEM_read_bio_X509
from the filesystem.
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);
}
Solution 7 - Ssl
You can use awk
to get closer to the SAN, piping the above options into the awk
statement:
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/'
Solution 8 - Ssl
An improved awk-based solution (hat-tip: @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}'
This prints out a list, as do the grep
and sed
solutions also found here. The difference is that there is a tighter control of where the information is found. Should the output format ever change, this version is more robust and will better hand the changes. Only the text between "Subject Alternative Name" and the very next "X509v3" section are printed out, and all optional preceding "DNS:" text is stripped out.
android.clients.google.com
android.com
developer.android.google.cn
g.co
goo.gl
...
Solution 9 - Ssl
Adding a python alternative. Prerequisite is that you have a string with the "DNS:" records.
> Fetch the certificate details (subprocess, OpenSSL module, etc) dnsstring contains the "DNS:" line of the "openssl" output. Example of how to get the string of DNS names from a text output of the certificate.
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])
A command line example:
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()])"
Output:
*.google.com, *.android.com, <etc>
Solution 10 - Ssl
Maybe this is enough:
openssl x509 -in cert.pem -noout -text -certopt ca_default,no_sigdump
Solution 11 - Ssl
Here's how we can do this via awk
.
'/Subject: C=/{printf $NF"\n"}
matches any line which has pattern /Subject: C=
and {printf $NF"\n"}
simply prints the last field with a newline.
/DNS:/{x=gsub(/ *DNS:/, ""); printf "SANS=" $0"\n"}
matches line with pattern DNS:
. gsub
is used to replace unwanted DNS:
before every fqdn. printf "SANS="$0"\n"
prints the whole line with a newline.
➤ 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
➤