1

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
Himalay Majumdar
  • 3,883
  • 14
  • 65
  • 94
  • 1
    As you've copied the certificate to the default truststore, you should not need to define a SSLContext as HttpClient and Java should fallback to the default truststore. Have you tried omitting the SSL configuration? Also, have you already tried to define a custom truststore and see if you still get the SSLHandshakeException? I've created a [sample server and client application](http://stackoverflow.com/questions/19679320/basic-authentication-using-http-commons-client/19679722#19679722) which uses custom keystore/truststore for rapid testing of correct handshaking – Roman Vottner Jul 14 '15 at 22:33
  • @RomanVottner I am using SSLContext to force it to use TLSv1 otherwise I get a different error. I tried your suggestion of using a custom truststore and getting another error. Please see my Update above. – Himalay Majumdar Jul 15 '15 at 00:41
  • is the certificate you got a trusted one (trusted by a public authority)? You can also enable debugging while handshaking via `-Djavax.net.debug=all` or [`-Djavax.net.debug=ssl`](http://stackoverflow.com/questions/9210514/unable-to-find-valid-certification-path-to-requested-target-error-even-after-c). You can also try to [check if a certificate is trusted](http://stackoverflow.com/questions/6629473/validate-x-509-certificate-agains-concrete-ca-java) or [something like this](http://jensign.com/JavaScience/IsCertTrusted/index.html) but I guess they should be valid else SoapUI should already fail – Roman Vottner Jul 15 '15 at 01:26
  • 1
    Do you have untrusted certificates in your default truststore? Or why do you define a trust-all certificates instruction in both of your attempts? Further, I'm not sure if the keystore should be used as truststore (as in your second attempt). As the cert is obviously trusted, could you try [one of these unaccepted answers](http://stackoverflow.com/questions/28391798/how-to-set-tls-version-on-apache-httpclient) – Roman Vottner Jul 15 '15 at 01:48
  • Don't use the same file as both KeyStore and truststore. They serve quite different purposes and should be subject to quite different access regimes. – user207421 Jul 15 '15 at 02:11
  • @RomanVottner Thank you for giving me different ideas, I tried those two but the error persists :( – Himalay Majumdar Jul 15 '15 at 05:01
  • @EJP From your suggestion, I also tried my local truststore instead of using the same jks file, but ended up with same result. I must also let you guys know that I am on a Mac which is facing certain firewall issues, as mentioned http://stackoverflow.com/questions/31408529/mac-access-issues-over-vpn. Hope this is not linked. – Himalay Majumdar Jul 15 '15 at 05:05
  • What does 'local truststore' mean? And is your server certificate CA-signed or not? If it is, you're doing the right things, although not necessarily the right way. If it isn't, you don't need to do any of them. You don't need the insecure TrustManager code in either case. You don't have a firewall issue, otherwise there wouldn't be a connection for the peer to close. Maybe you're using the installed JRE and not the JDK's JRE? – user207421 Jul 15 '15 at 05:11
  • @EJP By local truststore I meant 'lib/security/cacerts' in my jdk and verfired that I am using JDK's JRE. I think my Keystore.jks is a Self Signed certificate, and thats why I exported out the Keystore.cert file from the Keystore.jks and imported into 'lib/security/cacerts' in my jdk. – Himalay Majumdar Jul 15 '15 at 05:34

2 Answers2

0
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.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Could you please tell me if you see any error in me importing the certificate to my Java cacerts. I have done it multiple times already and in different versions of my JDKs JRE, following the 4 steps that I listed in my question. I still keep getting SSLHandshakeException. – Himalay Majumdar Jul 15 '15 at 07:40
  • I have added -Djavax.net.debug=all logs, could you please tell me if all certificates in "Certificate chain" after "ServerHello, TLSv1" should be present in my client truststore? I was given a Keystore.jks file by the server which had couple of certificates (which I stored them in client truststore) but did not contain just one certificate in the certificate chain which follows after "ServerHello, TLSv1" could that be the reason I am getting SSLHandshakeException? – Himalay Majumdar Jul 17 '15 at 03:00
  • Interestingly the Keystore.jks file works when I use it from SoapUI, could be the server issuing different certificates for SoapUI calls versus regular java client rest calls? – Himalay Majumdar Jul 17 '15 at 03:02
  • **One** of the certificates in the chain presented by the server must be in the truststore, preferably the top one. If there is more than one element in the chain, it isn't a self-signed certificate, and you shouldn't have to import anything into any truststore. Do it again with a clean cacerts, and post the *entire* server certificate chain here, and tells us whether any of those certificates appeared to be present in the clean cacerts. In fact if there is such a one, list it wih the keytool (-v) and post that htoo. I'm not clear what `keystore.jks` actually is here and where it came from. – user207421 Jul 17 '15 at 03:33
  • 1. `keystore.jks` is the file which I got from the server side folks manually thru email, I exported certificates from this file (as mentioned in the beginning of my question) and placed it in cacerts. 2. Following your suggestion I deleted the certificates (which I earlier exported from jks and imported into cacerts) and made it clean, my server certificate chain remains the same and now I do not see either of the certificates in cacerts. I am searching my certificate SerialNumber in cacerts. – Himalay Majumdar Jul 17 '15 at 05:03
  • 1. If, as seems likely, the `keystore.jks` file contains the private key, they shouldn't have sent it to you in the first place. They have just compromised their private key and **mus**t start the whole process again with a new one, a new CSR, a new signing, etc. As it's a CA-signed certificate, they should have sent you what the CA sent them, not a keystore. The entire process here is deeply suspect and should be investigated by someone competent. – user207421 Jul 17 '15 at 22:20
  • 2. I didn't say anything about deleting certificates. You need to get a clean copy of cacerts from another JRE installation. Deleting things that possibly should be there will only make it all worse. 3. I don't know what your final sentence means. – user207421 Jul 17 '15 at 22:22
  • 1. I agree, its just that pointing my SoapUI pro tool to the keystore.jks file I am able to make the secure REST calls, but not using Java way. 2. Apart from deleting certs, I also got a fresh java installation and tried with new cacerts. 3. If you notice the ** Certificate chain ** in my debug log, you will notice that each certificate has a "SerialNumber" number attached, thats how I am searching if the cert is present in my cacerts or not. – Himalay Majumdar Jul 18 '15 at 21:47
  • Apparently someone else from my company is able to call the same secured REST service following the exact process like me using the same jks file. I will let you know next week. – Himalay Majumdar Jul 18 '15 at 22:44
0

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.

Farlon Souto
  • 116
  • 1
  • 6