2012-05-14 12 views
5

Mi problema:¿Cómo recupero un certificado de servidor SSL no confiable para revisarlo y confiar en él?

Quiero conectarme a los servidores (no limitado al protocolo HTTPS - podría ser LDAP-over-SSL, podría ser SMTPS, podría ser IMAPS, etc.) que pueda estar usando certificados que Java no confiará por defecto (porque están autofirmados).

El flujo de trabajo deseado es intentar la conexión, recuperar la información del certificado, presentarla al usuario y, si la acepta, agregarla al almacén de confianza para que sea confiable en el futuro.

Estoy atascado en la recuperación del certificado. Tengo un código (ver al final de la publicación) que he copiado desde aquí y desde sitios apuntados por respuestas a preguntas sobre java SSL. El código simplemente crea un SSLSocket, inicia el protocolo de enlace SSL y solicita la sesión SSL para el Certificate[]. El código funciona bien cuando me conecto a un servidor usando un certificado ya confiable. Pero cuando me conecto a un servidor utilizando un certificado autofirmado tengo la costumbre:

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] 

Si me quedo con -Djavax.net.debug=all veo que la JVM hace recuperar el certificado autofirmado pero soplará la conexión para el uso un certificado que no es de confianza antes de llegar al punto en el que devolverá los certificados.

Parece un problema de huevo y gallina. No me deja ver los certificados porque no son de confianza. Pero necesito ver los certificados para poder agregarlos al almacén de confianza para que sean confiables. ¿Cómo rompes esto?

Por ejemplo, si se me acaba el programa como:

java SSLTest www.google.com 443 

consigo una copia impresa de los certs Google está utilizando. Pero si lo ejecuto como

java SSLTest my.imap.server 993 

obtengo la excepción mencionada anteriormente.

El código:

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

Respuesta

7

Para ello, puede implementar un temporal TrustManager que acepte todos los certificados y una temporal HostnameVerifier que verifica todos los nombres (obviamente tiene que usarlos sólo para recuperar el certificado y no enviar datos privados).

el siguiente código recuperar los certificados desde una dirección URL HTTPS arbitraria y guardar en un archivo:

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(); 
Cuestiones relacionadas