Conexión de URL de Java y HTTPS sin descargar el certificado

Este código se conecta a un sitio HTTPS y asumo que no estoy verificando el certificado. Pero ¿por qué no tengo que instalar un certificado localmente para el sitio? ¿No debería tener que instalar un certificado localmente y cargarlo para este progtwig o se descarga detrás de las cubiertas? ¿El tráfico entre el cliente y el sitio remoto todavía está encriptado en la transmisión?

import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.net.URLConnection; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class TestSSL { public static void main(String[] args) throws Exception { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; // Install the all-trusting trust manager final SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Create all-trusting host name verifier HostnameVerifier allHostsValid = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; // Install the all-trusting host verifier HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); URL url = new URL("https://www.google.com"); URLConnection con = url.openConnection(); final Reader reader = new InputStreamReader(con.getInputStream()); final BufferedReader br = new BufferedReader(reader); String line = ""; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } // End of main } // End of the class // 

La razón por la que no tiene que cargar un certificado localmente es que ha elegido explícitamente no verificar el certificado, con este administrador de confianza que confía en todos los certificados.

El tráfico seguirá encriptado, pero estás abriendo la conexión a los ataques Man-In-The-Middle: te estás comunicando secretamente con alguien, simplemente no estás seguro de si es el servidor que esperas o un posible atacante.

Si su certificado de servidor proviene de una CA conocida, parte del paquete predeterminado de certificados de CA incluidos con el JRE (generalmente cacerts file, consulte la Guía de referencia JSSE), puede usar el administrador de confianza predeterminado, no tiene que establecer cualquier cosa aquí.

Si tiene un certificado específico (autofirmado o de su CA), puede usar el administrador de confianza predeterminado o tal vez uno inicializado con un almacén de confianza específico, pero tendrá que importar el certificado explícitamente en su almacén de confianza (después verificación), como se describe en esta respuesta . Usted también podría estar interesado en esta respuesta .

Pero ¿por qué no tengo que instalar un certificado localmente para el sitio?

Bueno, el código que está utilizando está explícitamente diseñado para aceptar el certificado sin hacer ningún tipo de comprobación. Esta no es una buena práctica … pero si eso es lo que quieres hacer, entonces (obviamente) no hay necesidad de instalar un certificado que tu código ignora explícitamente.

¿No debería tener que instalar un certificado localmente y cargarlo para este progtwig o se descarga detrás de las cubiertas?

No y no. Véase más arriba.

¿El tráfico entre el cliente y el sitio remoto todavía está encriptado en la transmisión?

Sí lo es. Sin embargo, el problema es que, dado que le ha dicho que confíe en el certificado del servidor sin hacer ningún control, no sabe si está hablando con el servidor real o con otro sitio que se hace pasar por el servidor real. Si esto es un problema depende de las circunstancias.


Si usamos el navegador como ejemplo, generalmente un navegador no le pide al usuario que instale explícitamente un certificado para cada sitio SSL visitado.

El navegador tiene un conjunto de certificados raíz de confianza preinstalados. La mayoría de las veces, cuando visita un sitio “https”, el navegador puede verificar que el certificado del sitio sea (en última instancia, a través de la cadena de certificados) protegido por uno de esos certificados confiables. Si el navegador no reconoce el certificado al inicio de la cadena como un certificado de confianza (o si los certificados están desactualizados o son inválidos / inapropiados), aparecerá una advertencia.

Java funciona de la misma manera. El almacén de claves de la JVM tiene un conjunto de certificados confiables, y el mismo proceso se usa para verificar que el certificado esté protegido por un certificado de confianza.

¿La aplicación java https client admite algún tipo de mecanismo para descargar información del certificado automáticamente?

No. Permitir que las aplicaciones descarguen certificados de lugares aleatorios e instalarlos (de confianza) en el almacén de claves del sistema sería un agujero de seguridad.

Utilice el último X509ExtendedTrustManager en lugar de X509Certificate como se aconseja aquí: java.security.cert.CertificateException: Certificados no se ajusta a las restricciones de algoritmo

  package javaapplication8; import java.io.InputStream; import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509ExtendedTrustManager; /** * * @author hoshantm */ public class JavaApplication8 { /** * @param args the command line arguments * @throws java.lang.Exception */ public static void main(String[] args) throws Exception { /* * fix for * 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 */ TrustManager[] trustAllCerts = new TrustManager[]{ new X509ExtendedTrustManager() { @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { } @Override public void checkClientTrusted(X509Certificate[] xcs, String string, Socket socket) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] xcs, String string, Socket socket) throws CertificateException { } @Override public void checkClientTrusted(X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException { } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Create all-trusting host name verifier HostnameVerifier allHostsValid = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }; // Install the all-trusting host verifier HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); /* * end of the fix */ URL url = new URL("http://sofes.miximages.com/java/panel.bmp"); URLConnection con = url.openConnection(); //Reader reader = new ImageStreamReader(con.getInputStream()); InputStream is = new URL(url.toString()).openStream(); // Whatever you may want to do next } } 

Conexión de URL de Java y HTTPS sin descargar el certificado

Si realmente desea evitar la descarga del certificado del servidor, utilice un protocolo anónimo como Anonymous Diffie-Hellman (ADH). El certificado del servidor no se envía con ADH y amigos.

Selecciona un protocolo anónimo con setEnabledCipherSuites . Puede ver la lista de suites de cifrado disponibles con getEnabledCipherSuites .

Relacionado: es por eso que debes llamar a SSL_get_peer_certificate en OpenSSL. Obtendrá un X509_V_OK con un protocolo anónimo, y así es como se verifica para ver si se utilizó un certificado en el intercambio.

Pero como declararon Bruno y Stephed C, es una mala idea evitar los controles o usar un protocolo anónimo.


Otra opción es usar TLS-PSK o TLS-SRP. No requieren certificados de servidor tampoco. (Pero no creo que puedas usarlos).

El problema es que debe preprovisionarse en el sistema porque TLS-PSK es Pres-shared Secret y TLS-SRP es Secure Remote Password . La autenticación es mutua en lugar de solo servidor.

En este caso, la autenticación mutua es proporcionada por una propiedad que ambas partes conocen el secreto compartido y llegan al mismo secreto premaster; o uno (o ambos) no lo hace y la configuración del canal falla. Cada parte demuestra que el conocimiento del secreto es la parte “mutua”.

Finalmente, TLS-PSK o TLS-SRP no hacen tonterías, como copiar la contraseña del usuario como en una aplicación web usando HTTP (o sobre HTTPS). Es por eso que dije que cada parte prueba el conocimiento del secreto …

Una solución java simple, pero no pura, es desmantelar a curl desde java, que le da un control total sobre cómo se realiza la solicitud. Si solo hace esto por algo simple, esto le permite ignorar errores de certificado a veces, utilizando este método. Este ejemplo muestra cómo realizar una solicitud contra un servidor seguro con un certificado válido o no válido, pasar una cookie y obtener el resultado utilizando curl desde java.

 import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; public class MyTestClass { public static void main(String[] args) { String url = "https://www.google.com"; String sessionId = "faf419e0-45a5-47b3-96d1-8c62b2a3b558"; // Curl options are: // -k: ignore certificate errors // -L: follow redirects // -s: non verbose // -H: add a http header String[] command = { "curl", "-k", "-L", "-s", "-H", "Cookie: MYSESSIONCOOKIENAME=" + sessionId + ";", "-H", "Accept:*/*", url }; String output = executeShellCmd(command, "/tmp", true, true); System.out.println(output); } public String executeShellCmd(String[] command, String workingFolder, boolean wantsOutput, boolean wantsErrors) { try { ProcessBuilder pb = new ProcessBuilder(command); File wf = new File(workingFolder); pb.directory(wf); Process proc = pb.start(); BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream())); StringBuffer sb = new StringBuffer(); String newLine = System.getProperty("line.separator"); String s; // read stdout from the command if (wantsOutput) { while ((s = stdInput.readLine()) != null) { sb.append(s); sb.append(newLine); } } // read any errors from the attempted command if (wantsErrors) { while ((s = stdError.readLine()) != null) { sb.append(s); sb.append(newLine); } } String result = sb.toString(); return result; } catch (IOException e) { throw new RuntimeException("Problem occurred:", e); } } }