Conscrypt with jdk8 to enable ALPN for http2

2020-02-13 02:13发布

问题:

I have been searching how to implement Conscrypt SSL provider using conscrypt-openjdk-uber-1.4.1.jar for jdk8 to support ALPN for making a http2(using apache httpclient 5) connection to a server as jdk8 does not support ALPN by default or the other solution is to migrate to jdk9(or higher) which is not feasible for now as our product is heavily dependent on jdk8

I have been searching extensively for some docs or examples to implement but I could not find one.

I have tried to insert conscrypt provider as default and my program takes it as default provider but still it fails to connect with http2 server, my example is as follows,

public static void main(final String[] args) throws Exception {
    Security.insertProviderAt(new OpenSSLProvider(), 1);
    final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustAllStrategy()).build();
    final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(new H2TlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE)).build();
    final IOReactorConfig ioReactorConfig = IOReactorConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build();
    final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal(HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, null, ioReactorConfig, connectionManager);

    client.start();
    final HttpHost target = new HttpHost("localhost", 8082, "https");
    final Future<AsyncClientEndpoint> leaseFuture = client.lease(target, null);
    final AsyncClientEndpoint endpoint = leaseFuture.get(10, TimeUnit.SECONDS);
    try {
        String[] requestUris = new String[] {"/"};
        CountDownLatch latch = new CountDownLatch(requestUris.length);
        for (final String requestUri: requestUris) {
            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);
                            ex.printStackTrace();
                        }

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

                    });
        }
        latch.await();
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        endpoint.releaseAndReuse();
    }

    client.shutdown(ShutdownType.GRACEFUL);
}

this programs gives the output as

org.apache.hc.core5.http.ConnectionClosedException: Connection closed org.apache.hc.core5.http.ConnectionClosedException: Connection closed at org.apache.hc.core5.http2.impl.nio.FrameInputBuffer.read(FrameInputBuffer.java:146) at org.apache.hc.core5.http2.impl.nio.AbstractHttp2StreamMultiplexer.onInput(AbstractHttp2StreamMultiplexer.java:415) at org.apache.hc.core5.http2.impl.nio.AbstractHttp2IOEventHandler.inputReady(AbstractHttp2IOEventHandler.java:63) at org.apache.hc.core5.http2.impl.nio.ClientHttp2IOEventHandler.inputReady(ClientHttp2IOEventHandler.java:38) at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:117) 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)

If I print the provider and version it prints as Conscrypt version 1.0 and JDK 1.8.0_162, but still it fails to connect with a http2 endpoint

same chunk of code works perfectly if I connect using jdk9 with default provider, what I m missing here in conscrypt configuration?

Any help is appreciated

Thanks in advance

回答1:

Just replacing the default JSSE provider with Conscrypt is not enough. One also needs a custom TlsStrategy that can take advantage of Conscrypt APIs.

This what works for me with Java 1.8 and Conscrypt 1.4.1

static class ConscriptClientTlsStrategy implements TlsStrategy {

    private final SSLContext sslContext;

    public ConscriptClientTlsStrategy(final SSLContext sslContext) {
        this.sslContext = Args.notNull(sslContext, "SSL context");
    }

    @Override
    public boolean upgrade(
            final TransportSecurityLayer tlsSession,
            final HttpHost host,
            final SocketAddress localAddress,
            final SocketAddress remoteAddress,
            final Object attachment) {
        final String scheme = host != null ? host.getSchemeName() : null;
        if (URIScheme.HTTPS.same(scheme)) {
            tlsSession.startTls(
                    sslContext,
                    host,
                    SSLBufferMode.STATIC,
                    (endpoint, sslEngine) -> {
                        final SSLParameters sslParameters = sslEngine.getSSLParameters();
                        sslParameters.setProtocols(H2TlsSupport.excludeBlacklistedProtocols(sslParameters.getProtocols()));
                        sslParameters.setCipherSuites(H2TlsSupport.excludeBlacklistedCiphers(sslParameters.getCipherSuites()));
                        H2TlsSupport.setEnableRetransmissions(sslParameters, false);
                        final HttpVersionPolicy versionPolicy = attachment instanceof HttpVersionPolicy ?
                                (HttpVersionPolicy) attachment : HttpVersionPolicy.NEGOTIATE;
                        final String[] appProtocols;
                        switch (versionPolicy) {
                            case FORCE_HTTP_1:
                                appProtocols = new String[] { ApplicationProtocols.HTTP_1_1.id };
                                break;
                            case FORCE_HTTP_2:
                                appProtocols = new String[] { ApplicationProtocols.HTTP_2.id };
                                break;
                            default:
                                appProtocols = new String[] { ApplicationProtocols.HTTP_2.id, ApplicationProtocols.HTTP_1_1.id };
                        }
                        if (Conscrypt.isConscrypt(sslEngine)) {
                            sslEngine.setSSLParameters(sslParameters);
                            Conscrypt.setApplicationProtocols(sslEngine, appProtocols);
                        } else {
                            H2TlsSupport.setApplicationProtocols(sslParameters, appProtocols);
                            sslEngine.setSSLParameters(sslParameters);
                        }
                    },
                    (endpoint, sslEngine) -> {
                        if (Conscrypt.isConscrypt(sslEngine)) {
                            return new TlsDetails(sslEngine.getSession(), Conscrypt.getApplicationProtocol(sslEngine));
                        }
                        return null;
                    });
            return true;
        }
        return false;
    }

}

public static void main(String[] args) throws Exception {
    final SSLContext sslContext = SSLContexts.custom()
            .setProvider(Conscrypt.newProvider())
            .build();
    final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create()
            .setTlsStrategy(new ConscriptClientTlsStrategy(sslContext))
            .build();
    try (CloseableHttpAsyncClient client = HttpAsyncClients.custom()
            .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
            .setConnectionManager(cm)
            .build()) {

        client.start();

        final HttpHost target = new HttpHost("nghttp2.org", 443, "https");
        final String requestUri = "/httpbin";
        final HttpClientContext clientContext = HttpClientContext.create();

        final SimpleHttpRequest request = SimpleHttpRequests.GET.create(target, requestUri);
        final Future<SimpleHttpResponse> future = client.execute(
                SimpleRequestProducer.create(request),
                SimpleResponseConsumer.create(),
                clientContext,
                new FutureCallback<SimpleHttpResponse>() {

                    @Override
                    public void completed(final SimpleHttpResponse response) {
                        System.out.println(requestUri + "->" + response.getCode() + " " +
                                clientContext.getProtocolVersion());
                        System.out.println(response.getBody());
                        final SSLSession sslSession = clientContext.getSSLSession();
                        if (sslSession != null) {
                            System.out.println("SSL protocol " + sslSession.getProtocol());
                            System.out.println("SSL cipher suite " + sslSession.getCipherSuite());
                        }
                    }

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

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

                });
        future.get();

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