Java Certificate Client SSL: unable to find valid certification path to requested target

12,181

Solution 1

  1. Use openssl to generate your P12 file

    openssl pkcs12 -export -in /Users/me/test.authclient.int.com.crt -inkey /Users/me/test.authclient.int.com.key -out authClient.p12 -name authClientCert

  2. Generate the trust store key

    keytool -genkey -dname "cn=CLIENT" -alias trustStoreKey -keyalg RSA -keystore authClient-truststore.jks -keypass mypassword -storepass mypassword

  3. Now, import the trust store key

    keytool -import -keystore authClient-truststore.jks -file /Users/me/test.authclient.int.com/test.authclient.int.com.crt -alias.test.authclient.int.com

  4. Get the remote cert

    openssl x509 -in <(openssl s_client -connect the.ssl.api.i.want.to.call.com:443 -prexit 2>/dev/null) -out the.api.i.want.to.call.crt

  5. Add the server cert to the trust store

    keytool -importcert -file the.api.i.want.to.call.crt -alias the.api.i.want.to.call.com -keystore /Users/me/authClient-truststore.jks -storepass mypassword

Here's the client that I used to call the api that needed authentication.

    KeyStore clientStore = KeyStore.getInstance("PKCS12");
    clientStore.load(new FileInputStream("/Users/me/authClient.p12"), "mypassword".toCharArray());

    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(clientStore, "mypassword".toCharArray());
    KeyManager[] keyManagers = kmf.getKeyManagers();

    KeyStore trustStore = KeyStore.getInstance("JKS");
    trustStore.load(new FileInputStream("/Users/me/authClient-truststore.jks"), "mypassword".toCharArray());

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);
    TrustManager[] tms = tmf.getTrustManagers();

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, tms, new SecureRandom());

    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
    CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

    HttpGet httpget = new HttpGet(requestUrl);

    httpclient.execute(httpget);

That't it. Let me know if I can help by expanding, but this should be all you need.

Solution 2

It has nothing to you with your client certificate. Your truststore doesn't trust the server certificate.

Share:
12,181
Matt
Author by

Matt

Updated on June 04, 2022

Comments

  • Matt
    Matt almost 2 years

    We require client authentication to send a RESTful request to some of our web services. I've installed a client cert (.pem) and key on the my local mac os via the key tool. These are not self signed.

    openssl pkcs12 -export -name myservercert -in not_self_signed.crt -inkey server.key -out keystore.p12
    

    ...and converted to JKS format

    keytool -importkeystore -destkeystore mykeystore.jks -srckeystore keystore.p12 -srcstoretype pkcs12 -alias myservercert
    

    I'm trying to build a Java client to do the authentication. Here is what I've come up with so far:

    import java.io.FileInputStream;
    import java.security.KeyStore;
    
    import javax.net.ssl.SSLContext;
    
    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.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.ssl.SSLContexts;
    import org.apache.http.util.EntityUtils;
    
    
    public class TestClientCustomSSL {
    
        public final static void main(String[] args) throws Exception {
    
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream("/Users/me/mykeystore.jks"), "mypassword".toCharArray());
    
            SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, "mypassword".toCharArray()).build();
    
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslContext,
                    new String[] {"TLSv1"},
                    null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    
            try {
    
                HttpGet httpget = new HttpGet("https://restful-service-i-am-calling/v1/endpoint/data?ip=0.0.0.1");
    
                System.out.println("Executing request " + httpget.getRequestLine());
    
                CloseableHttpResponse response = httpclient.execute(httpget);
                try {
                    HttpEntity entity = response.getEntity();
    
                    System.out.println("----------------------------------------");
                    System.out.println(response.getStatusLine());
                    EntityUtils.consume(entity);
                } finally {
                    response.close();
                }
            } finally {
                httpclient.close();
            }
        }
    
    }
    

    Below is the stacktrace that I receive. But based on what I've read here my class should be able to send the request just fine.

    Exception in thread "main" 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 sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1439)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:878)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:814)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
        at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
        at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
        at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
        at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
        at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
        at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
        at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
        at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
        at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
        at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
        at com.mycompany.main(ClientCustomSSL.java:101)
    Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
        at sun.security.validator.Validator.validate(Validator.java:260)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1421)
        ... 20 more
    Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
        ... 26 more
    

    Any pointers are appreciated.

    EDIT:::

    FYI I am able to get a 200 response from the server using the same pem and key that I added to the trust store using wget.

    wget --certificate ~/Desktop/my.cert.pem --private-key ~/Desktop/my.key.key https://mycompany.com/v1/939044?data=0.0.0.1
    

    EDIT 2:::*

    Based on @EJP answer below, also added the cert from the server site:

    openssl x509 -in <(openssl s_client -connect the.api.i.am.calling.com:443 -prexit 2>/dev/null) -out ~/Desktop/the.api.i.am.calling.crt
    

    ...then I imported the cert to the same keystore:

    keytool -importcert -file ~/Desktop/the.api.i.am.calling.crt -alias the.api.i.am.calling.com -keystore /Users/me/mykeystore.jks -storepass mypassword
    

    Running the list command shows that both certs are in the keystore:

    keytool -list -keystore /Users/me/mykeystore.jks 
    Enter keystore password:  *********
    
    Keystore type: JKS
    Keystore provider: SUN
    
    Your keystore contains 2 entries
    
    my.auth.client.cert.com, Oct 17, 2015, PrivateKeyEntry, 
    Certificate fingerprint (SHA1): 3D:95:32:E5:F9:9E:4A:53:84:EB:AB:1B:B9:A2:4C:A5:1B:5E:DA:76
    the.api.i.am.calling.com, Oct 18, 2015, trustedCertEntry, 
    Certificate fingerprint (SHA1): 7C:4A:7B:CE:9B:0B:92:C0:4F:C0:DA:84:CF:F2:24:CF:99:83:0B:3F
    

    But am still receiving the same error.

    EDIT 3:::

    One more thing I forgot to mention. The only thing I ever gave the server-side team was our client cert names... Ie something like dev.auth.client.com. Do I really have to get the server-side cert to store in the keystore?

  • Matt
    Matt over 8 years
    Thanks EJP. If you wouldn't mind providing some details on how to get it to trust the server cert that would be helpful. I'm running this locally.
  • user207421
    user207421 over 8 years
    The real question is why isn't the server certificate signed? If they're all your certificates, export the server certificate from the server KeyStore and import it as a trusted cert into your client truststore, with the keytool.
  • Matt
    Matt over 8 years
    If this were the case, would I be able to send this? Which works fine from my local. wget --certificate ~/Desktop/my.cert.pem --private-key ~/Desktop/my.key.key mycompany.com/v1/939044?data=0.0.0.1
  • Matt
    Matt over 8 years
    I've added the server cert to the keystore. Edit 2 shows this update... Still the same result. Any additional pointers are appreciated.
  • user207421
    user207421 over 8 years
    I can't make head or tail of this. Step 1 doesn't use the keytool. Step 2 generates a key pair, which goes in a keystore. Step 3 imports a certificate, not a key. You have the client KeyStore and truststore all mixed up. Steps 4-5 are exactly what you claimed didn't work.
  • Matt
    Matt over 8 years
    I'm sure others will find it useful
  • treize
    treize over 7 years
    @MattB, thanks for the solution. This is the only one that works for a authClient authentication.
  • caraca
    caraca almost 3 years
    @MattB, in my case I'm connecting with a Kafka server. I was missing steps 4 and 5, just with that I could successfully connect. Thanks!