'SSLHandshakeException when calling a secure REST service
I was supplied a JKS file (keystore.jks) to make a secure RESTful call from my Java REST Client, Here is what I did.
1. Get alias from JKS file
keytool -list -v -keystore keystore.jks
2. Export Certificate from JKS
keytool -export -alias aliasName -file certName.cer -keystore keystore.jks
3. Import Certificate to JRE trustore
keytool -keystore cacerts -importcert -noprompt -trustcacerts -alias aliasName -file certName.cer
4. Verify if the certificate is added to truststore
keytool -list -v -keystore cacerts
JAVA CLIENT
package hello;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class HttpClientSSLTest {
public static void main(String[] args) throws Exception {
String trustStore = System.getProperty("javax.net.ssl.trustStore");
if (trustStore == null) {
System.out.println("javax.net.ssl.trustStore is not defined");
} else {
System.out.println("javax.net.ssl.trustStore = " + trustStore);
}
String keyStore = System.getProperty("javax.net.ssl.keyStore");
if (keyStore == null) {
System.out.println("javax.net.ssl.keyStore is not defined");
} else {
System.out.println("javax.net.ssl.keyStore = " + keyStore);
}
// Trust all certs
SSLContext sslcontext = buildSSLContext();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[] { "TLSv1" }, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf).build();
try {
HttpGet httpget = new HttpGet(
"https://devmachine12:12212/tools/reference-id/xyz"
);
httpget.addHeader("ApiKey", "XYZ");
httpget.addHeader("UserId:", "XYZ");
httpget.addHeader("Content-Type", "application/json;v=3");
httpget.addHeader("Accept", "application/json;v=3");
System.out.println("executing request " + httpget.getRequestLine());
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println("Response content length: "
+ entity.getContentLength());
}
for (Header header : response.getAllHeaders()) {
System.out.println(header);
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
private static SSLContext buildSSLContext()
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException {
SSLContext sslcontext = SSLContexts.custom()
.setSecureRandom(new SecureRandom())
.loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
return true;
}
}).build();
return sslcontext;
}
}
I am still getting this exception..
javax.net.ssl.trustStore = /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/security/cacerts
javax.net.ssl.keyStore = /Users/Path/To/keystore.jks
trustStore is: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/security/cacerts
trustStore type is : jks
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
17:00:25.312 [main] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: Shutdown connection
17:00:25.312 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Connection discarded
17:00:25.312 [main] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
17:00:25.313 [main] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://devmachine12:12212][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
17:00:25.313 [main] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection manager is shutting down
17:00:25.313 [main] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection manager shut down
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2011)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1113)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1363)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1391)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1375)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:290)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:259)
at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:125)
NOTE: Using the same Keystore.jks file in SOAP-UI I can make the REST call successfully.
Update 1: I also tried custom truststore
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
//FileInputStream instream = new FileInputStream(new File("/Users/xyz/Documents/keystore.jks"));
FileInputStream instream = new FileInputStream(new File("/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/security/cacerts"));
try {
trustStore.load(instream, "password".toCharArray());
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore,new TrustSelfSignedStrategy()).build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[] { "TLSv1" }, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf).build();
Getting this error..
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
-Djavax.net.debug=all
javax.net.ssl.trustStore = /Path/To/keystore.jks
javax.net.ssl.keyStore = /Library/Java/Home/lib/security/cacerts
trustStore is: /Library/Java/Home/lib/security/cacerts
trustStore type is : jks
trustStore provider is :
init truststore
adding as trusted cert:
Subject: CN=SecureTrust CA, O=SecureTrust Corporation, C=US
Issuer: CN=SecureTrust CA, O=SecureTrust Corporation, C=US
Algorithm: RSA; Serial number: 0xcf08e5c0444a5xxxv7ff0eb271859d0
Valid from Tue Nov 07 14:31:18 EST 2006 until Mon Dec 31 14:40:55 EST 2029
adding as trusted cert:
Subject: CN=DigiCert Global Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US
Issuer: CN=DigiCert Global Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US
Algorithm: RSA; Serial number: 0x83be056904df661a1743ac95991c74a
Valid from Thu Nov 09 19:00:00 EST 2006 until Sun Nov 09 19:00:00 EST 2031
....
*** **ClientHello, TLSv1**
RandomCookie: GMT: 1420323139 bytes = { 245, 155, 164, 46, 144, 29, 159, 19, 144, 152, 111, 67, 67, 81, 155, 132, 11, 444, 43, 777, 64, 110, 38, 59, 105, 57, 218, 148 }
Session ID: {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WI
*** ServerHello, TLSv1
RandomCookie: GMT: 1420323139 bytes = { 169, 124, 555, 87, 44, 71, 222, 62, 1, 171, 150, 217, 12, 44, 50, 35, 77, 76, 33, 219, 123, 191, 87, 188, 888, 99, 115, 158 }
Session ID: {133, 155, 225, 44, 44, 111, 105, 25, 229, 223, 99, 7, 12, 66, 184, 227}
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA
Compression Method: 0
***
Warning: No renegotiation indication extension in ServerHello
%% Initialized: [Session-1, TLS_RSA_WITH_AES_128_CBC_SHA]
main, READ: TLSv1 Handshake, length = 1664
***
**Certificate chain**
chain [0] = [
[
Version: V3
Subject: CN=xxx-qa.xxx.xxx.com, OU=Web Servers, O=xxx, C=US
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 13102849046627232962710284400322858861706811412350472430303415237614110859833765308993228833198516749796429689145995898905457746791810550642537672313
public exponent: 65537
Validity: [From: Fri Jun 18 10:56:04 EDT 2010,
To: Sun Aug 11 12:50:23 EDT 2019]
Issuer: O=xxx, C=US
SerialNumber: [ 494dadbd]
chain [1] = [
[
Version: V3
Subject: O=xxx, C=US
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 15728074885629223656589726231564957982308943232383883470077767024196824781883586292405962205030193006305258215264230938869191345249508973458673148381
public exponent: 3
Validity: [From: Wed Aug 11 12:20:23 EDT 1999,
To: Sun Aug 11 12:50:23 EDT 2019]
Issuer: O=xxx, C=US
SerialNumber: [ 37b1a9ce]
...
0000: 0E 00 00 00 ....
main, READ: TLSv1 Handshake, length = 4
*** ServerHelloDone
Solution 1:[1]
SunCertPathBuilderException: unable to find valid certification path to requested target
Your truststore doesn't trust the server certificate. If it's self-signed, import it; if you already did that, you did it wrong.
If it isn't self-signed, don't import it, and don't use a custom truststore at all. Use the one that comes with the JRE.
And don't use code that trusts all certificates, or tries to. It isn't secure. It is not a solution to this problem. The cure is worse than the disease.
Solution 2:[2]
Approaching the scenario where you are installing a new Java SDK:
You must remember that the keytool is available as part of the JDKs: Inside the old one you've been running and inside the current one you just installed - or is about to install. One must be careful with wich one is running in order to import stuff to the new one.
The keytool running is the one inside the installed Java SDK. You can determine which one is running by typing the following command
java -version
Whose output is something like the following:
openjdk version "11.0.14.1" 2022-02-08
OpenJDK Runtime Environment (build 11.0.14.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.14.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)
Not what you expect? Try the following:
echo $JAVA_HOME
It's expected that a path to the SDK home folder (of the SDK you want to use) is displayed as output, just like
/usr/lib/jvm/java-11-openjdk-amd64
If you are running on Linux environment, it's very convenient to run the update-alternatives command in order to check for the Java SDKs you have installed, which one is currently being used, and, eventually, pick up the desired one from the list.
When importing certificates to a newer Java SDK, you want the new version to be installed and running by the moment you execute the line below:
keytool -import -trustcacerts -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -noprompt -alias alias-of-the-entry-I-want-to-import -file ~/path_to/the/file/where-the/entry_is.crt
In order to better understand the command above, take a look at the keytool documentation.
Still desperate and just wanna take a look inside the keystore, so you can check if your entry there? Try the following:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | grep "your-entry-alias"
If it was imported, you will grep the corresponding line, if not, no line will show up. Run it again without the grep part in order to print out the entire content:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
Hope you all succeed.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | user207421 |
| Solution 2 | Farlon Souto |
