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 的支持

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.

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

参考资料