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

Mi problema:

Quiero conectarme a servidores (no limitados al protocolo HTTPS; podría ser LDAP sobre SSL, SMTPS, IMAPS, etc.) que pueden estar usando certificados en los que Java no confiará de forma predeterminada (porque son autofirmado).

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 recuperar el certificado. Tengo un código (ver al final de la publicación) que he encontrado aquí y de los sitios señalados por las respuestas a las preguntas sobre java SSL. El código simplemente crea unSSLSocket, inicia el protocolo de enlace SSL y solicita la sesión SSL para elCertificate[]. El código funciona bien cuando me conecto a un servidor utilizando un certificado que ya es confiable. Pero cuando me conecto a un servidor utilizando un certificado autofirmado, obtengo lo habitual:

<code>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]
</code>

Si corro con-Djavax.net.debug=all Veo que la JVM recupera el certificado autofirmado pero hará explotar la conexión por el uso de un certificado no confiable 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 salir de esto?

Por ejemplo, si ejecuto el programa como:

<code>java SSLTest www.google.com 443
</code>

Me sale una copia impresa de los certificados que Google está utilizando. Pero si lo ejecuto como

<code>java SSLTest my.imap.server 993
</code>

Me sale la excepción mencionada anteriormente.

El código:

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

Respuestas a la pregunta(2)

Su respuesta a la pregunta