While dealing with https communication in Java applications you might face the following error:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException
While this error might happen for a number of reasons, here I’ll describe the ones I’ve faced most often along with methods and tools that are helpful during debugging.
Please note that this post is not intended to have a general discussion about SSL Certificates and does not try to cover all sorts of problems you might face with them. It’s just a good starting point with some suggestions, so you could poke around and hopefully find out the cause of your issue easily.
Invalid certificate chain
Firstly, not all issues might be coming from your side. Sometimes it’s a sever configuration that is faulty. One of such server configuration issues is invalid certificate chain. It happens when a server only provides it’s certificate without intermediate one, that links the server certificate with a Root CA added to the truststore. While most modern browsers handle this problem by fetching intermediate certificates themselves, Java doesn’t.
Truststore in Java is a file that contains root certificates for Certificate Authorities that Java trusts. Meaning that for Java client to trust a resource - it’s certificate should be signed with one of the authorities from the truststore.
In order to check whether some server has a valid certificate chain you can use What’s My Chain Cert?
It also completes the chain if it’s invalid and allows to download a valid public certificate.
Checking chain with command line tools
echo -n | openssl s_client -connect google.com:443 -servername google.com CONNECTED(00000006) depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign verify return:1 depth=1 C = US, O = Google Trust Services, CN = GTS CA 1O1 verify return:1 depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com verify return:1 --- Certificate chain 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=*.google.com i:/C=US/O=Google Trust Services/CN=GTS CA 1O1 1 s:/C=US/O=Google Trust Services/CN=GTS CA 1O1 i:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign ---
Certificate chain part. It should go up till the Root CA (which is GlobalSign in this example).
In case it’s not - you have two options: either contact service responsible and ask him to fix the certificate chain, or add intermediate CA to the trust store.
Adding a certificate to the trust store
When either service provides an invalid certificate chain or uses self-signed certificates, or simply certificates that are signed by some CA that is not trusted by Java (e.g. older versions of Java do not trust Let’s Encrypt certificates), you’ll need to add this certificate to the truststore manually.
To do so you first need to download the certificate:
echo -n | openssl s_client -connect google.com:443 \ | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > server.crt
This one-liner uses the same
openssl command that was shown before, but with sed piped with it to grab only the certificate part from the output.
Once you got a certificate you need to import it into the Java trust store:
keytool -import -cacerts -alias server -file server.crt -storepass changeit -noprompt Certificate was added to keystore
Please note the password after
Verifying Java trust store
You can check the list of the trusted certificate issuers by running the following command and verifying that it contains the one that you’ve added:
keytool -list -cacerts -storepass changeit Keystore type: JKS Keystore provider: SUN Your keystore contains 137 entries aaacertificateservices, May 12, 2021, trustedCertEntry, Certificate fingerprint (SHA-256): D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4 accvraiz1, May 12, 2021, trustedCertEntry, Certificate fingerprint (SHA-256): 9A:6E:C0:12:E1:A7:DA:9D:BE:34:19:4D:47:8A:D7:C0:DB:18:22:FB:07:1D:F1:29:81:49:6E:D1:04:38:41:13 ...
Even a better way of verification - is to issue a test request to a service via secure connection. While you could simply spin up you application and see, it’s not always convenient or safe.
A better approach would be to spin up a small utility with the same set of JVM options and verify the connection only. I’ll use ssl-check. It simply creates a secure socket and initializes SSL Handshake process with a specified host.
In order to run it - go to the machine where you have Java truststore configured or get an interactive shell inside a container that is going to run your application, download and compile ssl-check:
wget https://raw.githubusercontent.com/commandercool/ssl-check/master/src/SSLCheck.java javac SSLCheck.java
Now start it, providing host info:
java -cp . SSLCheck google.com 443 About to connect to 'google.com' on port 443 Successfully connected
In case everything is configured right - you should see
Successfully connected message. This means that you are set and good to go with your application!