Search code examples
javassl-certificateapache-httpclient-4.xkeystore

PKIX path building failed ~ Dont want to add certificate to the jvm, want it to be read from code


I am getting the above error, saw a lot of solutions, have implemented the code and yet get the error.

Mar 07, 2018 10:17:59 PM <Class> performHttpRequest
SEVERE: IO error while performing HTTP POST to https://<url>/ with data <randomData>
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:1959)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:328)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:322)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1614)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1052)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:987)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
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:141)
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 <our code line>
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
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:397)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1596)
... 45 more
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:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
... 51 more

My code is as follows:

private String performHttpRequest(Configuration configuration, HttpEntityEnclosingRequestBase httpRequest, String body) {

    CloseableHttpClient httpClient = createHttpClient(configuration);
    HttpEntity responseEntity = null;

    try {
        HttpResponse response = httpClient.execute(httpRequest);
        responseEntity = response.getEntity();

        return EntityUtils.toString(responseEntity);
    } catch (Exception e) {
        String errorMessage = "IO error while performing HTTP " + httpRequest.getMethod() + " to " + httpRequest.getURI() + " with data " + body;
        errorMessage = stringUtil.removeConfidentialData(errorMessage);
        log.error(errorMessage, e);
        throw new RuntimeException(errorMessage, e);
    } finally {
        if (responseEntity != null) {
            try {
                EntityUtils.consume(responseEntity);
            } catch (IOException e) {
                String errorMessage = "IO Error closing HTTP Client connection";
                log.error(errorMessage, e);
                throw new RuntimeException(errorMessage, e);
            }
        }
    }
}

private CloseableHttpClient createHttpClient(Configuration configuration) {
    return HttpClients.custom().setSSLSocketFactory(getSSLSocketFactory(configuration)).build();
}

public SSLConnectionSocketFactory getSSLSocketFactory(Configuration configuration) {
    try {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        KeyManager[] keyStore = getKeyManager("PKCS12",
                        configuration.getString("keystore.path"),
                        configuration.getString("keystore.password"));
        TrustManager[] trustManager = getTrustManager("jks",
                        configuration.getString("truststore.path"),
                        configuration.getString("truststore.password"));
        sslContext.init(keyStore, trustManager, new SecureRandom());
        return new SSLConnectionSocketFactory(sslContext);
    } catch (Exception e) {
        throw new RuntimeException("Unable to setup keystore and truststore", e);
    }
}

private KeyManager[] getKeyManager(String keyStoreType, String keyStoreFile, String keyStorePassword) throws Exception {
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyStore.load(this.getClass().getClassLoader().getResourceAsStream(keyStoreFile), keyStorePassword.toCharArray());
    kmf.init(keyStore, keyStorePassword.toCharArray());
    return kmf.getKeyManagers();

}

private TrustManager[] getTrustManager(String trustStoreType, String trustStoreFile, String trustStorePassword) throws Exception {
    KeyStore trustStore = KeyStore.getInstance(trustStoreType);
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustStore.load(this.getClass().getClassLoader().getResourceAsStream(trustStoreFile), trustStorePassword.toCharArray());
    tmf.init(trustStore);

    return tmf.getTrustManagers();
}

The path for both truststore and the keystore are maintained in the classpath.

We had got the .crt from the 3rd party vendor, I generated the keys following the instructions from this: https://gist.github.com/mtigas/952344

From the p12 generated, I imported them into a jks following this: https://www.webfarmr.eu/2010/04/import-pkcs12-private-keys-into-jks-keystores-using-java-keytool/

Yet, when I run my tests, it fails consistently, with the above error. I don't want to manually add my certificate to the JVM, is it possible to still make the HTTPs calls without adding certificate to the server?

My local unit test passes if I make the below change in code in getting the SSL context:

public SSLConnectionSocketFactory getSSLSocketFactory(Configuration configuration) {
    try {

SSLContexts.custom().loadKeyMaterial(getKeyManager("PKCS12",
                        configuration.getString("keystore"),
                        configuration.getString("keystore.password")),
configuration.getString("keystore.password").toCharArray()).build();
        return new SSLConnectionSocketFactory(sslContext);
    } catch (Exception e) {
        throw new RuntimeException("Unable to setup keystore and truststore", e);
    }
}

I don't know what's difference when I run it as an integration test, as in why it's not loading the certifiate.


Solution

  • The problem was in the generation of the PKCS12 file and in generating the keystore, corrected them and it worked.

    The commands used were:

    keytool -import -trustcacerts -alias root -file <file>.crt -keystore keystore.jks
    
    keytool -importkeystore -srckeystore keystore.jks -destkeystore <new-file>.p12 -srcstoretype JKS -deststoretype PKCS12
    

    This works with both sets of code mentioned above.