首先要获取服务器证书以便手动进行验证,无论它是否有效,最简单的方法是在禁用任何证书验证后通过SSLSocket 连接。
创建一个SSLContext 让任何东西都可以通过:
SSLContext sslContext = SSLContext.getInstance("TLS");
X509TrustManager passthroughTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { passthroughTrustManager },
null);
(旁注:对于寻找在内部使用测试证书的方法的人,我建议构建自己的测试 CA 而不是禁用检查,以防这些虚拟检查错误地留在生产代码中,并且因为它使测试更加真实。)
创建一个套接字,连接并显式地开始握手(因为你不会真正阅读它):
SSLSocketFactory ssf = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket) ssf.createSocket(
"the.host.name", 443);
socket.startHandshake();
获取对等证书链。第一项是实际的服务器证书。
X509Certificate[] peerCertificates = (X509Certificate[]) socket
.getSession().getPeerCertificates();
如果您想针对默认信任锚(默认受信任的 CA 证书)进行验证,请根据默认值构建一个 X509TrustManager(这实际上是上面的 passthroughTrustManager 禁用的):
// By default on Oracle JRE, algorithm is PKIX
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// 'null' will initialise the tmf with the default CA certs installed
// with the JRE.
tmf.init((KeyStore) null);
X509TrustManager tm = (X509TrustManager) tmf.getTrustManagers()[0];
现在,及时检查证书当前是否有效,是否针对受信任的锚进行验证,以及是否根据RFC 3280具有正确的扩展名。
try {
// Assuming RSA key here.
tm.checkServerTrusted(peerCertificates, "RSA");
} catch (CertificateException e) {
// Here you may check which subclass of CertificateException to know what the error is.
}
以上所有内容都使用了JSSE API。
或者,如果您想深入了解 PKIX 的细节,可以使用Java Certification Path API(JSSE 使用)。
(如果您只想要有效的证书,只需在开始时使用SSLContext sslContext = SSLContext.getDefault(),创建套接字并进行握手。)
要直接获取服务器证书,可以这样:
X509Certificate serverCert = peerCertificates[0];
X509Certificate 有许多关于日期和各种扩展的方法。例如:
Date expirationDate = serverCert.getNotAfter();
主机名验证应遵循RFC 6125(或至少RFC 2818)。简而言之,检查您要连接的主机名是否是主题备用名称 (SAN) DNS 条目之一。否则,请返回检查主机名是否在证书的 CN RDN 中(为此,您需要将主题 DN 拆分为 RDN)。你可能也对this discussion (on the specific case of using IP addresses)感兴趣。
这完全取决于您要进行多少“手动”验证。除了 API 文档之外,还有许多规范需要阅读(至少是 RFC 5280/RFC 3280 和 RFC 6125/RFC 2818)。
Security.SE 上的这个问题也应该很有趣: