Intro

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.

Certificate Chain
Figure 1. Certificate chain. Image source - wikipedia.

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

In case you can’t check a server certificate with What’s My Chain Cert? (because it’s not accessible from the internet for example), you can use the powerful command line tool called opnessl.

Just run:

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
---

And check 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 -storepass parameter. changeit is the default value.

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!