Jak odzyskać niezaufany certyfikat serwera SSL, aby go przejrzeć i zaufać?

Mój problem:

Chcę połączyć się z serwerami (nie ograniczając się do protokołu HTTPS - może to być LDAP-over-SSL, może to być SMTPS, może być IMAPS itp.), Które mogą używać certyfikatów, którym Java nie będzie ufać domyślnie (ponieważ są z podpisem własnym).

Żądany przepływ pracy to próba nawiązania połączenia, pobranie informacji o certyfikacie, przedstawienie go użytkownikowi, a jeśli go zaakceptuje, dodanie go do magazynu zaufanych certyfikatów, aby można było zaufać.

Utknąłem w odzyskaniu certyfikatu. Mam kod (zobacz na końcu posta), który pobrałem z tego miejsca i ze stron wskazanych przez odpowiedzi na pytania dotyczące java SSL. Kod po prostu tworzySSLSocket, uruchamia uzgadnianie SSL i prosi o sesję SSL dlaCertificate[]. Kod działa poprawnie, gdy łączę się z serwerem za pomocą certyfikatu, który jest już zaufany. Ale kiedy łączę się z serwerem za pomocą samopodpisanego certyfikatu, otrzymuję zwykłe:

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

Jeśli ucieknę-Djavax.net.debug=all Widzę, że maszyna JVM pobiera certyfikat samopodpisany, ale przedłuży połączenie, aby użyć niezaufanego certyfikatu, zanim dotrze do punktu, w którym zwróci certyfikaty.

Wygląda na problem z kurczakiem i jajkiem. Nie pozwoli mi zobaczyć certyfikatów, ponieważ nie są one zaufane. Ale muszę zobaczyć certyfikaty, aby móc dodać je do magazynu zaufanych certyfikatów. Jak się z tego wyrwać?

Na przykład, jeśli uruchomię program jako:

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

Dostaję wydruk certyfikatów, których używa Google. Ale jeśli go uruchomię

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

Dostaję wyjątek, o którym mowa powyżej.

Kod:

<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