2012-05-14 20 views
5

Il mio problema:Come posso recuperare un certificato del server SSL non attendibile per poterlo verificare e fidarsi?

Voglio collegarmi ai server (non limitato al protocollo HTTPS - potrebbe essere LDAP-over-SSL, potrebbe essere SMTPS, potrebbe essere IMAPS, ecc.) Che potrebbe utilizzare i certificati che Java non si fideranno di default (perché sono autofirmati).

Il flusso di lavoro desiderato è tentare la connessione, recuperare le informazioni sul certificato, presentarlo all'utente e, se lo accetta, aggiungerlo al truststore in modo che possa essere considerato affidabile in futuro.

Sono bloccato al recupero del certificato. Ho il codice (vedi alla fine del post) che ho cribbed da qui e dai siti a cui si fa riferimento con le risposte alle domande su SSL Java. Il codice crea semplicemente un SSLSocket, avvia l'handshake SSL e richiede la sessione SSL per lo Certificate[]. Il codice funziona correttamente quando mi collego a un server utilizzando un certificato già affidabile. Ma quando mi collego a un server utilizzando un CERT auto-firmato ho la solita:

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] 

Se corro con -Djavax.net.debug=all vedo che la JVM fa recuperare il CERT auto-firmato, ma farà saltare la connessione per l'utilizzo un certificato non attendibile prima di arrivare al punto in cui restituirà i certificati.

Sembra un problema di pollo e uova. Non mi consente di vedere i certificati perché non sono attendibili. Ma ho bisogno di vedere i certificati per essere in grado di aggiungerli al truststore in modo che siano attendibili. Come si esce da questo?

Per esempio, se faccio funzionare il programma come:

java SSLTest www.google.com 443 

ricevo una stampa dei certs Google sta utilizzando. Ma se lo eseguo come

java SSLTest my.imap.server 993 

Ricevo l'eccezione di cui sopra.

Il codice:

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

risposta

7

È possibile eseguire l'implementazione di uno TrustManager temporaneo che accetta tutti i certificati e uno HostnameVerifier temporaneo che verifica tutti i nomi (ovviamente è necessario utilizzarli solo per recuperare il certificato e non per inviare dati privati).

Il codice seguente recuperare i certificati da un URL HTTPS arbitraria e salvarli in un file:

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(); 
Problemi correlati