如何检索不受信任的SSL服务器证书以便查看和信任它?

我的问题:

我想连接到服务器(不限于HTTPS协议 – 可能是LDAP-over-SSL,可能是SMTPS,可能是IMAPS等),可能正在使用Java默认不信任的证书(因为它们是自签名)。

所需的工作流程是尝试连接,检索证书信息,将其呈现给用户,如果他接受,则将其添加到信任库,以便将其信任。

我被困在检索证书。 我有代码(请参阅post的末尾),我已经从这里和从有关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 

我得到了Google正在使用的证书的打印输出。 但如果我把它作为

 java SSLTest my.imap.server 993 

我得到上面引用的exception。

代码:

 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"); } } } } } 

请参阅Andreas Sterbenz的InstallCert实用程序的此副本 。

您可以这样做,实现一个接受所有证书的临时TrustManager和一个validation所有名称的临时HostnameVerifier (显然您只需要使用它们来检索证书而不是发送私有数据)。

以下代码从任意https URL检索证书并将其保存到文件中:

 URL url = new URL("https://"); SSLContext sslCtx = SSLContext.getInstance("TLS"); sslCtx.init(null, new TrustManager[]{ new X509TrustManager() { private X509Certificate[] accepted; @Override public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { accepted = xcs; } @Override public X509Certificate[] getAcceptedIssuers() { return accepted; } }}, null); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String string, SSLSession ssls) { return true; } }); connection.setSSLSocketFactory(sslCtx.getSocketFactory()); if (connection.getResponseCode() == 200) { Certificate[] certificates = connection.getServerCertificates(); for (int i = 0; i < certificates.length; i++) { Certificate certificate = certificates[i]; File file = new File("/tmp/newcert_" + i + ".crt"); byte[] buf = certificate.getEncoded(); FileOutputStream os = new FileOutputStream(file); os.write(buf); os.close(); } } connection.disconnect();