【问题标题】:How do I retrieve an untrusted SSL server certificate in order to review and trust it?如何检索不受信任的 SSL 服务器证书以查看和信任它?
【发布时间】:2012-05-21 13:34:16
【问题描述】:

我的问题:

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

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

我在检索证书时遇到了困难。我有代码(见文章末尾),这些代码是我从这里和从回答有关 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

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

代码:

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

【问题讨论】:

    标签: java ssl ssl-certificate


    【解决方案1】:

    您可以通过实现一个接受所有证书的临时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】:

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-14
      • 2011-08-01
      • 1970-01-01
      • 2014-08-16
      • 2016-01-15
      • 1970-01-01
      • 2014-11-29
      相关资源
      最近更新 更多