web-dev-qa-db-fra.com

Java SSLException: le nom d'hôte dans le certificat ne correspond pas

J'utilise le code suivant pour me connecter à l'un des services de Google. Ce code a bien fonctionné sur ma machine locale:

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

J'ai mis ce code dans un environnement de production qui bloquait Google.com. Sur demande, ils ont autorisé la communication avec le serveur Google en me permettant d'accéder à une adresse IP: 74.125.236.52 - qui est l'une des adresses IP de Google. J'ai édité mon fichier hosts pour ajouter cette entrée aussi.

Je ne pouvais toujours pas accéder à l'URL, je me demande pourquoi. J'ai donc remplacé le code ci-dessus par:

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

Maintenant, je reçois une erreur comme celle-ci:

javax.net.ssl.SSLException: le nom d'hôte dans le certificat ne correspond pas: <74.125.236.52>! = <www.google.com>

J'imagine que c'est parce que Google a plusieurs adresses IP. Je ne peux pas demander à l'administrateur réseau de me permettre d'accéder à toutes ces adresses IP. Il est même possible que je ne reçoive pas toute cette liste.

Qu'est-ce que je devrais faire maintenant ? Existe-t-il une solution de contournement au niveau Java? Ou est-ce totalement entre les mains du responsable réseau?

39
WinOrWin

Merci Vineet Reynolds. Le lien que vous avez fourni contenait de nombreux commentaires d'utilisateurs, dont l'un que j'ai essayé en désespoir de cause et qui m'a aidé. J'ai ajouté cette méthode:

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

Cela me semble correct maintenant, même si je sais que cette solution est temporaire. Je travaille avec les personnes du réseau pour identifier la raison pour laquelle mon fichier hosts est ignoré.

5
WinOrWin

Vous pouvez également essayer de définir un HostnameVerifier comme décrit ici . Cela a fonctionné pour moi pour éviter cette erreur.

// 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);
27
H6.

Le processus de vérification du certificat vérifiera toujours le nom DNS du certificat présenté par le serveur, avec le nom d'hôte du serveur dans l'URL utilisée par le client.

Le code suivant

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

le processus de vérification du certificat vérifie si le nom usuel du certificat émis par le serveur, c.-à-d. www.google.com correspond au nom d’hôte i.e. 74.125.236.52. Évidemment, cela entraînera probablement un échec (vous auriez pu le vérifier en allant sur l’URL https://74.125.236.52/accounts/ClientLogin avec un navigateur et vu l’erreur qui en résulte vous-même).

Soi-disant, pour des raisons de sécurité, vous hésitez à écrire votre propre TrustManager (et vous ne devez pas comprendre comment écrire un code sécurisé), vous devez envisager d'établir des enregistrements DNS dans votre centre de données pour: s'assurer que toutes les recherches sur www.google.com va se résoudre à 74.125.236.52; cela devrait être fait soit dans vos serveurs DNS locaux, soit dans le fichier hosts de votre système d'exploitation; vous devrez peut-être également ajouter des entrées à d'autres domaines. Inutile de dire que vous devrez vous assurer que cela est conforme aux enregistrements renvoyés par votre fournisseur de services Internet.

22
Vineet Reynolds

J'ai eu le même problème. J'utilisais DefaultHttpClient d'Android. J'ai lu que HttpsURLConnection peut gérer ce type d'exception. J'ai donc créé HostnameVerifier personnalisé qui utilise le vérificateur de HttpsURLConnection. J'ai également intégré l'implémentation à HttpClient personnalisé.

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);
}

Voici la classe 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 {

}

}

13
granko87

Une approche plus propre (uniquement pour l’environnement de test) dans httpcliet4.3.3 est la suivante.

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

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
8
Rishitesh Mishra

Dans httpclient-4.3.3.jar, il existe un autre HttpClient à utiliser:

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);
    }
}

Cette HttpClientBuilder.create (). Build () retournera org.Apache.http.impl.client.InternalHttpClient. Il peut gérer le problème suivant le nom d’hôte du certificat ne correspond pas.

5
oraclesoon

Le problème est que nous ne devrions pas utiliser ALLOW_ALL_HOSTNAME_VERIFIER.

Pourquoi ne pas implémenter mon propre vérificateur de nom d'hôte?

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");
    }
}

Testons contre https://www.rideforrainbows.org/ qui est hébergé sur un serveur partagé.

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:

Exception dans le fil "principal" javax.net.ssl.SSLException: le nom d'hôte dans le certificat ne correspond pas: www.rideforrainbows.org! = Stac.rt.sg OR stac.rt.sg = OR www.stac.rt.sg
à org.Apache.http.conn.ssl.AbstractVerifier.verify (AbstractVerifier.Java:231)
...

Faites avec 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);
    }
}

Spectacles:

Hôte = www.rideforrainbows.org
Hôte SSL = www.rideforrainbows.org

Au moins, j’ai la logique pour comparer (hôte == hôte SSL) et renvoyer true.

Le code source ci-dessus fonctionne pour httpclient-4.2.3.jar et httpclient-4.3.3.jar.

3
oraclesoon