As a java developer, if you have not been stung by the below-mentioned exception while running a Java application developed by you on your machine that hits an SSL server (https), then be prepared to get a nasty experience at some point of your coding journey.
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
To give a context to the above-mentioned issue, I would highlight few situations where you might build a java application apart from Android, Tomcat Server, and JavaFX.
- When building a bot for a product/company.
- An android app that requires data to be fetched from the server and put into the assets folder for each build.
- Building some file parser for your need.
- Doing ComputerVision projects.
- Doing Machine learning projects.
- Creating a Java library.
If a java application tries to connect to a server secured by the SSL (https sites) then you might get the above-mentioned exception.
A server which uses SSL for providing identity and encryption sends a certificate which is verified by a trusted third party like Verisign, GoDaddy, and few others. Through this certificate, a browser or a java clients knows that they are talking to the correct site (who it claims to be) and not on redirected proxy site. This step is pretty transparent if we access a site using the browser because if SSL certificate is not in the browser’s trusted store then it will ask us to add that certificate and it will be subsequently added. But when we access a secure site using Java program, this step of SSL handshake is not transparent to a user. The certificates are verified from JRE’s trustStore. This trustStore is located on JDK Installation directory referred by JAVA_HOME ($JAVA_HOME/jre/lib/security) and commonly named as “cacerts”.
If the certificate provided by a secure site is present on JRE’s trustStore, then SSL connection would be established but if the certificate is not there then Java will throw an exception(mentioned above) and to solve that we will need to add that certificate into trustStore.
A good overview of SSL is provided on this blog: https://www.digicert.com/ssl.htm
This problem is more prevalent for those servers which use “Let’s Encrypt” SSL certificate. But since we are the open source lovers, we will use it and also because it’s free.
In order to witness this issue, I have created a GitHub java project(link provided below). Clone and run it. You will get the exception at runtime.
This update-https-demo repo is build upon the structure of java project generated by gradle.
gradle init --type java-library
I will write another post that will explain gradle based Java project development environment setup and structure, which will be different from Android setup.
This application tries to make API call to https://httpbin.org/get URL(httpbin is an open source project) using
OkHttp and then parses the response JSON into Java POJO model class objects using
Gson. I have written a
JsonParser class to make the parsing easy and generic.
Now, that we know the situation in which this issue can appear in a java program, let’s understand it’s solution.
As mentioned above, we will have to add the SSL certificate for that site into the JRE’s trustStore.
Follow the below-mentioned steps to add the SSL certificate into the JRE’s trustStore.
- The first step is to download the SSL certificate for the site. To do so open the terminal and point to the directory where the certificate will be saved. I have saved the certificate at Desktop directory.
- Hit the site and save the certificate using openssl command.
openssl s_client -connect <site-url>:443 -servername <site-url> > <saved-cetificate-file-name-we-want-to-give>
In our case: openssl s_client -connect httpbin.org:443 -servername httpbin.org > https_bin_ssl_cert.pem
Hit ctrl + z after the command to exit.
verify that https_bin_ssl_cert.pem file has been saved in the Desktop directory.
Open the file in any text editor (like sublime) and view the contents. It will have the issuing authority name, keys, encryption algorithm, and other information.
- Open $JAVA_HOME/jre/lib/security, here certificates are stored(provided you have java installed and java home path setup).
- Now we put the certificate in the keystore i.e. the cacerts. To do so run the below command.
sudo keytool -import -keystore cacerts -alias <alias-name> -file <certificate-file-path>
In our case: sudo keytool -import -keystore cacerts -alias httpbin -file /Users/janisharali/Desktop/https_bin_ssl_cert.pem
It will prompt you to enter the password, first for the sudo and then for the keystore.
The default password for the keystore is “changeit”
Note: If asked for the new password after entering the “changeit”, either use the same “changeit” or change the password and remember it for future.
- After this command, it will verify the certificate and then ask for your confirmation. When it asks, “trust this certificate” then enter “y”. It will finally print out “Certificate was added to keystore”.
Now, you will be able to run the java application and get the response from the server.
Coder’s Rock 🙂