Reputation: 6084
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.
Upvotes: 104
Views: 195072
Reputation: 61
Use the openssl's -ext
option, refer here for openssl x509
command options and here for available -extensions
flags you can use.
Sample output:
$ 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
Upvotes: 6
Reputation: 434
Just one more option if anyone prefers. This will capture any number of Alt Names, "push"ing each into a Bash array. Below the parsing code, I'll explain how to retrieve any single entry, all the entries, the count of the entries, and explain the command pipe by pipe.
Lets assume we have a cert that contains:
[...] 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: [...]
(with IP Address:127.0.0.1, IP Address:10.0.2.2, DNS:localhost
, of course, being our targets)
$ 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
I know this is way late to the party... but maybe it'll help someone with a similar need someday. <3
(Edit: Hey, sooo... I'm no Bash guru... I just use it a lot. If someone has a better way to perform this or streamline/improve it, please: drop me a comment below? I love to learn new techniques!)
Upvotes: 0
Reputation: 70722
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)
could be written:
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)
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
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 ) )
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
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 -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; }'
Have a look at bottom of How to determine SSL cert expiration date from a PEM encoded certificate?!!
Upvotes: 14
Reputation: 18089
gnutls
and certtool
$ gnutls-cli example.com -p 443 --print-cert < /dev/null | certtool -i | grep -C3 -i dns
openssl
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
| 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()
$ 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
Upvotes: 43
Reputation: 17234
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
➤
Upvotes: 0
Reputation: 791
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
Upvotes: 79
Reputation: 2006
Maybe this is enough:
openssl x509 -in cert.pem -noout -text -certopt ca_default,no_sigdump
Upvotes: 0
Reputation: 2135
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>
Upvotes: 0
Reputation: 1032
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
...
Upvotes: 0
Reputation: 19565
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.
Upvotes: 67
Reputation: 71
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
Upvotes: 7
Reputation: 9
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/'
Upvotes: 1
Reputation: 102205
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);
}
Upvotes: 4