Search code examples
javaself-signedapache-httpcomponentsapache-httpasyncclientapache-httpclient-5.x

Ignore self signed certificates in AsyncClientHttp2Multiplexing


I'm trying to create multiple asynchronous HTTP connections using single client endpoint I have tried the multiplexing example given in Apache site

the code snippet is as follows,

final IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
            .setSoTimeout(Timeout.ofSeconds(5))
            .build();

    final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal(
            HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, null, ioReactorConfig);

    client.start();

    final HttpHost target = new HttpHost("localhost", 7070, "https");
    final Future<AsyncClientEndpoint> leaseFuture = client.lease(target, null);
    final AsyncClientEndpoint endpoint = leaseFuture.get(30, TimeUnit.SECONDS);
    try {
        final String[] requestUris = new String[] {"/test.html"};

        final CountDownLatch latch = new CountDownLatch(requestUris.length);
        for (final String requestUri: requestUris) {
            final SimpleHttpRequest request = SimpleHttpRequest.get(target, requestUri);
            endpoint.execute(
                    SimpleRequestProducer.create(request),
                    SimpleResponseConsumer.create(),
                    new FutureCallback<SimpleHttpResponse>() {

                        @Override
                        public void completed(final SimpleHttpResponse response) {
                            latch.countDown();
                            System.out.println(requestUri + "->" + response.getCode());
                            System.out.println(response.getBody());
                        }

                        @Override
                        public void failed(final Exception ex) {
                            latch.countDown();
                            System.out.println(requestUri + "->" + ex);
                        }

                        @Override
                        public void cancelled() {
                            latch.countDown();
                            System.out.println(requestUri + " cancelled");
                        }

                    });
        }
        latch.await();
    } finally {
        endpoint.releaseAndReuse();
    }

    System.out.println("Shutting down");
    client.shutdown(ShutdownType.GRACEFUL);

This example works properly for a site which have a valid certificate, but if I want to try a site which certificate is expired/self signed, it throws the following exceptions

javax.net.ssl.SSLHandshakeException: General SSLEngine problem javax.net.ssl.SSLHandshakeException: General SSLEngine problem at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1478) at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535) at sun.security.ssl.SSLEngineImpl.writeAppRecord(SSLEngineImpl.java:1214) at sun.security.ssl.SSLEngineImpl.wrap(SSLEngineImpl.java:1186) at javax.net.ssl.SSLEngine.wrap(SSLEngine.java:469) at org.apache.hc.core5.reactor.ssl.SSLIOSession.doWrap(SSLIOSession.java:256) at org.apache.hc.core5.reactor.ssl.SSLIOSession.doHandshake(SSLIOSession.java:294) at org.apache.hc.core5.reactor.ssl.SSLIOSession.isAppInputReady(SSLIOSession.java:502) at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:112) at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:50) at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:173) at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:123) at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:80) at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44) at java.lang.Thread.run(Thread.java:748) Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1728) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:304) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026) at sun.security.ssl.Handshaker$1.run(Handshaker.java:966) at sun.security.ssl.Handshaker$1.run(Handshaker.java:963) at java.security.AccessController.doPrivileged(Native Method) at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1416) at org.apache.hc.core5.reactor.ssl.SSLIOSession.doRunTask(SSLIOSession.java:274) at org.apache.hc.core5.reactor.ssl.SSLIOSession.doHandshake(SSLIOSession.java:331) ... 8 more 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:281) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:136) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1501) ... 16 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) ... 22 more

What I have tried: I created a socket factory that trusts all certificates and tried setting in connectionManager but that creates a CloseableHttpClient and I think which cannot be used for asynchronous multiplexing, the code is as follows

SSLContext sslContext = SSLContextBuilder
                .create()
                .loadTrustMaterial(new TrustSelfSignedStrategy())
                .build();

        // we can optionally disable hostname verification. 
        // if you don't want to further weaken the security, you don't have to include this.
        HostnameVerifier allowAllHosts = new NoopHostnameVerifier();

        // create an SSL Socket Factory to use the SSLContext with the trust self signed certificate strategy
        // and allow all hosts verifier.
        SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts);

        Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create().register("https", connectionFactory).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
        CloseableHttpClient build = HttpClients.custom().setConnectionManager(cm).build();

Kindly let me know if there are any ways or workarounds to ignore self signed certificates in MinimalHttpAsyncClient.


Solution

  • First off, configure SSL context to be used by your application. It is highly advisable to make it trust only specific self-signed certificate if you absolutely have to do so, instead of trusting all certificates indiscriminately

    final SSLContext sslContext = SSLContexts.custom()
            .loadTrustMaterial(new TrustAllStrategy())
            .build();
    

    Create a custom connection manager with the given SSL context

    final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
            .setTlsStrategy(new H2TlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE))
            .build();
    

    Create a custom HttpAsyncClient instance with the given connection manager

    final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal(
           HttpVersionPolicy.FORCE_HTTP_2, 
           H2Config.DEFAULT, 
           null, 
           ioReactorConfig, 
           connectionManager);
    

    Alternatively, if you only care about HTTP/2 and do not need a client with HTTP/1.1 fall-back, consider using an HTTP/2 optimized implementations. A minimal implementation will provide essential message transport features (no state management, no authentication, no caching, no automatic redirects) with a least possible overhead.

    final MinimalHttp2AsyncClient h2ClientMinimal = HttpAsyncClients.createHttp2Minimal(
          H2Config.DEFAULT, 
          ioReactorConfig, 
          new H2TlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE));
    

    A full-featured implementation will provide a fully featured HTTP/2 transport with all the functionality supported by the classic HttpClient with a sole exception of transparent content decompression:

    final HttpAsyncClient h2Client = HttpAsyncClients.customHttp2()
            .setIOReactorConfig(ioReactorConfig)
            .setTlsStrategy(new H2TlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE))
            .build();
    

    Hope this helps