6

我的问题:

我想连接到可能使用 Java 默认不信任的证书(因为它们是自签名)。

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

我被困在检索证书上。我有代码(见文章末尾),这些代码是我从这里和从回答有关 java SSL 的问题所指向的站点中抄来的。SSLSocket该代码只是创建一个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

我得到了上面提到的异常。

编码:

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

2 回答 2

14

您可以执行此操作,实现一个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();
于 2016-05-06T07:37:25.713 回答
2

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

于 2012-05-14T07:05:43.120 回答