Skip to main content

How to fix SSLHandshakeException PKIX path building failed in Java



TL;DR

1. Extract the public certificate of the website/API that you are trying to connect from your Java application.

Steps are mentioned in this post

2. Use the Java keytool to install the extracted certificate into the "cacerts" file (Trust store)

keytool -import -trustcacerts -alias <domain name> -file <public certificate>.cert -keystore /path_to_java_home/jre/lib/security/cacerts -storepass changeit

3. Restart your Java application




Exception

A typical exception stack trace would look like below.

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026) at sun.security.ssl.Handshaker.process_record(Handshaker.java:961) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl... 

Exception de-mistified


Let's look at the above exception closely.

SSLHandshakeException :
This says it happened when you try to make an HTTP call to a website in your Java application, and it was a website enabled HTTPS.

PKIX path building failed :
PKIX is the acronym for Public-Key Infrastructure X.509. Which means this is something to with the certificates in the HTTPS connections. X.509 is the format of the certificate. Path building failed means it failed to find some kind of path for an X.509 certificate.

unable to find valid certification path to requested target at :
The requested target is the website/URL that you were trying to reach.
Apparently, it was looking for a valid certificate in some path in order to connect with that website.



The role of SSL certificates in HTTPS


To understand how HTTPS works in complete flow, please have a look at this post on the below link.

How HTTPS works

If you want to skip, that's fine I'll briefly explain only how the certificates fit in here.

We all know that HTTPS makes your request more secure. Why? HTTPS buys us two things.

1. Authenticity validation or the Trust
2. Encryption

The main role of the certificates is to provide the trust or the authenticity.



Let me explain this with a story;

I need to buy an antique Volkswagen Beetle car from the 70s, but I know nothing about antique cars or beetle (Don't tell me I shouldn't consider buying it in the first place). So I reach out to a guy who sells an antique Beatle and he tells me all about how special and unique model it is and a price. I'm not sure if that price is fair or if he is lying about the car's historical and unique value.

So, I ask him "Buddy, I don't know you, so how can I trust what you are saying is true about the car?". Then he would tell me "Do you know Mr. Peters who lives there, he is a customer of mine. He'd vouch for me."

Then I think to myself, ok I know Mr. Peters personally and he got a good sense of old cars. In fact, it is true that he owns one. So, I can trust this guy!

So, this is how trust works also in digital certificates.

And if I went to another seller whom I personally know, then I can trust him anyway without checking with anybody else.



Digital trust


The SSL certificate of a typical website is also signed by some third party who is well known. So if the client browser knows this third party, then it can also trust this website. Some times this go couple of levels above.

If I need to connect to a website A and it has a certificate signed by B, but I don't know A or B. But, B's certificate is signed by C who I know. So, I can trust B, because I can trust B, then I can trust A.

This is known as the certificate chain. you'll be able to see this in a typical certificate like below.




The above image is from the Microsoft web site's digital certificate. It has a chain of 3 certificates. The lowest certificate is the microsoft.com's certificate. It is signed by Microsoft IT TLS CA 5 which is a CA (Certificate Authority) of Microsoft. This certificate is signed by DigiCert. So the top-level certificate we call the root certificate. This root certificate is the certificate of the root CA. There are about 180 root CAs for the whole internet and (more may add) and the other mid-level certificate authorities are kind of like resellers, they are certified by the root CA to issue certificates to normal websites.

In order to trust a website, the client (the web browser) has to know either the website's certificate or the mid-level certificate authority or the root certificate authority. Normally, all web browsers have a built-in list of root CA certificates. They update these certificates from time to time whenever they expire or whenever new ones being added. The same kind of list is included in Java (JRE) as well, we call it the trust store. This file is located in the following location.

$JAVA_HOME/jre/lib/security/cacerts

The filename "cacerts" is to mean CA  (Certificate Authority) Certificates. However, if you don't update the Java version, you will have to manually update this "cacerts" file. Normally people don't update the Java version of a live production server application frequently enough.


JVM as a client

This is where most people get confused when talking about the Java web app and installing SSL certificate to fix this kind of error. 

Whenever people say about SSL certificate error, they jump into the conclusion where such that you have to update your server certificate and that is WRONG in this case. No, it is actually a scenario where your Java app is the client.






Java code as an HTTP client

Following is a typical Java code as an HTTP client making a GET request to an HTTPS website.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class Main {

    public static void main(String[] args) {

        HttpGet request = new HttpGet("https://letsencrypt.org");

        CloseableHttpClient httpClient = HttpClients.createDefault();

        try (CloseableHttpResponse response = httpClient.execute(request)) {

            System.out.println(response.getStatusLine().toString());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


I have used the Apache httpclient library to invoke the HTTP call.

Maven dependency:



        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.10</version>
        </dependency>


If I execute this I'd get the above-mentioned error because my Java version is outdated and I don't have the up-to-date root certificates in my cacerts file.


To fix this issue, we can do three things.

The Fix


1) Ignore certificate validation (insecure and NOT recommended)

This method is insecure and therefore not recommended. If you really don't care about the security of your website, then you can just do this, but not recommended.

If you are using the Apache httpclient library, you can ignore HTTPS issues like below.


        CloseableHttpClient httpClient = HttpClients.custom()
                    .setSSLContext(new SSLContextBuilder()
                            .loadTrustMaterial(null, TrustAllStrategy.INSTANCE)
                            .build())
                    .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                    .build();

It says the httpCilent to trust all connections and do not verify the hostname with certificates.


In older Java versions, SSL validation could be turned off just by a JVM option such as;
 -Dcom.sun.net.ssl.checkRevocation=false
However, it might work for certificate revocations or expirations, but not with new CAs or hostname verification issues.

However, you shouldn't do this at all. If you do this, there's a window for a malicious party to deceive your Java app by pretending to be the website that you are calling to. Because with this, you are giving up the trust of the HTTPS call.

Depending on the library, you are using, the code fix may differ. I'm not worried much about writing the other workaround code fixes because this is not the best way to fix.


2) Install website's certificate.

You can extract the website's certificate as mentioned here. https://tjisblogging.blogspot.com/2020/06/how-to-extract-ssl-certificate-from.html

Then locate your cacerts file. Usually, it is located in $JAVA_HOME/jre/lib/security/cacerts

($JAVA_HOME means the directory where the Java is installed.)

And execute the below keytool command. (Keytool is a tool that comes with Java to handle certificate related stuff).

keytool -import -trustcacerts -alias <domain name> -file <public certificate>.cert -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

eg:

keytool -import -trustcacerts -alias letsencrypt.org -file /tmp/letsencryptorg_b64_encoded.cer -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

Note: After -storepass You have to give the password of the keystore. Default password is "changeit".

Then, it will print the details of that certificate and ask you whether to trust it.



Type in "yes" and hit Enter.

Then, it'll print "Certificate was added to keystore"

After restarting your Java app, it should work.

3) Install the Root certificate


This is good if you are dealing with multiple calls to different websites/APIs similarly. In that case, you will require to install multiple certificates like this if it is expired.

Instead of that, you can just import the root certificate as a new CA root.

It is pretty much like the above but when extracting the certificate from the browser, you have to extract the root certificate in the chain (or the middle depending on the situation).



Other steps are pretty much the same as the above option 2.

Remember to restart your Java app, since it loads the certificates during the startup.










Comments

Popular posts from this blog

Install Docker on Windows 11 with WSL Ubuntu 22.04

This is to install Docker within Ubuntu WSL without using the Windows Docker application. Follow the below steps. Install Ubuntu 22.04 WSL 1. Enable Windows Subsystem for Linux and Virtual Machine platform Go to Control Panel -> Programs -> Programs and Features -> Turn Windows features on or off 2. Switch to WSL 2 Open Powershell and type in the below command. wsl --set-default-version 2 If you don't have WSL 2, download the latest WSL 2 package and install it.  3. Install Ubuntu Open Microsoft Store and search for Ubuntu. Select the version you intend to install. I'd use the latest LTS version Ubuntu 22.04. Click on the Get button. It will take a couple of minutes to download and install. 4. Open up the installed Ubuntu version that was installed. If you get an error like the below image, make sure to install the WSL2 Kernel update .  If it's an older Ubuntu version the error message would be something like the image below. Error: WSL 2 requires an update to its

Wget download pause and continue

If you are downloading a file with the wget command, sometimes you may need to pause it and start it back from the place where you paused rather than starting from the beginning. So you don't have to re-download the entire package. Wget can do this just like downloading from a web browser. Let's say I need to download a file from the web. So I'm using the wget command as follows. Download wget <url for the file> Pause To pause the download just hit ctrl + c  the shortcut to terminate the current command. Continue This is going to be the same command but -c switch to continue from the previous download. wget -c <url for the file> Simple as that! Yeah, you can download from a web browser, but this is more fun and easier 😋 If you are lazy like me, wget saves a couple of clicks.