How to extract the Root CA and Subordinate CA from a certificate chain in Linux?
From a web site, you can do:
openssl s_client -showcerts -verify 5 -connect stackexchange.com:443 < /dev/null
That will show the certificate chain and all the certificates the server presented.
Now, if I save those two certificates to files, I can use openssl verify
:
$ openssl verify -show_chain -untrusted dc-sha2.crt se.crt
se.crt: OK
Chain:
depth=0: C = US, ST = NY, L = New York, O = "Stack Exchange, Inc.", CN = *.stackexchange.com (untrusted)
depth=1: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA (untrusted)
depth=2: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
The -untrusted
option is used to give the intermediate certificate(s); se.crt
is the certificate to verify. The depth=2 result came from the system trusted CA store.
If you don't have the intermediate certificate(s), you can't perform the verify. That's just how X.509 works.
Depending on the certificate, it may contain a URI to get the intermediate from. As an example, openssl x509 -in se.crt -noout -text
contains:
Authority Information Access:
OCSP - URI:http://ocsp.digicert.com
CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2HighAssuranceServerCA.crt
That "CA Issuers" URI points to the intermediate cert (in DER format, so you need to use openssl x509 -inform der -in DigiCertSHA2HighAssuranceServerCA.crt -out DigiCertSHA2HighAssuranceServerCA.pem
to convert it for further use by OpenSSL).
If you run openssl x509 -in /tmp/DigiCertSHA2HighAssuranceServerCA.pem -noout -issuer_hash
you get 244b5494
, which you can look for in the system root CA store at /etc/ssl/certs/244b5494.0
(just append .0
to the name).
I don't think there is a nice, easy OpenSSL command to do all that for you.
tl;dr - one liner bash magic to dump all certs in the chain
openssl s_client -showcerts -verify 5 -connect de.wikipedia.org:443 < /dev/null | awk '/BEGIN/,/END/{ if(/BEGIN/){a++}; out="cert"a".crt"; print >out}' && for cert in *.crt; do newname=$(openssl x509 -noout -subject -in $cert | sed -n 's/^.*CN=\(.*\)$/\1/; s/[ ,.*]/_/g; s/__/_/g; s/^_//g;p').pem; mv $cert $newname; done
Explanation in 2 steps
To dump all certs in the chain to the current dir as cert${chain_number}.pem
:
openssl s_client -showcerts -verify 5 -connect your_host:443 < /dev/null | awk '/BEGIN/,/END/{ if(/BEGIN/){a++}; out="cert"a".pem"; print >out}'
bonus-track to rename them to their common name:
for cert in *.pem; do newname=$(openssl x509 -noout -subject -in $cert | sed -n 's/^.*CN=\(.*\)$/\1/; s/[ ,.*]/_/g; s/__/_/g; s/^_//g;p').pem; mv $cert $newname; done
I found out that with the option -verify 5
openssl is going deep in the chain showing all the cert, even that not included in your certificate deployment.
If you really want to understand which chain is provided with your certificate you should run:
openssl s_client -showcerts -partial_chain -connect YOUR_ENDPOINT:443 < /dev/null |less