myoungberg
myoungberg

Reputation: 157

`Unable to find valid certification path...` while building own SSLContext (Java)

I'm trying to add a cert to an HttpClient at runtime via an SSLContext object so that way I can distribute the program to anyone with a JVM and they don't have to worry about loading a cert via the keytool utility. The cert itself would be packaged w/ the program as a resource.

Although many other answers regarding the error message I'll share have historically been answered by use of the keytool, I really don't want my end-users to be encumbered by it. There's a small handful of questions on StackOverflow that move past the keytool and try to offer an alternative solution, but this question is the closest I've found to my actual predicament.

It matches my scenario perfectly, but then imports an existing truststore instead of creating one at runtime and loading it with the certificate.

My question is: Do I even have to save a literal KeyStore when I have the runtime KeyStore object at my disposal? If so, why isn't my code working?

Here's a skeleton of my code (Forgive any breaches of norms, I'm new to coding in Java):

private HttpClient buildClient() {
    return HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .followRedirects(HttpClient.Redirect.ALWAYS)
            .sslContext(getSSLContext())  // Where I set the SSLContext
            .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
            .connectTimeout(Duration.ofMinutes(2L))
            .build();
}

private SSLContext getSSLContext() {
    try {
        return createSSLContext();
    } catch (Exception e) {
        throw new RuntimeException("The necessary certificate could not be added to the HttpClient");
    }
}

private SSLContext createSSLContext() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, KeyManagementException, IOException {
    SSLContext context = SSLContext.getInstance("SSLv3");
    TrustManager[] trustManagers = createTrustManagers();
    context.init(null, trustManagers, null);
    return context;
}

private TrustManager[] createTrustManagers() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
        TrustManagerFactory factory = TrustManagerFactory.getInstance("PKIX");
    factory.init(getConfiguredKeyStore());
    return factory.getTrustManagers();
}

private KeyStore getConfiguredKeyStore() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
        Certificate certificate = getCertificate();
    KeyStore keyStore = createNewKeystore();
    keyStore.setCertificateEntry("www.thewebsite.com", certificate);
    return keyStore;
}

private KeyStore createNewKeystore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    char[] password = "password".toCharArray();
    keyStore.load(null, password);
    return keyStore;
}

private Certificate getCertificate() throws CertificateException {
    InputStream certStream = getClass().getClassLoader().getResourceAsStream("certFile.crt");
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    return certFactory.generateCertificate(certStream);
}

And here is the error it throws (Which is the same as if the certificate hadn't been added as a context at all):

Exception in thread "main" javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:556)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
    at CustomHttpClient.authenticate(CustomHttpClient.java:41)
    at Main.main(Main.java:61)
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:369)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:312)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:480)
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1267)
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1254)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:691)
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1199)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate.lambda$executeTasks$3(SSLFlowDelegate.java:1122)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:155)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate.executeTasks(SSLFlowDelegate.java:1117)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate.doHandshake(SSLFlowDelegate.java:1083)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:484)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader$ReaderDownstreamPusher.run(SSLFlowDelegate.java:268)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$TryEndDeferredCompleter.complete(SequentialScheduler.java:315)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    at java.base/java.lang.Thread.run(Thread.java:832)
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 java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439)
    at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306)
    at java.base/sun.security.validator.Validator.validate(Validator.java:264)
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:285)
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:632)
    ... 23 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297)
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434)
    ... 28 more

I'm scratching my head. Does anyone have any idea what I could do?

Upvotes: 1

Views: 1104

Answers (1)

queeg
queeg

Reputation: 9463

The error message does not say anything about the quality of your keystore - it just states that no suitable certificate could be matched to validate the one the server presented.

When I solved the same problem I build a keystore file at compile time, then added that as a resource into the application jar. The application creates the keystore object by loading from a resource stream - from there on our code looks fairly similar.

Therefore I'd focus more on the certificate inside the keystore and what the server is presenting. At least in my environment I found quite some easter eggs (like the certificate presented to my DEV machine was different than for other users or later in production).

Upvotes: 1

Related Questions