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
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).
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
Post a Comment