2011-08-31 22 views
35

He estado usando el siguiente código para conectarme a uno de los servicios de google. Este código funcionaba bien en mi máquina local:Java SSLException: nombre de host en el certificado no coincide

HttpClient client=new DefaultHttpClient(); 
HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin"); 
post.setEntity(new UrlEncodedFormEntity(myData)); 
HttpResponse response = client.execute(post); 

que poner este código en un entorno de producción, que había bloqueado Google.com. A petición, permitieron la comunicación con el servidor de Google al permitirme acceder a una IP: 74.125.236.52, que es una de las direcciones IP de Google. Edité mi archivo hosts para agregar esta entrada también.

Todavía no pude acceder a la URL, lo que me pregunto por qué. Así que sustituye el código anterior con:

HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin"); 

Ahora me sale un error como este:

javax.net.ssl.SSLException: nombre de host en el certificado no coincide: <74.125.236.52>! = <www.google.com>

supongo que esto se debe a que Google tiene varias direcciones IP. No puedo pedirle al administrador de la red que me permita acceder a todas esas direcciones IP; es posible que ni siquiera obtenga esta lista completa.

¿Qué debo hacer ahora? ¿Hay una solución en el nivel de Java? ¿O está totalmente en manos del chico de la red?

+0

El certificado SSL por lo general viene con un dominio específico * Nombre * al que se aplica, y si ese nombre no coincide con el Al solicitar el nombre, su cliente le advierte que la conexión no está autenticada correctamente.Puede verificar si su cliente le permite especificar una anulación de certificado explícita para la conexión. –

+3

El nombre de host en la URL debe coincidir con el nombre de host en el certificado. Deberías intentar hacerlo funcionar con el archivo hosts. De lo contrario, puede anular la rutina de validación de certificados para aceptar también google.com para 74.125.236.52 (¡no lo haga demasiado indulgente!). – Thilo

+0

@Thilo: ¿Cómo anular la rutina de validación? – WinOrWin

Respuesta

4

Gracias Vineet Reynolds. El enlace que proporcionó contenía muchos comentarios de los usuarios, uno de los cuales probé con desesperación y me ayudó. Agregué este método:

// Do not do this in production!!! 
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){ 
    public boolean verify(String string,SSLSession ssls) { 
     return true; 
    } 
}); 

Esto parece estar bien para mí ahora, aunque sé que esta solución es temporal. Estoy trabajando con la gente de la red para identificar por qué mi archivo de hosts está siendo ignorado.

+12

Esto es realmente una mala idea. Por cierto, es posible que desee verificar el orden de búsqueda en '/ etc/nsswitch.conf' y ver si el archivo hosts se ignora en la búsqueda. –

+0

buena idea si está haciendo algo como ... if (url.startsWith ("https: // localhost")) deshabilitar ssl –

+12

Por favor, no deje esto como la respuesta aceptada ya que es una idea ridículamente mala, temporal o no. En este contexto, podría estar bien, pero es poco probable que se pueda generalizar a otras personas que encuentren esta pregunta debido a problemas similares. Es mejor que no uses SSL, al menos en ese caso no estás bromeando con nadie sobre seguridad. Tal vez edite la pregunta una vez que de manera concluyente y real resuelva el problema (después de las conversaciones con su gente de la red, etc.) y solo la acepte en ese momento. – roguesys

22

El proceso de verificación del certificado siempre verificará el nombre DNS del certificado presentado por el servidor, con el nombre de host del servidor en la URL utilizada por el cliente.

El siguiente código

HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin"); 

resultará en el proceso de verificación de certificado verificar si el nombre común del certificado emitido por el servidor, es decir www.google.com coincide con el nombre de host es decir 74.125.236.52. Obviamente, esto tiene como resultado la falla (puede haber verificado esto al navegar a la URL https://74.125.236.52/accounts/ClientLogin con un navegador, y ha visto el error resultante).

Supuestamente, en aras de la seguridad, duda si debe escribir su propio TrustManager (y no debe comprender a menos que sepa cómo escribir uno seguro), debe buscar establecer registros DNS en su centro de datos para garantizar que todas las búsquedas de www.google.com se resolverán en 74.125.236.52; esto debe hacerse en sus servidores DNS locales o en el archivo hosts de su sistema operativo; es posible que necesite agregar entradas a otros dominios también. Huelga decir que deberá asegurarse de que esto sea coherente con los registros devueltos por su ISP.

+0

Según usted, la edición del archivo hosts debería resolver el problema ... ¡Todavía me pregunto qué más hacer! Por cierto, ¿puede TrustManager ayudarme en este caso? ¿Puede indicarme un lugar donde debería aprender a usar esto correctamente? Gracias. – WinOrWin

+0

@WinOrWin, [este sitio] (http://exampledepot.com/egs/javax.net.ssl/TrustAll.html) contiene un ejemplo de un TrustManager. Sin embargo, no recomendaría usar el mismo bit de código en producción, ya que TrustManager no verifica en absoluto el certificado del servidor. Lo que debe hacer, es incorporar cheques para verificar que el servidor presentado por '74.125.236.52' se emite a' www.google.com'. Por supuesto, esto no es tan preferido como corregir su búsqueda de DNS; Sugiero que obtengan un volcado de Wireshark para ver qué está fallando y que se ignore el archivo 'hosts'. –

+3

Esto no es un problema de TrustManager. Es un problema HTTPS HostnameVerifier. La solución de DNS lo resolverá. Ningún TrustManager puede resolverlo. – EJP

25

También puede intentar establecer un HostnameVerifier como se describe here. Esto funcionó para mí para evitar este error.

// Do not do this in production!!! 
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; 

DefaultHttpClient client = new DefaultHttpClient(); 

SchemeRegistry registry = new SchemeRegistry(); 
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); 
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); 
registry.register(new Scheme("https", socketFactory, 443)); 
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry); 
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams()); 

// Set verifier  
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 

// Example send http request 
final String url = "https://encrypted.google.com/"; 
HttpPost httpPost = new HttpPost(url); 
HttpResponse response = httpClient.execute(httpPost); 
+3

El uso de este verificador de nombre de host generalmente es una mala idea (vea [esta pregunta] (http://security.stackexchange.com)/q/22965/2435)). – Bruno

+0

Recursos obsoletos para Java 1.8 – nabsATX

+0

¿por qué lo configura dos veces? – pedr0

2

La preocupación es que no debemos usar ALLOW_ALL_HOSTNAME_VERIFIER.

¿Qué tal si implemento mi propio verificador de nombre de host? prueba

class MyHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier 
{ 
    @Override 
    public boolean verify(String host, SSLSession session) { 
     String sslHost = session.getPeerHost(); 
     System.out.println("Host=" + host); 
     System.out.println("SSL Host=" + sslHost);  
     if (host.equals(sslHost)) { 
      return true; 
     } else { 
      return false; 
     } 
    } 

    @Override 
    public void verify(String host, SSLSocket ssl) throws IOException { 
     String sslHost = ssl.getInetAddress().getHostName(); 
     System.out.println("Host=" + host); 
     System.out.println("SSL Host=" + sslHost);  
     if (host.equals(sslHost)) { 
      return; 
     } else { 
      throw new IOException("hostname in certificate didn't match: " + host + " != " + sslHost); 
     } 
    } 

    @Override 
    public void verify(String host, X509Certificate cert) throws SSLException { 
     throw new SSLException("Hostname verification 1 not implemented"); 
    } 

    @Override 
    public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { 
     throw new SSLException("Hostname verification 2 not implemented"); 
    } 
} 

Vamos contra https://www.rideforrainbows.org/ que está alojado en un servidor compartido.

public static void main (String[] args) throws Exception { 
    //org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); 
    //sf.setHostnameVerifier(new MyHostnameVerifier()); 
    //org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf); 

    org.apache.http.client.HttpClient client = new DefaultHttpClient(); 
    //client.getConnectionManager().getSchemeRegistry().register(sch); 
    org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/"); 
    org.apache.http.HttpResponse response = client.execute(post); 
    java.io.InputStream is = response.getEntity().getContent(); 
    java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is)); 
    String line; 
    while ((line = rd.readLine()) != null) { 
     System.out.println(line); 
    } 
} 

SSLException:

Excepción en el hilo "principal" javax.net.ssl.SSLException: nombre de host en el certificado no ha producido: www.rideforrainbows.org = O stac.rt.sg stac.rt.sg O www.stac.rt.sg
en org.apache.http.conn.ssl.AbstractVerifier.verify (AbstractVerifier.java:231)
...

hacer con MyHostnameVerifier :

public static void main (String[] args) throws Exception { 
    org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); 
    sf.setHostnameVerifier(new MyHostnameVerifier()); 
    org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf); 

    org.apache.http.client.HttpClient client = new DefaultHttpClient(); 
    client.getConnectionManager().getSchemeRegistry().register(sch); 
    org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/"); 
    org.apache.http.HttpResponse response = client.execute(post); 
    java.io.InputStream is = response.getEntity().getContent(); 
    java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is)); 
    String line; 
    while ((line = rd.readLine()) != null) { 
     System.out.println(line); 
    } 
} 

Shows:

Host = www.rideforrainbows.org
Host SSL = www.rideforrainbows.org

por lo menos tengo la lógica de comparar (Host == SSL Host) y devuelve verdadero.

El código fuente anterior funciona para httpclient-4.2.3.jar y httpclient-4.3.3.jar.

+1

Su implementación solo realiza una búsqueda DNS inversa, no verifica nada del certificado. Por lo tanto, es inseguro. – Bruno

+0

Esto resolvió mi problema – Jxadro

4

En httpclient-4.3.3.jar, hay otra HttpClient de usar:

public static void main (String[] args) throws Exception { 
    // org.apache.http.client.HttpClient client = new DefaultHttpClient(); 
    org.apache.http.client.HttpClient client = HttpClientBuilder.create().build(); 
    System.out.println("HttpClient = " + client.getClass().toString()); 
    org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/"); 
    org.apache.http.HttpResponse response = client.execute(post); 
    java.io.InputStream is = response.getEntity().getContent(); 
    java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is)); 
    String line; 
    while ((line = rd.readLine()) != null) { 
     System.out.println(line); 
    } 
} 

Este HttpClientBuilder.create() construcción() volverá org.apache.http.impl.. client.InternalHttpClient. Puede manejar el nombre de host en el certificado que no coincide con el problema.

+0

Esta respuesta se puede mejorar proporcionando el enlace para descargar Bibliotecas HTTP. –

+0

¿Cómo resolverá esto la respuesta? –

+0

Además, ¿está relacionado con https://issues.apache.org/jira/browse/HTTPCLIENT-1119? –

7

Un enfoque más limpio (solo para el entorno de prueba) en httpcliet4.3.3 es el siguiente.

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 
+0

Estas clases no están disponibles –

13

Tuve un problema similar. Estaba usando DefaultHttpClient de Android. He leído que HttpsURLConnection puede manejar este tipo de excepción. Así que creé un HostnameVerifier personalizado que usa el verificador de HttpsURLConnection. También envolví la implementación con HttpClient personalizado.

public class CustomHttpClient extends DefaultHttpClient { 

public CustomHttpClient() { 
    super(); 
    SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); 
    socketFactory.setHostnameVerifier(new CustomHostnameVerifier()); 
    Scheme scheme = (new Scheme("https", socketFactory, 443)); 
    getConnectionManager().getSchemeRegistry().register(scheme); 
} 

Aquí es la clase CustomHostnameVerifier:

public class CustomHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier { 

@Override 
public boolean verify(String host, SSLSession session) { 
    HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); 
    return hv.verify(host, session); 
} 

@Override 
public void verify(String host, SSLSocket ssl) throws IOException { 
} 

@Override 
public void verify(String host, X509Certificate cert) throws SSLException { 

} 

@Override 
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { 

} 

}

+0

Me salvó el día :) ¡¡Gracias !! –

+0

¿Quiere saber una cosa, es una especie de omisión/pasando el certificado SSL? –

+1

Todavía estamos verificando el URL a través del verificador HttpsURLConnection, por lo que no hay un bypass SSL. – granko87

Cuestiones relacionadas