在做https双向认证通信类开发的时候,遇到一个问题,错误信息是:javax.net.ssl.SSLPeerUnverifiedException: Certificate for <我请求的IP地址> doesn't match any of the subject alternative names: []
遇到这种情况,先查看你安装的Certificate证书的subject alternative name(SAN)是否有设置,设置的记录是否包含你请求的IP/域名,如果没有设置即报此错误。(关于SAN的科普建议百度)
百度的证书举例:
一个证书保护N多个域名,而我所请求的服务器的证书没有设置SAN,所以在做https请求时,走到SSLSocketConnectionFactory的connectSocketFactory时,verifyHostname抛异常,见代码。
SSLSocketConnectionFactory:
@Override
public Socket connectSocket(
final int connectTimeout,
final Socket socket,
final HttpHost host,
final InetSocketAddress remoteAddress,
final InetSocketAddress localAddress,
final HttpContext context) throws IOException {
Args.notNull(host, "HTTP host");
Args.notNull(remoteAddress, "Remote address");
final Socket sock = socket != null ? socket : createSocket(context);
if (localAddress != null) {
sock.bind(localAddress);
}
try {
if (connectTimeout > 0 && sock.getSoTimeout() == 0) {
sock.setSoTimeout(connectTimeout);
}
if (this.log.isDebugEnabled()) {
this.log.debug("Connecting socket to " + remoteAddress + " with timeout " + connectTimeout);
}
sock.connect(remoteAddress, connectTimeout);
} catch (final IOException ex) {
try {
sock.close();
} catch (final IOException ignore) {
}
throw ex;
}
// Setup SSL layering if necessary
if (sock instanceof SSLSocket) {
final SSLSocket sslsock = (SSLSocket) sock;
this.log.debug("Starting handshake");
sslsock.startHandshake();
verifyHostname(sslsock, host.getHostName());
return sock;
} else {
return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context);
}
}
verifyHostname实现细节截取:
final Certificate[] certs = session.getPeerCertificates();
final X509Certificate x509 = (X509Certificate) certs[0];
final X500Principal peer = x509.getSubjectX500Principal();
this.log.debug(" peer principal: " + peer.toString());
final Collection<List<?>> altNames1 = x509.getSubjectAlternativeNames();
if (altNames1 != null) {
final List<String> altNames = new ArrayList<String>();
for (final List<?> aC : altNames1) {
if (!aC.isEmpty()) {
altNames.add((String) aC.get(1));
}
}
this.log.debug(" peer alternative names: " + altNames);
}
后面会将从证书里获取到的altNames和所请求的host做比对,如果没有和host所匹配的,则抛如题的异常了。
if (!this.hostnameVerifier.verify(hostname, session)) {
final Certificate[] certs = session.getPeerCertificates();
final X509Certificate x509 = (X509Certificate) certs[0];
final List<SubjectName> subjectAlts = DefaultHostnameVerifier.getSubjectAltNames(x509);
throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
"of the subject alternative names: " + subjectAlts);
}
解决办法
1. 修改请求的域名/ip,使证书可以覆盖到。
2. 设置证书的SAN,覆盖到请求的域名/ip。
3. 从代码上关闭https请求的主机认证,代码:
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
NoopHostnameVerifier.INSTANCE);//暂时关闭hostnameVerify
// SSLConnectionSocketFactory.getDefaultHostnameVerifier());
问题解决。