在新的 Java 9 客户端中启用 TLS SNI

Posted

技术标签:

【中文标题】在新的 Java 9 客户端中启用 TLS SNI【英文标题】:Enable TLS SNI in new Java 9 client 【发布时间】:2017-11-12 18:19:26 【问题描述】:

如何在新的 Java 9 客户端中启用 SNI 扩展? (包jdk.incubator.http)

HttpClient client = HttpClient.newHttpClient();
client.send(
       HttpRequest
           .newBuilder(uri)
           .GET()
           .build(),
       HttpResponse.BodyHandler.asString());

但此请求因缺少 TLS SNI 扩展而失败

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at java.base/sun.security.ssl.Alerts.getSSLException(Alerts.java:198)
at java.base/sun.security.ssl.Alerts.getSSLException(Alerts.java:159)
at java.base/sun.security.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1905)
at java.base/sun.security.ssl.SSLEngineImpl.processInputRecord(SSLEngineImpl.java:1140)
at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1020)
at java.base/sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:902)
at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:680)
at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:626)
at jdk.incubator.httpclient/jdk.incubator.http.AsyncSSLDelegate.unwrapBuffer(AsyncSSLDelegate.java:476)
at jdk.incubator.httpclient/jdk.incubator.http.AsyncSSLDelegate.handshakeReceiveAndUnWrap(AsyncSSLDelegate.java:395)
at jdk.incubator.httpclient/jdk.incubator.http.AsyncSSLDelegate.doHandshakeImpl(AsyncSSLDelegate.java:294)
at jdk.incubator.httpclient/jdk.incubator.http.AsyncSSLDelegate.doHandshakeNow(AsyncSSLDelegate.java:262)
at jdk.incubator.httpclient/jdk.incubator.http.AsyncSSLDelegate.connect(AsyncSSLDelegate.java:233)
at jdk.incubator.httpclient/jdk.incubator.http.AsyncSSLConnection.connect(AsyncSSLConnection.java:78)
at jdk.incubator.httpclient/jdk.incubator.http.Http2Connection.<init>(Http2Connection.java:263)
at jdk.incubator.httpclient/jdk.incubator.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:108)
at jdk.incubator.httpclient/jdk.incubator.http.ExchangeImpl.get(ExchangeImpl.java:86)
at jdk.incubator.httpclient/jdk.incubator.http.Exchange.establishExchange(Exchange.java:272)
at jdk.incubator.httpclient/jdk.incubator.http.Exchange.responseImpl0(Exchange.java:283)
at jdk.incubator.httpclient/jdk.incubator.http.Exchange.responseImpl(Exchange.java:260)
at jdk.incubator.httpclient/jdk.incubator.http.Exchange.response(Exchange.java:136)
at jdk.incubator.httpclient/jdk.incubator.http.MultiExchange.response(MultiExchange.java:154)
at jdk.incubator.httpclient/jdk.incubator.http.HttpClientImpl.send(HttpClientImpl.java:234)
at CheckProj/Main.Main.lambda$main$0(Main.java:42)
at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:714)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
at jdk.incubator.httpclient/jdk.incubator.http.ResponseProcessors$ByteArrayProcessor.onComplete(ResponseProcessors.java:219)
at jdk.incubator.httpclient/jdk.incubator.http.BlockingPushPublisher.acceptData(BlockingPushPublisher.java:65)
at jdk.incubator.httpclient/jdk.incubator.http.AbstractPushPublisher.consume(AbstractPushPublisher.java:51)
at jdk.incubator.httpclient/jdk.incubator.http.ResponseContent.pushBodyChunked(ResponseContent.java:238)
at jdk.incubator.httpclient/jdk.incubator.http.ResponseContent.pushBody(ResponseContent.java:110)
at jdk.incubator.httpclient/jdk.incubator.http.Http1Response.lambda$readBody$2(Http1Response.java:157)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1161)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:844)

我该如何解决?

【问题讨论】:

你怎么知道它因此而失败? 用wireshark调试、测试、比较数据包。 (HttpUrlConnection 和这个 newHttpClient)。唯一的区别是 SNI。服务器响应:handshake_failure(40) 服务器需要 SNI! 见Which Cipher Suites to enable for SSL Socket? 它向您展示了如何启用协议和密码套件。 SNI 是 TLS 扩展,因此请务必使用 TLS 1.0 或更高版本。 Java 曾经潜入 SSLv3;确保它不是意外启用的。 我确定我使用的是 TLS >= 1.0。但是如何在新的 HttpClient 中启用 SNI 扩展? 【参考方案1】:

这是一个不应该工作的解决方法,但到目前为止。 我已经实现了SSLContext 的子类,它总是创建一个带有主机名/端口集的SSLEngine(以及用于检查主机名的 SSL 参数)。

https://gist.github.com/beders/51d3600d7fb57ad7d36a1745749ef641

这样使用

HttpClient client = HttpClient.newBuilder().sslContext(ctx).sslParameters(ctx.getParametersForSNI()).followRedirects(HttpClient.Redirect.ALWAYS).build();

让我知道它是否有效

【讨论】:

后来的 JDK 版本解决了这个问题吗?似乎内置客户端使用请求 URL 中的主机 看起来它已修复为 10:bugs.openjdk.java.net/browse/JDK-8182589

以上是关于在新的 Java 9 客户端中启用 TLS SNI的主要内容,如果未能解决你的问题,请参考以下文章

JAVA 6 不支持 SNI,还有其他方法可以通过 TLS 验证和接受 SSL 证书吗?

SNI: 实现多域名虚拟主机的SSL/TLS认证

SNI: 实现多域名虚拟主机的SSL/TLS认证

SNI协议分析

Server Name Indication(SNI)

Azure w/TLS/SNI 上的 .net httpclient:请求被中止:无法创建 SSL/TLS 安全通道