使用 jdk8 Conscrypt 为 http2 启用 ALPN

Conscrypt with jdk8 to enable ALPN for http2

我一直在寻找如何使用 conscrypt-openjdk-uber-1.4.1.jar 实现 Conscrypt SSL 提供程序jdk8 支持 ALPN 以建立 http2(使用 apache httpclient 5) 连接到服务器,因为 jdk8 不支持 ALPN默认 或其他解决方案是迁移到 jdk9(或更高版本),这目前不可行,因为我们的产品 严重依赖 jdk8

我一直在广泛搜索一些要实施的文档或示例,但找不到。

我已经尝试插入 concrypt 提供程序作为默认提供程序,我的程序将其作为默认提供程序,但它仍然无法连接到 http2 服务器,我的示例如下,

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);
}

此程序的输出为

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)

如果我打印提供程序和版本,它会打印为 Conscrypt 版本 1.0JDK 1.8.0_162,但它仍然无法连接到 http2 端点

如果我使用 jdk9 与默认提供程序连接,相同的代码块可以完美运行,我在 concrypt 配置中缺少什么?

感谢任何帮助

提前致谢

仅用 Conscrypt 替换默认的 JSSE 提供程序是不够的。还需要一个可以利用 Conscrypt API 的自定义 TlsStrategy

这对我适用于 Java 1.8 和 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);
    }
}