java - Resolving javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Error?


Keywords:java 


Question: 

Edit :- Tried to format the question and accepted answer in more presentable way at mine Blog

Here is the original issue:-

I am getting this error

detailed message sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

cause 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

i am using tomcat 6 as webserver. i have two https webbapplication installed on different tomcat on differte port but on same machine. Say App1(port 8443) and App2(port 443). App1 connects to App2 .When App1 connects to App2 i get above error. I know this is very common error so came across many solutions on different forums and sites. I have below entry in server.xml of both tomcat i.e

keystoreFile="c:/.keystore" 
keystorePass="changeit"

Every site says the same reason that certificate given by app2 is not in the trusted store of app1 jvm. This seems to be true also when i tired to hit the same URL in IE browser, it works(with warming, There is a problem with this web site's security certificate. here i say continue to this website) But when same url is hit by java client(in my case). So i get the above error. So to put it in trustore i tried these tree options i.e

Option1

System.setProperty("javax.net.ssl.trustStore", "C:/.keystore");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

Option2 Setting below in environment variable

CATALINA_OPTS -- param name
-Djavax.net.ssl.trustStore=C:\.keystore -Djavax.net.ssl.trustStorePassword=changeit ---param value

Option3 Setting below in environment variable

JAVA_OPTS -- param name
-Djavax.net.ssl.trustStore=C:\.keystore -Djavax.net.ssl.trustStorePassword=changeit ---param value

But nothing worked .

What at last worked is executing the java approach suggested in How to handle invalid SSL certificates with Apache HttpClient? by Pascal Thivent i.e executing the program InstallCert.

But this approach is fine for devbox setup but i can not use it at production environment.

I am wondering why three approaches mentioned above did not work when i have mentioned same values in server.xml of app2 server and same values in truststore by setting

System.setProperty("javax.net.ssl.trustStore", "C:/.keystore") and System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

in app1 program.

For more information this is how i am making the connection

URL url = new URL(urlStr);

URLConnection conn = url.openConnection();

if (conn instanceof HttpsURLConnection) {

  HttpsURLConnection conn1 = (HttpsURLConnection) url.openConnection();

  conn1.setHostnameVerifier(new HostnameVerifier() {
    public boolean verify(String hostname, SSLSession session) {
      return true;
    }
  });

  reply.load(conn1.getInputStream());

15 Answers: 

You need to add the certificate for App2 to the truststore file of the used JVM located at %JAVA_HOME%\lib\security\cacerts.

First you can check if your certificate is already in the truststore by running the following command: keytool -list -keystore "%JAVA_HOME%/jre/lib/security/cacerts" (you don't need to provide a password)

If your certificate is missing, you can get it by downloading it with your browser and add it to the truststore with the following command:

keytool -import -noprompt -trustcacerts -alias <AliasName> -file <certificate> -keystore <KeystoreFile> -storepass <Password>

Afer import you can run the first command again to check if your certificate was added.

Sun/Oracle information can be found here.

 

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

• When I got the error, I tried to Google out the meaning of the expression and I found, this issue occurs when a server changes their HTTPS SSL certificate, and our older version of java doesn’t recognize the root certificate authority (CA).

• If you can access the HTTPS URL in your browser then it is possible to update Java to recognize the root CA.

• In your browser, go to the HTTPS URL that Java could not access. Click on the HTTPS certificate chain (there is lock icon in the Internet Explorer), click on the lock to view the certificate.

• Go to “Details” of the certificate and “Copy to file”. Copy it in Base64 (.cer) format. It will be saved on your Desktop.

• Install the certificate ignoring all the alerts.

• This is how I gathered the certificate information of the URL that I was trying to access.

Now I had to make my java version to know about the certificate so that further it doesn’t refuse to recognize the URL. In this respect I must mention that I googled out that root certificate information stays by default in JDK’s \jre\lib\security location, and the default password to access is: changeit.

To view the cacerts information the following are the procedures to follow:

• Click on Start Button-->Run

• Type cmd. The command prompt opens (you may need to open it as administrator).

• Go to your Java/jreX/bin directory

• Type the following

keytool -list -keystore D:\Java\jdk1.5.0_12\jre\lib\security\cacerts

It gives the list of the current certificates contained within the keystore. It looks something like this:

C:\Documents and Settings\NeelanjanaG>keytool -list -keystore D:\Java\jdk1.5.0_12\jre\lib\security\cacerts

Enter keystore password: changeit

Keystore type: jks

Keystore provider: SUN

Your keystore contains 44 entries

verisignclass3g2ca, Mar 26, 2004, trustedCertEntry,

Certificate fingerprint (MD5): A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9

entrustclientca, Jan 9, 2003, trustedCertEntry,

Certificate fingerprint (MD5): 0C:41:2F:13:5B:A0:54:F5:96:66:2D:7E:CD:0E:03:F4

thawtepersonalbasicca, Feb 13, 1999, trustedCertEntry,

Certificate fingerprint (MD5): E6:0B:D2:C9:CA:2D:88:DB:1A:71:0E:4B:78:EB:02:41

addtrustclass1ca, May 1, 2006, trustedCertEntry,

Certificate fingerprint (MD5): 1E:42:95:02:33:92:6B:B9:5F:C0:7F:DA:D6:B2:4B:FC

verisignclass2g3ca, Mar 26, 2004, trustedCertEntry,

Certificate fingerprint (MD5): F8:BE:C4:63:22:C9:A8:46:74:8B:B8:1D:1E:4A:2B:F6

• Now I had to include the previously installed certificate into the cacerts.

• For this the following is the procedure:

keytool –import –noprompt –trustcacerts –alias ALIASNAME -file FILENAME_OF_THE_INSTALLED_CERTIFICATE -keystore PATH_TO_CACERTS_FILE -storepass PASSWORD

If you are using Java 7:

keytool –importcert –trustcacerts –alias ALIASNAME -file PATH_TO_FILENAME_OF_THE_INSTALLED_CERTIFICATE -keystore PATH_TO_CACERTS_FILE -storepass changeit

• It will then add the certificate information into the cacert file.

It is the solution I found for the Exception mentioned above!!

 

How to work-it in Tomcat 7

I wanted to support a self signed certificate in a Tomcat App but the following snippet failed to work

import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class HTTPSPlayground {
    public static void main(String[] args) throws Exception {

        URL url = new URL("https:// ... .com");
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();

        httpURLConnection.setRequestMethod("POST");
        httpURLConnection.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
        httpURLConnection.setDoOutput(true);
        DataOutputStream wr = new DataOutputStream(httpURLConnection.getOutputStream());

        String serializedMessage = "{}";
        wr.writeBytes(serializedMessage);
        wr.flush();
        wr.close();

        int responseCode = httpURLConnection.getResponseCode();
        System.out.println(responseCode);
    }
}

this is what solved my issue:

1) Download the .crt file

echo -n | openssl s_client -connect <your domain>:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/<your domain>.crt
  • replace <your domain> with your domain (e.g. jossef.com)

2) Apply the .crt file in Java's cacerts certificate store

keytool -import -v -trustcacerts -alias <your domain> -file ~/<your domain>.crt -keystore <JAVA HOME>/jre/lib/security/cacerts -keypass changeit -storepass changeit
  • replace <your domain> with your domain (e.g. jossef.com)
  • replace <JAVA HOME> with your java home directory

3) Hack it

Even though iv'e installed my certificate in Java's default certificate stores, Tomcat ignores that (seems like it's not configured to use Java's default certificate stores).

To hack this, add the following somewhere in your code:

String certificatesTrustStorePath = "<JAVA HOME>/jre/lib/security/cacerts";
System.setProperty("javax.net.ssl.trustStore", certificatesTrustStorePath);

// ...
 

My cacerts file was totally empty. I solved this by copying the cacerts file off my windows machine (that's using Oracle Java 7) and scp'd it to my Linux box (OpenJDK).

cd %JAVA_HOME%/jre/lib/security/
scp cacerts mylinuxmachin:/tmp

and then on the linux machine

cp /tmp/cacerts /etc/ssl/certs/java/cacerts

It's worked great so far.

 

For me, this error appeared too while trying to connect to a process behind an NGINX reverse proxy which was handling the SSL.

It turned out the problem was a certificate without the entire certificate chain concatenated. When I added intermediate certs, the problem was solved.

Hope this helps.

 

Using Tomcat 7 under Linux, this did the trick.

String certificatesTrustStorePath = "/etc/alternatives/jre/lib/security/cacerts";
System.setProperty("javax.net.ssl.trustStore", certificatesTrustStorePath);
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

Under Linux, $JAVA_HOME is not always setup, but usually /etc/alternatives/jre points to $JAVA_HOME/jre

 

Another reason could be an outdated version of JDK. I was using jdk version 1.8.0_60, simply updating to the latest version solved the certificate issue.

 

Below code works for me :

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

public class TrustAnyTrustManager implements X509TrustManager {

public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
}

HttpsURLConnection conn = null;
            URL url = new URL(serviceUrl);
            conn = (HttpsURLConnection) url.openConnection();
             SSLContext sc = SSLContext.getInstance("SSL");  
             sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());  

             conn.setSSLSocketFactory(sc.getSocketFactory());
 

i wrote a small win32 (WinXP 32bit testet) stupid cmd (commandline) script which looks for all java versions in program files and adds a cert to them. The Password needs to be the default "changeit" or change it yourself in the script :-)

@echo off

for /F  %%d in ('dir /B %ProgramFiles%\java') do (
    %ProgramFiles%\Java\%%d\bin\keytool.exe -import -noprompt -trustcacerts -file some-exported-cert-saved-as.crt -keystore %ProgramFiles%\Java\%%d\lib\security\cacerts -storepass changeit
)

pause
 

For Tomcat running on Ubuntu server, to find out which Java is being used, use "ps -ef | grep tomcat" command:

Sample:

/home/mcp01$ **ps -ef |grep tomcat**
tomcat7  28477     1  0 10:59 ?        00:00:18 **/usr/local/java/jdk1.7.0_15/bin/java** -Djava.util.logging.config.file=/var/lib/tomcat7/conf/logging.properties -Djava.awt.headless=true -Xmx512m -XX:+UseConcMarkSweepGC -Djava.net.preferIPv4Stack=true -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/usr/share/tomcat7/endorsed -classpath /usr/share/tomcat7/bin/bootstrap.jar:/usr/share/tomcat7/bin/tomcat-juli.jar -Dcatalina.base=/var/lib/tomcat7 -Dcatalina.home=/usr/share/tomcat7 -Djava.io.tmpdir=/tmp/tomcat7-tomcat7-tmp org.apache.catalina.startup.Bootstrap start
1005     28567 28131  0 11:34 pts/1    00:00:00 grep --color=auto tomcat

Then, we can go in to: cd /usr/local/java/jdk1.7.0_15/jre/lib/security

Default cacerts file is located in here. Insert the untrusted certificate into it.

 

I was using jdk1.8.0_171 when I faced the same issue. I tried top 2 solutions here (adding a certificate using keytool and another solution which has a hack in it) but they didn't work for me.

I upgraded my JDK to 1.8.0_181 and it worked like a charm.

 

I have this problem too.

I tried almost everything by adding the SSL cert to .keystore, but, it was not working with Java1_6_x. For me it helped if we start using newer version of Java, Java1_8_x as JVM.

 

In my case the issue was that the webserver was only sending the certificate and the intermediate CA, not the root CA. Adding this JVM option solved the problem: -Dcom.sun.security.enableAIAcaIssuers=true

Support for the caIssuers access method of the Authority Information Access extension is available. It is disabled by default for compatibility and can be enabled by setting the system property com.sun.security.enableAIAcaIssuers to the value true.

If set to true, Sun's PKIX implementation of CertPathBuilder uses the information in a certificate's AIA extension (in addition to CertStores that are specified) to find the issuing CA certificate, provided it is a URI of type ldap, http, or ftp.

Source

 

Its a flaw of Java not using the standard Operating system keystore like in MacOS X. I filed a change request today see

 

for safety we should not use self signed certificates in our implementation. However, when it comes to development often we have to use trial environments which got self-signed certs. I tried to fix this issue programmatically in my code and I fail. However, by adding the cert to the jre trust-store fixed my issue. Please find below steps,

  1. Download the site cert,

  2. Copy the certificate(ex:cert_file.cer) into the directory $JAVA_HOME\Jre\Lib\Security

  3. Open CMD in Administrator and change the directory to $JAVA_HOME\Jre\Lib\Security

  4. Import the certificate to a trust store using below command,

keytool -import -alias ca -file cert_file.cer -keystore cacerts -storepass changeit

If you got a error saying keytool is not recognizable please refer this.

Type yes like below

Trust this certificate: [Yes]

  1. Now try to run your code or access the URL programmatically using java.

Hope this helps!