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

7

我的问题:

我想要连接到一些服务器(不限于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");
                }
            }
        }
    }
}
2个回答

16

您可以通过实现一个临时TrustManager和一个验证所有名称的临时HostnameVerifier来实现此操作(显然,您只需在检索证书时使用它们,而不是发送私人数据)。

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

URL url = new URL("https://<yoururl>");

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();

2

4
谢谢!这正是我在寻找的东西。为了记录,这里有一个仅限控制台的版本:http://code.google.com/p/java-use-examples/source/browse/trunk/src/com/aw/ad/util/InstallCert.java - QuantumMechanic
我已经取消接受这个答案,并将其给予另一个答案,因为链接已经失效了。(我的上面的评论中的链接也是如此。) - undefined
1
@QuantumMechanic 是的。Oracle又来了。你会认为一个从事数据库业务的公司应该知道如何保持链接的稳定,但当他们接管Sun之后,这是他们首先要破坏的事情。你也会认为一个在软件业务上有几十年经验的公司应该对向后兼容性有所了解。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接