Como faço para recuperar um certificado de servidor SSL não confiável para revisar e confiar nele?

Meu problema:

Eu quero ligar para servidores (não limitado ao protocolo HTTPS - poderia ser LDAP-over-SSL, poderia ser SMTPS, poderia ser IMAPS, etc.) que pode estar usando certificados que Java não confiará por padrão (porque eles são auto-assinado).

O fluxo de trabalho desejado é tentar a conexão, recuperar as informações do certificado, apresentar isso ao usuário e, se ele aceitar, adicioná-lo ao armazenamento confiável, para que seja confiável daqui para frente.

Eu estou preso em recuperar o certificado. Eu tenho código (veja no final do post) que eu tenho aqui e de sites apontados por respostas a perguntas sobre o java SSL. O código simplesmente cria umSSLSocket, inicia o handshake SSL e solicita a sessão SSL para oCertificate[]. O código funciona bem quando estou conectando a um servidor usando um certificado já confiável. Mas quando me conecto a um servidor usando um certificado auto-assinado, recebo o usual:

<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>

Se eu correr com-Djavax.net.debug=all Vejo que a JVM recupera o certificado autoassinado, mas explodirá a conexão para usar um certificado não confiável antes de chegar ao ponto em que ele retornará os certificados.

Parece um problema de galinha e ovo. Não vai me deixar ver os certificados porque eles não são confiáveis. Mas preciso ver os certificados para poder adicioná-los ao truststore para que eles sejam confiáveis. Como você sai disso?

Por exemplo, se eu executar o programa como:

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

Recebo uma cópia dos certificados que o Google está usando. Mas se eu correr como

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

Eu recebo a exceção mencionada acima.

O 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>

questionAnswers(2)

yourAnswerToTheQuestion