41

I have been using the following code to connect to one of google's service. This code worked fine on my local machine :

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

I put this code in a production environment, which had blocked Google.com. On request, they allowed communication with Google server by allowing me to accessing an IP : 74.125.236.52 - which is one of Google's IPs. I edited my hosts file to add this entry too.

Still I could not access the URL, which I wonder why. So I replaced the above code with :

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

Now I get an error like this :

javax.net.ssl.SSLException: hostname in certificate didn't match: <74.125.236.52> != <www.google.com>

I guess this is because Google has multiple IPs. I cant ask the network admin to allow me access to all those IPs - I may not even get this entire list.

What should I do now ? Is there a workaround at Java level ? Or is it totally in hands of the network guy ?

Mike Tunnicliffe
  • 10,674
  • 3
  • 31
  • 46
WinOrWin
  • 2,107
  • 3
  • 19
  • 25
  • The SSL certificate usually comes with a specific domain *name* to which it applies, and if that name doesn't match the requesting name, your client warns you that the connection is not correctly authenticated. You could check if your client lets you specify an explicit certificate override for the connection. – Kerrek SB Aug 31 '11 at 12:40
  • 4
    The hostname in the URL must match the hostname in the certificate. You should try get it working with the hosts file. If not, you can override the certificate validation routine to also accept google.com for 74.125.236.52 (don't make it too lenient!). – Thilo Aug 31 '11 at 12:40
  • @Thilo : How to override the validation routine ? – WinOrWin Aug 31 '11 at 13:07
  • Re: validation routine: http://download.oracle.com/javase/1.4.2/docs/api/javax/net/ssl/X509TrustManager.html – Thilo Aug 31 '11 at 13:18
  • It gets worse with Apache HttpClient 4.4! It uses PublicSuffix.org to verify hostnames and rejects most requests to popular domains like `*.googleapis.com`. – albogdano Mar 18 '15 at 12:26

9 Answers9

28

You can also try to set a HostnameVerifier as described here. This worked for me to avoid this 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);
Community
  • 1
  • 1
disco crazy
  • 31,313
  • 12
  • 80
  • 83
22

The certificate verification process will always verify the DNS name of the certificate presented by the server, with the hostname of the server in the URL used by the client.

The following code

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

will result in the certificate verification process verifying whether the common name of the certificate issued by the server, i.e. www.google.com matches the hostname i.e. 74.125.236.52. Obviously, this is bound to result in failure (you could have verified this by browsing to the URL https://74.125.236.52/accounts/ClientLogin with a browser, and seen the resulting error yourself).

Supposedly, for the sake of security, you are hesitant to write your own TrustManager (and you musn't unless you understand how to write a secure one), you ought to look at establishing DNS records in your datacenter to ensure that all lookups to www.google.com will resolve to 74.125.236.52; this ought to be done either in your local DNS servers or in the hosts file of your OS; you might need to add entries to other domains as well. Needless to say, you will need to ensure that this is consistent with the records returned by your ISP.

jww
  • 97,681
  • 90
  • 411
  • 885
Vineet Reynolds
  • 76,006
  • 17
  • 150
  • 174
  • As per you, editing the hosts file should solve the problem...I still wonder what else to do ! BTW, can TrustManager help me in this case ? Can you point me to a place where I should learn how to use this properly ? Thanks. – WinOrWin Aug 31 '11 at 13:24
  • @WinOrWin, [this site](http://exampledepot.com/egs/javax.net.ssl/TrustAll.html) contains an example of a TrustManager. However, I wouldn't recommend using the same bit of code in production, for the TrustManager doesn't verify the server's certificate at all. What you ought to be doing, is to incorporate checks to verify that the server presented by `74.125.236.52` is issued to `www.google.com`. This is of course, not as preferred as getting your DNS lookup corrected; I would suggest getting a Wireshark dump to see what is going wrong resulting in the `hosts` file being ignored. – Vineet Reynolds Aug 31 '11 at 13:48
  • 3
    This isn't a TrustManager issue. It is an HTTPS HostnameVerifier issue. The DNS solution will solve it. No TrustManager can possibly solve it. – user207421 Sep 01 '11 at 01:09
  • 1
    "*The certificate verification process will always verify the common name of the certificate [...]*". Actually, that's not correct, it's not always the CN, especially when using an IP address (see [this question](http://stackoverflow.com/a/8444863/372643)). – Bruno Jun 05 '13 at 12:40
13

I had similar problem. I was using Android's DefaultHttpClient. I have read that HttpsURLConnection can handle this kind of exception. So I created custom HostnameVerifier which uses the verifier from HttpsURLConnection. I also wrapped the implementation to custom HttpClient.

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

Here is the CustomHostnameVerifier class:

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 {

}

}

granko87
  • 211
  • 2
  • 5
10

A cleaner approach ( only for test environment) in httpcliet4.3.3 is as follows.

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

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

In httpclient-4.3.3.jar, there is another HttpClient to use:

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

This HttpClientBuilder.create().build() will return org.apache.http.impl.client.InternalHttpClient. It can handle the this hostname in certificate didn't match issue.

oraclesoon
  • 799
  • 8
  • 12
5

Thanks Vineet Reynolds. The link you provided held a lot of user comments - one of which I tried in desperation and it helped. I added this method :

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

This seems fine for me now, though I know this solution is temporary. I am working with the network people to identify why my hosts file is being ignored.

jww
  • 97,681
  • 90
  • 411
  • 885
WinOrWin
  • 2,107
  • 3
  • 19
  • 25
  • 12
    This is actually a bad idea. By the way, you might want to verify the lookup order in `/etc/nsswitch.conf` and see if the hosts file is being ignored in the lookup. – Vineet Reynolds Sep 02 '11 at 08:25
  • good idea if you are doing something like... if (url.startsWith("https://localhost")) disable ssl – Nicholas DiPiazza Nov 29 '12 at 20:19
  • 12
    Please don't leave this as the accepted answer as it is a ridiculously bad idea, temporary or not. It might be fine for you in this context, but it's unlikely to be generalizable to other people that find this question due to similar issues. You might as well not use SSL, at least in that case you're not kidding anyone about security. Perhaps edit the question once you conclusively and actually _resolve_ the issue (after talks with your Network people etc) and only accept it then. – roguesys Dec 12 '13 at 19:49
  • 12
    You just effectively removed the lock from the front door of your house because you didn't understand how to use a key. – Martin Konecny Apr 07 '14 at 21:06
  • 1
    This is an excellent solution for me in my unit tests - wiremock defaults to a self-signed certificate and the above code is in my `setUp()` to ensure nothing breaks during testing on various build hosts. – mvreijn Aug 09 '16 at 19:29
  • for the records: This is identical to using `org.apache.http.conn.ssl.NoopHostnameVerifier` – crusy May 18 '17 at 08:18
3

The concern is we should not use ALLOW_ALL_HOSTNAME_VERIFIER.

How about I implement my own hostname verifier?

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

Let's test against https://www.rideforrainbows.org/ which is hosted on a shared server.

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 in thread "main" javax.net.ssl.SSLException: hostname in certificate didn't match: www.rideforrainbows.org != stac.rt.sg OR stac.rt.sg OR www.stac.rt.sg
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)
...

Do with 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
SSL Host=www.rideforrainbows.org

At least I have the logic to compare (Host == SSL Host) and return true.

The above source code is working for httpclient-4.2.3.jar and httpclient-4.3.3.jar.

oraclesoon
  • 799
  • 8
  • 12
  • 1
    Your implementation is only doing a reverse DNS lookup, it's not checking anything from the certificate. It's therefore insecure. – Bruno May 21 '14 at 10:18
0

Updating the java version from 1.8.0_40 to 1.8.0_181 resolved the issue.

0
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
  • Thank you for posting an answer to this question! Code-only answers are discouraged on Stack Overflow, because it can be difficult for the original poster (or future readers) to understand the logic behind them. Please, edit your answer and include an explanation of your code so that others can benefit from it. Thanks! – Maximillian Laumeister Oct 13 '21 at 22:16