2012-05-14 18 views
5

我的問題:如何檢索不可信的SSL服務器證書以檢查和信任它?

我想連接到服務器(不限於HTTPS協議 - 可能是LDAP環比SSL,可能是SMTPS,可能是IMAPS等)可以使用的證書的Java默認情況下不會信任(因爲它們是自簽名的)。

所需的工作流程是嘗試連接,檢索證書信息,將其提供給用戶,如果他接受它,則將其添加到信任庫,以便將來信任它。

我被困在檢索證書。我有代碼(見帖子末尾),我已經從這裏和從有關java SSL問題的答案指向的網站上分離出來。代碼僅創建SSLSocket,啓動SSL握手,並詢問Certificate[]的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"); 
       } 
      } 
     } 
    } 
} 

回答

7

爲此,您可以實施臨時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();