Broken Certificate Chain
Certificate chain issues are extremely widespread and more common than you think. You start to see how many websites have broken certificate chains when you start performing SSL inspection on a network.
If you want to read more about certificate chains, my post Certificate Chain goes into more detail.
My website, thedxt.ca, has a server certificate for thedxt.ca, issued by the intermediate CA GTS CA 1P5. The intermediate CA GTS CA 1P5 certificate is issued by the public root CA GTS Root R1.
The image above is an example of a correctly formed certificate chain. Each item is issued by the previous one, and because the root CA is trusted, the entire chain is trusted.
In this post, I will go over what can cause a broken certificate chain and how to verify your certificate chain properly.
There are two main types of broken certificate chains. First is the malformed certificate chain or the out of order certificate chain. The second is an incomplete certificate chain.
Malformed Certificate Chain
If my certificate chain for my website, thedxt.ca, was configured in the following order:
- A server certificate for thedxt.ca.
- The public root CA GTS Root R1 certificate.
- The intermediate CA GTS CA 1P5 certificate.
This order would cause a broken certificate chain as the certificate chain would be out of order and considered malformed, which can cause issues.
The image above is an example of a malformed certificate chain, as each item is not issued by the previous one.
Incomplete Certificate Chain
Another common issue that causes broken certificate chains is an incomplete certificate chain. An incomplete certificate chain means a certificate is missing in the certificate chain. Usually, this is when one or more intermediate CA certificates are missing. It’s an issue because the path to the root CA can’t be completed.
The image above is an example of an incomplete certificate chain, as there are no links to follow for the path to the root CA.
The Why
Many people don’t realize they have a broken certificate chain, as most things will just work even when the certificate chain is broken. Web browsers try to fix broken certificate chains for websites so users don’t run into issues.
A very common practice for checking if your SSL/TLS certificate is working on a public-facing item is to use a web browser to browse to the item and see if you get any certificate issues. Because web browsers auto-fix broken certificate chains, you end up with a false sense that everything is working correctly.
If you can’t trust a web browser to check if your certificate chain is correct, how do you check it?
Correctly Checking a Certificate Chain
The best way I’ve found to check your certificate chain is to use OpenSSL.
Prerequisites
- OpenSSL binary installed. You can find the OpenSSL binaries on the OpenSSL wiki.
- OPTIONAL: Place the root CA certificate in a working directory. I will be using
C:\SSL
as my working directory.
OpenSSL will still work even if you don’t place the root CA certificate in a working directory. However, you will get errors such as Verify return code: 20 (unable to get local issuer certificate)
or Verify return code: 19 (self-signed certificate in certificate chain)
. These errors appear because root CA certificates are self-signed (this is normal) or because the root CA certificate is not included, which is also correct, and because OpenSSL doesn’t trust anything, including public root CAs.
The Process
- Open command line. You can use Linux or Windows. The commands are all the same regardless of which OS you are using. I will be using Microsoft Windows with Windows Terminal and PowerShell.
The command below will output the certificates of the host we choose to check. We use s_client
to tell OpenSSL that we want to invoke the SSL/TLS client program. We use the option showcerts
to tell OpenSSL to output the certificates that the server we connect to sends back. We use the option connect
followed by the host and the port to tell OpenSSL what to connect to.
- Run the following command and replace HOST with the host’s FQDN from which you want to check the certificates. Replace PORT with the port that is being used by the application.
OpenSSL s_client -showcerts -connect HOST:PORT
For me, that command will look like OpenSSL s_client -showcerts -connect thedxt.ca:443
OPTIONAL: If you decide to follow the optional prerequisite of placing the root CA certificate in your working directory, you can specify it with the CAfile
option, as this tells OpenSSL which root CA certificate to use, as OpenSSL will not use any by default. Add CAfile
to the end of your command and replace PATH_TO_CA with the path to your CA certificate file. OpenSSL s_client -showcerts -connect HOST:PORT -CAfile PATH_TO_CA.crt
For me, that command will look like OpenSSL s_client -showcerts -connect thedxt.ca:443 -CAfile C:\SSL\GTS_Root_R1.crt
When you run the command, the output will look something like this.
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify return:1
depth=1 C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
verify return:1
depth=0 CN=thedxt.ca
verify return:1
---
Certificate chain
0 s:CN=thedxt.ca
i:C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: May 28 14:16:51 2024 GMT; NotAfter: Aug 26 14:16:50 2024 GMT
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgIQbHfL/SFxXawTv8x+n9HnWjANBgkqhkiG9w0BAQsFADBG
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
qMIIsm4o7/PVsX+/IYjjgRHBLIDPtUMRbkD5qwKItjjYhBKf4/ZTvTXLGgS5sY2y
mjs68EekumMy+00uRGzBcR4ZE0dMXiYIVpWy/XYgOowh/zxNGXU=
-----END CERTIFICATE-----
1 s:C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
i:C=US, O=Google Trust Services LLC, CN=GTS Root R1
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Aug 13 00:00:42 2020 GMT; NotAfter: Sep 30 00:00:42 2027 GMT
-----BEGIN CERTIFICATE-----
MIIFjDCCA3SgAwIBAgINAgO8UKMnU/CRgCLt8TANBgkqhkiG9w0BAQsFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
0/H4zRK5aiWQW+OFIOb12stAHBk0IANhd7p/SA9JCynr52Fkx2PRR+sc4e6URu85
c8zuTyuN3PtYp7NlIJmVuftVb9eWbpQ99HqSjmMd320=
-----END CERTIFICATE-----
2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R1
i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
v:NotBefore: Jun 19 00:00:42 2020 GMT; NotAfter: Jan 28 00:00:42 2028 GMT
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE
+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi
d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
-----END CERTIFICATE-----
Code language: plaintext (plaintext)
With the output above you can see that the certificate chain goes from the server certificate for thedxt.ca that is issued by the intermediate CA GTS CA 1P5 to the intermediate CA GTS CA 1P5 certificate that is issued by the public root CA GTS Root R1 and then to the public root CA GTS Root R1.
If you run the same command against the website incomplete-chain.badssl.com, you can see what an incomplete chain looks like. This is the command to do that OpenSSL s_client -showcerts -connect incomplete-chain.badssl.com:443
The output should look something like this.
depth=0 C=US, ST=California, L=Walnut Creek, O=Lucas Garron Torres, CN=*.badssl.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C=US, ST=California, L=Walnut Creek, O=Lucas Garron Torres, CN=*.badssl.com
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 C=US, ST=California, L=Walnut Creek, O=Lucas Garron Torres, CN=*.badssl.com
verify error:num=10:certificate has expired
notAfter=May 17 12:00:00 2022 GMT
verify return:1
depth=0 C=US, ST=California, L=Walnut Creek, O=Lucas Garron Torres, CN=*.badssl.com
notAfter=May 17 12:00:00 2022 GMT
verify return:1
---
Certificate chain
0 s:C=US, ST=California, L=Walnut Creek, O=Lucas Garron Torres, CN=*.badssl.com
i:C=US, O=DigiCert Inc, CN=DigiCert SHA2 Secure Server CA
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Mar 23 00:00:00 2020 GMT; NotAfter: May 17 12:00:00 2022 GMT
-----BEGIN CERTIFICATE-----
MIIGqDCCBZCgAwIBAgIQCvBs2jemC2QTQvCh6x1Z/TANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
MwdGrM6kt0lfJy/gvGVsgIKZocHdedPeECqAtq7FAJYanOsjNN9RbBOGhbwq0/FP
CC01zojqS10nGowxzOiqyB4m6wytmzf0QwjpMw==
-----END CERTIFICATE-----
Code language: plaintext (plaintext)
With the output above you can see that there’s no path to the root CA as the only certificate is the server certificate, which is issued by the intermediate CA DigiCert SHA2 Secure Server CA. Due to the missing intermediate CA certificate, the certificate chain has no links to follow to the root CA, which can result in issues. If the certificate wasn’t expired and you used a web browser to visit the site you would see that you can browse it without issue as your web browser is fixing the certificate chain automatically. If you ignore the expired certificate error you test this for yourself.
There are websites dedicated to checking certificate chains. One I like to use as it’s very fast is What’s My Chain Cert? https://whatsmychaincert.com/
Another one I like to use when I want even more details is the SSL Server Test created and maintained by SSL Labs https://www.ssllabs.com/ssltest/.
2 responses to “Broken Certificate Chain”