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");
}
}
}
}
}
Gracias! Justo el tipo de cosa que estaba buscando. Y para el registro, aquí hay una versión de la misma consola de la misma cosa: http://code.google.com/p/java-use-examples/source/browse/trunk/src/com/aw/ad/ util/InstallCert.java – QuantumMechanic