Java中TLS及HTTPS学习
Contents
HTTPS
在 Java 中连接 HTTPS 示例代码
@Test
public void testHandshake() throws Exception {
URL url = new URL("https://www.google.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.getInputStream();
}
SNI
Server Name Indication
因为服务器可能是在多虚拟主机下配置的, 比如配置了
https://beta.host.com
https://beta1.host.com
但它们是同一个 IP, 同一个端口(443) 下的, 普通的 HTTP 连接, 通过 Host
请求头可以区别出来. 但在 HTTPS 中, 由于建立 TLS 是在 TCP 层, 所以需要额外的标识来决定哪个 Server Name . 这就是 SNI 的作用.
它是在 https://www.ietf.org/rfc/rfc4366.txt 中定义的. 属于 TLS extension .
如果连接的时候, 不指定 SNI , 则它会根据 Nginx 的默认主机(default
) (如果有) 或 配置文件里的顺序按第一个匹配成功的主机来建立 TLS 连接.
验证
要想知道如果不指定 server name 时, 默认建立的 TLS 实际是哪个虚拟主机, 可以使用下面的命令
# 不指定 server name , 这时看输出的结果, 跟 connect 后面的主机名是否一致
echo | openssl s_client -connect weibo.com:443 | openssl x509 -noout -dates
# 指定 server name
echo | openssl s_client -connect weibo.com:443 -servername weibo.com | openssl x509 -noout -dates
Java 中对 SNI 的支持
- JDK6 在
Java™ SE Development Kit 6, Update 121 (JDK 6u121)
中才添加了对 SNI 的支持. https://www.oracle.com/java/technologies/javase/6-relnotes.html . 不过, 该版本要付费. JDK6 最新可免费下载的版本为JDK 6u45
Addition of the SNI extension to ClientHello
This extension is described in RFC 6066 section 3. This extension may be disabled by setting the
jsse.enableSNIExtension
system property to false.
- JDK7 默认是会发送SNI
- JDK 8 默认也会发送 SNI . 但如果设置了
connection.setHostnameVerifier();
就会导致失效而没发送 SNI 了. 而 JDK7 设置了也会发送. 如果实在要设置这个参数, 可参考下面的参考该连接的做法 https://javabreaks.blogspot.com/2015/12/java-ssl-handshake-with-server-name.html
Java 中调试是否发送SNI
java -Djavax.net.debug=all -jar demo-1.0-SNAPSHOT.jar > console.log 2>&1
即设置属性 javax.net.debug=all
. 然后查找是否有 SNI 的属性
$ cat console.log | grep -i "server_name"
Extension server_name, server_name: [host_name: weibo.com]
Extension server_name, server_name:
第一行表示的是 ClientHello , 第二行表示的是 ServerHello .
如果没发送, 则没有输出
常见的异常
验证 Host 不一致
即请求的Host 是 A, 但证书的 Host 是 B.
javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching xxxxx found.
javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching xxxxx found.
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1731)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:241)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:235)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1206)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:136)
at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:593)
at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:529)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:925)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1170)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1197)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1181)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:434)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:133)
at com.uniweibo.Main.sendInfoToWeiBo(Main.java:117)
at com.uniweibo.Main.main(Main.java:20)
Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching xxxxx found.
at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:193)
at sun.security.util.HostnameChecker.match(HostnameChecker.java:77)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:264)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:250)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1185)
证书过期
查看证书的时间
echo | openssl s_client -connect weibo.com:443 | openssl x509 -noout -dates
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: timestamp check failed
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: timestamp check failed
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153)
at com.uniweibo.Main.sendInfoToWeiBo(Main.java:118)
at com.uniweibo.Main.main(Main.java:20)
Caused by: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: timestamp check failed
at sun.security.validator.PKIXValidator.doValidate(PKIXValidator.java:350)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:260)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)
... 12 more
Caused by: java.security.cert.CertPathValidatorException: timestamp check failed
at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:159)
at sun.security.provider.certpath.PKIXCertPathValidator.doValidate(PKIXCertPathValidator.java:351)
at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:191)
at java.security.cert.CertPathValidator.validate(CertPathValidator.java:279)
at sun.security.validator.PKIXValidator.doValidate(PKIXValidator.java:345)
... 18 more
Caused by: java.security.cert.CertificateExpiredException: NotAfter: Fri May 15 20:00:00 HKT 2020
at sun.security.x509.CertificateValidity.valid(CertificateValidity.java:273)
at sun.security.x509.X509CertImpl.checkValidity(X509CertImpl.java:575)
at sun.security.provider.certpath.BasicChecker.verifyTimestamp(BasicChecker.java:184)
at sun.security.provider.certpath.BasicChecker.check(BasicChecker.java:136)
at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:133)
... 22 more