I'm getting the following error
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
when connecting to google maps geocoding API. I am able to reproduce the error in a simple Main program. Here's how to reproduce it with this test program:
import javax.net.ssl.*;
import java.net.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
String httpsURL = "https://maps.googleapis.com/maps/api/geocode/json?address=49+874%2Cla+plata%2Cbuenos+aires%2Cargentina&sensor=false&key=AIzaSyAJ1QS0C6KjiWajwxx4jUb_Jz0b8lBZyyE";
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection) myurl.openConnection();
InputStream ins = con.getInputStream();
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader in = new BufferedReader(isr);
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
}
}
Saved as Main.java Compile it
javac Main.java
Run it
java Main
I get the normal result (json response is printed).
But if I create a TrustStore with a certificate from here: https://www.clic.gob.ar/ I downloaded the SSL certificate and saved it as an X.509 PEM file named clic.gob.ar
Create a new TrustStore named keystorefede.jks
keytool -import -file clic.gob.ar -alias clicCert -keystore keystorefede.jks
I gave it password tompass. I can list it
keytool -list -keystore keystorefede.jks -storepass tompass
Tipo de Almacén de Claves: JKS Proveedor de Almacén de Claves: SUN
Su almacén de claves contiene 1 entrada
cliccert, 01/08/2014, trustedCertEntry, Huella Digital de Certificado (SHA1): 15:3B:67:EE:51:C9:F2:CF:68:7C:24:51:A4:B6:6E:AE:EA:61:D5:B5
Now run the same program using the trustStore
java -Djavax.net.ssl.trustStore=/home/fede/keystorefede.jks -Djavax.net.ssl.trustStorePassword=tompass Main
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This happens with Java 8 and also Java 7.
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)
The problem was first discovered inside a web application running inside Tomcat. The certificate has to be in a TrustStore in Tomcat's command line for another request; the trust store has nothing google related, just one certificate. If I add Google's certificate to the trust store then the problem is solved, but this is not a proper solution. Google does not accept geocoding requests over http using an API key. It the -Djavax.net.ssl.trustStore overriding all the root CA's Java knows?
The application doesn't find the right truststore. You can define it that way:
System.setProperty("javax.net.ssl.trustStore", ".\\src\\truststore.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "psw123");
The javax.net.ssl.trustStore property, indeed, overrides all known certificates of the JVM with the one you provide.
By default, the JVM comes with a trustStore prepopulated with a fairly decent number of well known authorities (the Oracle JVM stores it in JAVA_HOME/jre/lib/security/cacerts).
This keyStore will be used as the default by the JSSE (Java Secure Socket Extension) by default to validate SSL handshakes.
The javax.net.ssl.trustStore environnement variable overrides this default location, meaning none of its content's are relevant any more.
Going forward, you have a few solutions:
One is : you build your own JKS containing everything you need.
Second is : you add certificates to your JVM's default file.
Third is : you code.
Getting your own SSL Context "by hand" ?
Sockets that underly HTTPURLConnection are made out of
SocketFactory
instances. When HTTPS is involved, what happens is that you need to initialize your own SSLSocketFactory with wathever certificate/private keys are needed for your call, and associate the SocketFactory with the HTTPURLConnection before connecting it : see http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html#setSSLSocketFactory%28javax.net.ssl.SSLSocketFactory%29This works like this. First, you need to load your KeyStore (JKS file containing your certificate, exception handling cut for shortening) :
Once you have a KeyStore instance, you can build a "TrustManager" that will use any certificates declared as trusted in the Keystore as valid trust anchors.
You can do the same for your SSL KeyManagerFactory (if you use 2 way SSL), the pattern is exactly the same. Once you have TrustManagerFactory and KeyManagerFactory instances, you are ready to build a SSLSocketFactory.
At this point, you can do