¿Cómo puedo utilizar diferentes certificados en conexiones específicas?

Resuelto skiphoppy asked hace 15 años • 5 respuestas

Un módulo que estoy agregando a nuestra gran aplicación Java tiene que conversar con el sitio web protegido por SSL de otra empresa. El problema es que el sitio utiliza un certificado autofirmado. Tengo una copia del certificado para verificar que no estoy encontrando un ataque de intermediario y necesito incorporar este certificado en nuestro código de tal manera que la conexión al servidor sea exitosa.

Aquí está el código básico:

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

Sin ningún manejo adicional para el certificado autofirmado, este muere en conn.getOutputStream() con la siguiente excepción:

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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Idealmente, mi código necesita enseñarle a Java a aceptar este certificado autofirmado, para este lugar de la aplicación y en ningún otro lugar.

Sé que puedo importar el certificado al almacén de autoridad de certificación de JRE y eso permitirá que Java lo acepte. Ese no es un enfoque que quiera adoptar si puedo ayudar; parece muy invasivo hacerlo en todas las máquinas de nuestros clientes para un módulo que quizás no utilicen; afectaría a todas las demás aplicaciones Java que utilizan el mismo JRE, y eso no me gusta, aunque las probabilidades de que cualquier otra aplicación Java acceda a este sitio son nulas. Tampoco es una operación trivial: en UNIX tengo que obtener derechos de acceso para modificar el JRE de esta manera.

También he visto que puedo crear una instancia de TrustManager que realiza algunas comprobaciones personalizadas. Parece que incluso podría crear un TrustManager que delega en el TrustManager real en todos los casos excepto en este certificado. Pero parece que TrustManager se instala globalmente y supongo que afectaría a todas las demás conexiones de nuestra aplicación, y eso tampoco me parece muy bien.

¿Cuál es la forma preferida, estándar o mejor de configurar una aplicación Java para que acepte un certificado autofirmado? ¿Puedo lograr todos los objetivos que tengo en mente anteriormente o tendré que hacer concesiones? ¿Existe una opción que involucre archivos, directorios y ajustes de configuración, y poco o ningún código?

skiphoppy avatar May 13 '09 23:05 skiphoppy
Aceptado

Cree una SSLSocketfábrica usted mismo y configúrela HttpsURLConnectionantes de conectarse.

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

Querrás crear uno SSLSocketFactoryy conservarlo. Aquí hay un esquema de cómo inicializarlo:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

Si necesita ayuda para crear el almacén de claves, comente.


A continuación se muestra un ejemplo de cómo cargar el almacén de claves:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

Para crear el almacén de claves con un certificado en formato PEM, puede escribir su propio código usando CertificateFactory, o simplemente importarlo keytooldesde el JDK (keytool no funcionará para una "entrada de clave", pero está bien para una "entrada confiable" ).

keytool -import -file selfsigned.pem -alias server -keystore server.jks
erickson avatar May 13 '2009 17:05 erickson

Leí MUCHOS lugares en línea para resolver este problema. Este es el código que escribí para que funcione:

ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateString es una Cadena que contiene el Certificado, por ejemplo:

static public String certificateString=
        "-----BEGIN CERTIFICATE-----\n" +
        "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
        "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
        ... a bunch of characters...
        "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
        "-----END CERTIFICATE-----";

He probado que puedes poner cualquier carácter en la cadena del certificado, si está autofirmado, siempre y cuando mantengas la estructura exacta anterior. Obtuve la cadena del certificado con la línea de comando de Terminal de mi computadora portátil.

Josh avatar Dec 30 '2015 16:12 Josh

Si crear una SSLSocketFactoryno es una opción, simplemente importe la clave a la JVM

  1. Recupere la clave pública:, $openssl s_client -connect dev-server:443luego cree un archivo dev-server.pem que se parezca a

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. Importar la clave: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Contraseña: cambiarla

  3. Reiniciar JVM

Fuente: ¿Cómo resolver javax.net.ssl.SSLHandshakeException?

user454322 avatar Nov 02 '2012 10:11 user454322

Copiamos el almacén de confianza del JRE y agregamos nuestros certificados personalizados a ese almacén de confianza, luego le indicamos a la aplicación que use el almacén de confianza personalizado con una propiedad del sistema. De esta manera dejamos solo el almacén de confianza JRE predeterminado.

La desventaja es que cuando actualiza el JRE, su nuevo almacén de confianza no se fusiona automáticamente con el personalizado.

Quizás podría manejar este escenario si tuviera un instalador o una rutina de inicio que verifique el almacén de confianza/jdk y compruebe si hay discrepancias o actualice automáticamente el almacén de confianza. No sé qué sucede si actualiza el almacén de confianza mientras se ejecuta la aplicación.

Esta solución no es 100% elegante ni infalible, pero es simple, funciona y no requiere código.

Mr. Shiny and New 安宇 avatar May 13 '2009 17:05 Mr. Shiny and New 安宇