我正在尝试弄清楚如何向需要 SNI 的服务器发送成功的 HTTP GET 请求。
我在SO等地方搜索了一下,发现有一些文章说JDK7现在支持SNI,还有Apache HTTP Components。
https://issues.apache.org/jira/browse/HTTPCLIENT-1119 https://wiki.apache.org/HttpComponents/SNISupport
相关SO文章:HTTPSURLconnection和Apache(系统)DefaultHttpClient之间的证书链不同
--
但是,我似乎找不到任何说明如何使其工作的文档。
这是我正在使用的代码...
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
String trustedCertsPath = System.getenv("JAVA_HOME") + "/jre/lib/security/cacerts";
FileInputStream certstream = new FileInputStream(new File(trustedCertsPath));
try {
trustStore.load(certstream, "changeit".toCharArray());
} finally {
certstream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient2 = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(uri);
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
tempFile = File.createTempFile(httpFile.getTempFilePrefix(), httpFile.getTempFilePosfix());
FileOutputStream os = new FileOutputStream(tempFile);
InputStream instream = entity.getContent();
try {
IOUtils.copy(instream, os);
} finally {
try { instream.close(); } catch (Exception e) {}
try { os.close(); } catch (Exception e) {}
}
}
} finally {
response.close();
}
当我运行此命令时,请求失败。
服务器在请求中需要 SNI,如果没有 SNI,它会返回一个具有错误 CommonName 的过期证书,因此会被拒绝。
如果我使用 httpclient2 实例,即使用自定义 SSL 上下文进行设置以允许所有 hTTP 证书,则请求会成功。然而,这不是我想要在每天针对不同主机进行大量下载的服务器上启用的功能。
我正在使用httpclient v 4.3.5
任何帮助表示赞赏。
谢谢。
SNI 在 Java 1.7 或更高版本上运行时应该完全透明地工作。无需配置。如果由于某种原因与启用 SNI 的服务器的 SSL 握手失败,您应该能够通过打开 SSL 调试日志记录来找出原因(以及是否正确使用了 SNI 扩展),如此处 [1] 和此处 [2] 中所述
“调试 SSL/TLS 连接”可能看起来已经过时,但在解决 SSL 相关问题时仍然很有用。
[1] http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/ReadDebug.html
[2] http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug
背景: SNI(服务器名称识别)于 2003 年 (RFC 3546) 添加到 TLS v1.2,然后于 2006 年 (RFC 4366) 和 2011 年进行修订 (https://www.rfc-editor.org/rfc/rfc6066.html -当前的)。 SNI 现已成为 TLS v1.3 的一部分(https://datatracker.ietf.org/doc/html/rfc8446 - 于 2018 年发布)。
目的不是添加任何额外的安全性,也不是修复 SSL 漏洞,而是帮助入站前端将传入会话分派到正确的服务器,并在多个服务器侦听相同的 IP 地址和端口时选择适当的证书(现在常见于 CDN)。从今以后,尽管仍然是一个可选的 TLS ClientHello(连接打开数据报)扩展字段,标记为“server_name”,但您可以理解,为了使 Internet 正常运行,任何请求打开 HTTPS 会话的现代浏览器或 HTTP 客户端库都包含 server_name 字段(SNI) 到 ClientHello 中。类似地,任何现代 Web 服务器都允许使用不同的服务器密钥和证书在同一 IP 地址和端口上配置多个虚拟主机。
有关 SNI 的问题与该领域的意外用途有关,这些用途是为了监视谁连接到谁(机密泄露)并强制执行阻止、过滤或限制机制,否则就是拒绝服务。 SNI 最初是 ClientHello 的一个明确字段,很快就变成了 ESNI(加密 SNI - 当时很容易破解),现在是 TLS v1.3 中加密 ClientHello 扩展字段的组成部分。所有问题的详细总结已发布在 https://datatracker.ietf.org/doc/rfc8744/。
检查远程服务器是否需要 SNI 可以轻松实现:
# without SNI - the session closes quickly, no certificate visible
openssl s_client -connect remote.server.com:443
# with SNI - the session handshake is complete, you can see the trace of the server certificate (PEM formatted) at stake
openssl s_client -connect remote.server.com:443 -servername remote.server.com
检查您的 HTTP 客户端软件是否在 clientHello 中设置了 SNI:使用 Java,您可以使用 JVM 选项“-Djavax.net.debug=ssl:handshake”启动您的应用程序或服务器,并应在跟踪中找到类似的内容(此处对于 TLS v1.2):
*** ClientHello, TLSv1.2
RandomCookie: GMT: 1645107779 bytes = { ...
Session ID: { ...
Cipher Suites: [ ...
Compression Methods: { 0 }
Extension elliptic_curves, curve names: ...
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: ...
Extension server_name, server_name: [type=host_name (0),value=remote.server.com]
***
当 SNI 丢失时:这可能完全取决于软件库和版本的组合。例如。 Appache HTTP 客户端 4.3 或更高版本,结合 Java JRE 8 或更高版本(事实上,SSL/TLS 实现是 JRE/JDK 中本机 JSSE 包的一部分)应该可以很好地支持 TLS v1.2,并且在 TLS 情况下实际上需要 v1.3 支持,您应使用 Java 11 或更高版本,否则将 TLS1.3 向后移植到 Oracle JDK 8 或 Open JDK 8(请参阅TLSv1.3 - 现在在 Java 8 中可用吗?)。
在这种情况下,您需要直接通过 JSSE 强制执行 SNI,例如:
你也可能会遇到这个BUG(使用自定义的HostNameVerifier):https://bugs.openjdk.java.net/browse/JDK-8144566