PROBLEM
When invoking a HTTPS URL from Java, for example…
final RestTemplate restTemplate = new RestTemplate();
restTemplate.execute("https://www.google.com/some-api", HttpMethod.GET, null, ...);
…the following exception is thrown…
org.springframework.web.client.ResourceAccessException: I/O error on GET
request for "https://www.google.com":sun.security.validator.ValidatorException:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target; nested exception
is 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 org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:607)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)
SOLUTION 1: Disable SSL Validation – NOT RECOMMENDED
One way is to simply disable the SSL validation by configuring SSLContext to trust all X.509 certificates before invoking the intended HTTPS URL.
@Configuration
class MyAppConfig {
@Bean
public Boolean disableSSLValidation() throws Exception {
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}}, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return true;
}
}
Unless you are writing test cases or implementing non-production code, this approach is highly discouraged because it doesn’t prevent the man-in-the-middle attacks.
SOLUTION 2: Import Certificate into Java Keystore – RECOMMENDED
The reason we see this exception is because the certificate used by the HTTPS URL doesn’t exist in the Java Keystore. Hence, the SSL validation fails.
To fix this, we can download the website’s certificate using a browser to be added into Java Keystore.
Access the website (in this example, I use https://www.google.com) and click on the pad lock.
Find the button that allows you to view and export the certificate as a PEM file. In this example, I named that file google.cer.
The content of the downloaded PEM file should look like this:-
-----BEGIN CERTIFICATE-----
MIIEgDCCA2igAwIBAgIITq9JKLrGf5EwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
eoV23mHmV6/0mOwocVYt/Th96WNGGmhANkFW//HCphRWnhaOqIG6yFRQ/jxArTvZ
QJEGI5AiYHzQn7LdUM8mH1o3ifR+lX+QiAwyeU9oegdlRslI2KMoPOuOFj329NFx
Bw+XVQXMsRJITPg8pnegPmLCOjpz8y7pBxbxGnfaI66I8X4dArsaXX4r5mkfhk2e
mm7fxQ8qUaW9mKoW0XvwGxU0AwKI8OopuXHoD97vr2GSK0QNZ19A96mtTWnQ2cu2
i9qjGw==
-----END CERTIFICATE-----
Go to the Terminal.
If you have multiple versions of java, make sure you choose the correct version first:-
export JAVA_HOME="`/usr/libexec/java_home -v '1.7*'`"
Import the certificate into Java Keystore:-
sudo keytool -import -trustcacerts -file /path/to/google.cer -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
Now, you should be able to invoke the HTTPS URL without any exception.
If for some reason you need to remove the imported certificate from Java Keystore, you can refer to that certificate using the alias and run this command:-
sudo keytool -delete -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts
Leave a Reply