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

Java 如何检索不受信任的SSL服务器证书以查看和信任它?,java,ssl,ssl-certificate,Java,Ssl,Ssl Certificate,我的问题是: 我想连接到服务器(不限于HTTPS协议——可以是SSL上的LDAP,可以是SMTPS,可以是IMAP,等等),这些服务器可能使用Java默认不信任的证书(因为它们是自签名的) 所需的工作流是尝试连接,检索证书信息,将其呈现给用户,如果用户接受它,则将其添加到信任库中,以便继续信任它 我在检索证书时遇到了困难。我有一些代码(见本文末尾),我从这里和一些关于JavaSSL问题的答案所指向的站点上抄袭了这些代码。代码只需创建一个SSLSocket,启动SSL握手,并向SSL会话请求证书[

我的问题是:

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

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

我在检索证书时遇到了困难。我有一些代码(见本文末尾),我从这里和一些关于JavaSSL问题的答案所指向的站点上抄袭了这些代码。代码只需创建一个
SSLSocket
,启动SSL握手,并向SSL会话请求
证书[]
。当我使用已经可信任的证书连接到服务器时,代码工作正常。但当我使用自签名证书连接到服务器时,我会得到通常的:

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
我得到了上面提到的异常

守则:

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

查看此图。

您可以实现一个临时的
信任管理器来接受所有证书,以及一个临时的
主机名验证器来验证所有名称(显然,您只需使用它们来检索证书,而不必发送私有数据)

以下代码从任意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();
URL URL=newurl(“https://”);
SSLContext sslCtx=SSLContext.getInstance(“TLS”);
init(null,new TrustManager[]{new X509TrustManager(){
私人X509证书[]已接受;
@凌驾
public void checkClientTrusted(X509Certificate[]xcs,String String)抛出CertificateException{
}
@凌驾
public void checkServerTrusted(X509Certificate[]xcs,String String)抛出CertificateException{
已接受=xcs;
}
@凌驾
公共X509证书[]getAcceptedIssuers(){
接受退货;
}
}},空);
HttpsURLConnection连接=(HttpsURLConnection)url.openConnection();
connection.setHostnameVerifier(新的HostnameVerifier(){
@凌驾
公共布尔验证(字符串、SSLSession ssls){
返回true;
}
});
connection.setsslssocketfactory(sslCtx.getSocketFactory());
if(connection.getResponseCode()==200){
证书[]证书=连接。getServerCertificates();
for(int i=0;i
谢谢!这正是我要找的东西。为了记录在案,这里有一个同样的东西的控制台版本:链接不再工作:(