最近在学习用java实现HTTPS协议,Client用HttpsURLConnection来发起访问。传给SSLContext的TrustManager是我自己实现的一个简单的X509TrustManager,按照以往经验将checkClientTrusted和checkServerTrusted重写为空函数,getAcceptedIssuers方法直接返回null。本以为不会再有认证服务器的错误,但依然报错如下:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: No subject alternative names present
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:352)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:295)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:290)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1359)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1268)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:401)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:373)
at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:567)
at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1592)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
at com.company.SimpleHTTPClient.main(SimpleHTTPClient.java:52)
Caused by: java.security.cert.CertificateException: No subject alternative names present
at java.base/sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:142)
at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:101)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:455)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:429)
at java.base/sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:1544)
at java.base/sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1511)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:638)
... 17 more
我使用的Java版本是Java 11.0.10,先给出解决办法:
X509TrustManager trustManager = new X509TrustManager() {
...
}
改为
X509TrustManager trustManager = new X509ExtendedTrustManager() {
...
}
有兴趣了解分析过程的小伙伴可以接着看下面内容。
调试观察函数调用栈,发现调用CertificateMessage.checkServerCerts()方法时,会调用tm.checkServerTrusted(),但这个tm是sun.security.ssl.X509TrustManagerImpl,而不是我传进去自己实现的X509TrustManager。
为了找出原因,再跟踪SSLContext.init()过程。sun.security.ssl.SSLContextImpl.engineInit()方法中通过this.trustManager = this.chooseTrustManager(tm)的带SSL上下文的trustManager。下面是chooseTrustManager方法的代码。
private X509TrustManager chooseTrustManager(TrustManager[] tm) throws KeyManagementException {
for(int i = 0; tm != null && i < tm.length; ++i) {
if (tm[i] instanceof X509TrustManager) {
if (SunJSSE.isFIPS() && !(tm[i] instanceof X509TrustManagerImpl)) {
throw new KeyManagementException("FIPS mode: only SunJSSE TrustManagers may be used");
}
if (tm[i] instanceof X509ExtendedTrustManager) {
return (X509TrustManager)tm[i];
}
return new AbstractTrustManagerWrapper((X509TrustManager)tm[i]);
}
}
return DummyX509TrustManager.INSTANCE;
}
可以看到,如果我们实现的是X509TrustManager,而不是一个X509ExtendedTrustManager,调用返回new AbstractTrustManagerWrapper((X509TrustManager)tm[i]);因此我需要实现的是X509ExtendedTrustManager而不止是X509TrustManager。