Java SSLException: hostname in certificate didn't match

匿名 (未验证) 提交于 2019-12-03 02:13:02

问题:

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 ?

回答1:

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


回答2:

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.



回答3:

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 {  } 

}



回答4:

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.



回答6:

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.



回答7:

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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!