我的问题:
我想要连接到一些服务器(不限于HTTPS协议 - 可能是使用自签名证书的LDAP-over-SSL,SMTPS,IMAPS等),这些服务器Java默认是不信任的。
期望的工作流程是尝试连接,检索证书信息,将其呈现给用户,如果用户接受,则将其添加到信任库中,以便今后可信任。
我遇到了检索证书的难题。我从这里和其他关于Java SSL的问题的答案链接到的网站中抄来了代码(请见本帖底部),该代码简单地创建一个SSLSocket
,启动SSL握手,并要求SSL会话返回Certificate []
。当我连接到已经被信任的证书的服务器时,代码工作得很好。但当我连接到使用自签名证书的服务器时,我就会遇到通常的问题:
Exception in thread "main" javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
[etc]
如果我使用
-Djavax.net.debug=all
运行,可以看到JVM确实检索了自签名证书,但在到达返回证书的点之前,由于使用未受信任的证书而破坏连接。
好像是一个先有鸡还是先有蛋的问题。它不会让我看到证书,因为它们不受信任。但是我需要查看证书,才能将它们添加到信任库中,以便它们受信任。如何打破这个困境?
例如,如果我按以下方式运行程序:
java SSLTest www.google.com 443
我得到了谷歌使用的证书的打印输出。但如果我按以下方式运行:
java SSLTest my.imap.server 993
I get the exception referenced above.
The code:
import java.io.InputStream;
import java.io.OutputStream;
import java.security.cert.*;
import javax.net.SocketFactory;
import javax.net.ssl.*;
public class SSLTest
{
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: SSLTest host port");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
SocketFactory factory = SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.startHandshake();
Certificate[] certs = socket.getSession().getPeerCertificates();
System.out.println("Certs retrieved: " + certs.length);
for (Certificate cert : certs) {
System.out.println("Certificate is: " + cert);
if(cert instanceof X509Certificate) {
try {
( (X509Certificate) cert).checkValidity();
System.out.println("Certificate is active for current date");
} catch(CertificateExpiredException cee) {
System.out.println("Certificate is expired");
}
}
}
}
}