example - Java SSLException: el nombre de host en el certificado no coincide
java truststore (7)
He estado usando el siguiente código para conectarme a uno de los servicios de google. Este código funcionó bien en mi máquina local:
HttpClient client=new DefaultHttpClient();
HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin");
post.setEntity(new UrlEncodedFormEntity(myData));
HttpResponse response = client.execute(post);
Puse 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.
Aún así, no pude acceder a la URL, y me pregunto por qué. Así que reemplacé el código anterior por:
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
Ahora recibo un error como este:
javax.net.ssl.SSLException: el nombre de host en el certificado no coincide: <74.125.236.52>! = <www.google.com>
Supongo que esto se debe a que Google tiene múltiples 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é debería hacer ahora ? ¿Hay una solución en el nivel de Java? ¿O está totalmente en manos del chico de la red?
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");
dará como resultado el proceso de verificación del certificado que verifica 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 dará lugar a una falla (podría haberlo verificado navegando a la URL https://74.125.236.52/accounts/ClientLogin
con un navegador, y habría visto el error resultante).
Supuestamente, en aras de la seguridad, no está seguro de escribir su propio TrustManager
(y no debe comprender a menos que TrustManager
cómo escribir uno seguro), debe considerar establecer registros DNS en su centro de datos para asegurarse de que todas las búsquedas de www.google.com
se resolverá a 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.
En httpclient-4.3.3.jar, hay otro HttpClient para 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 (). Build () devolverá org.apache.http.impl.client.InternalHttpClient . Puede manejar este nombre de host en el certificado que no coincide con el problema.
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 me parece bien 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.
La preocupación es que no debemos usar ALLOW_ALL_HOSTNAME_VERIFIER.
¿Qué tal si implemento mi propio verificador de nombre de host?
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");
}
}
Probemos 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 coincide: www.rideforrainbows.org! = Stac.rt.sg O 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);
}
}
Muestra:
Anfitrión = www.rideforrainbows.org
Host SSL = www.rideforrainbows.org
Al menos tengo la lógica para comparar (Host == SSL Host) y regreso verdadero.
El código fuente anterior funciona para httpclient-4.2.3.jar y httpclient-4.3.3.jar.
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);
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í está 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 {
}
}
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();