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