3

I'm having this big problem and I can't find any answer on google, so I try to write here.

I'm developing a web service client. To connect to the web service, I have to create an ssl channel with client authentication and use a proxy (the call must be done from a whitelisted IP). To do this, I'm using the HttpClient 4.2.3 library with Java 1.5 (compatibility mode with 1.4), under WebSphere 6.1 environment.

Being work-related, I'm deleting all references to the actual web service I'm calling so you will find some weird variable/host/class names here and there :)

I've set up the connection like this

    // Costruzione del proxy
    Resources res = new Resources();
    String proxyHost = res.get(PROXY_HOST);
    int proxyPort = Integer.parseInt(res.get(PROXY_PORT));

    HttpHost proxy = new HttpHost(proxyHost, proxyPort);


    // Costruzione del post method
    String serviceUrl = "https://" + someHost + meth.getServiceURL();
    HttpPost post = new HttpPost(serviceUrl);
    post.addHeader("Content-Type", "text/xml");

    String soapAction = "https://" + someHost + meth.getAction();
    post.addHeader("SOAPAction", soapAction);

    AbstractHttpEntity postBody = new StringEntity(soapEnvelope);
    post.setEntity(postBody);


    // Costruzione dell'ambiente di chiamata HTTPS
    SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore, res.get(KEY_STORE_PASS_CONF), trustStore);
    Scheme httpsScheme = new Scheme("https", HTTPS_PORT, sslSocketFactory);

    Scheme httpScheme = new Scheme("http", HTTP_PORT, PlainSocketFactory.getSocketFactory());

    final SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(httpScheme);
    schemeRegistry.register(httpsScheme);

    PoolingClientConnectionManager connManager = new PoolingClientConnectionManager(schemeRegistry);

    HttpParams params = new BasicHttpParams();

    HttpClient client = new DefaultHttpClient(connManager, params);
    client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);


    // CHIAMATA A MEF
    HttpResponse resp = client.execute(post);

Keystore and Truststore have been loaded from a file

File ksFile = new File(res.get(KEY_STORE_PATH_CONF));
File tsFile = new File(res.get(TRUST_STORE_PATH_CONF));
InputStream ksInput, tsInput;

try {
    ksInput = new FileInputStream(ksFile);
    tsInput = new FileInputStream(tsFile);
} catch ... {}

KeyStore keyStore, trustStore;
try {
    String ksPassword = res.get(KEY_STORE_PASS_CONF);
    String tsPassword = res.get(TRUST_STORE_PASS_CONF);

    keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(ksInput, ksPassword.toCharArray());   

    trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(tsInput, tsPassword.toCharArray());
} catch ... {}

When I try to call the client.execute method, it gives me this exception

javax.net.ssl.SSLException: hostname in certificate didn't match: <right.host.it> != <unknown.host.it>
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java)
at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:628)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.updateSecureConnection(DefaultClientConnectionOperator.java:232)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.layerProtocol(ManagedClientConnectionImpl.java:401)
at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:842)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:649)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:480)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java)
at my.class.package.MyClass.callWs(MyWebService.java)

There are some weird things that I noticed:

  • the unknown host in the exception message is from the same domain of the right one, so it is taken necessarily from a certificate of that company
  • I've tried to debug the call, and it looks like it takes other certificates. The unknown.host.it address is taken from the CN of an x509 certificate (seen in debug), and both certificates in the keystore file contains other CNs. Moreover, I have two certificates in my keystore and when the first AbstractVerifier.verify method is called there are three certificates in the call parameters, one of them containing the unknown.host.it string in the CN
  • actually, I can't find the unknown.host.it string in any certificate in my possess

The problem is probably that I don't know how actually SSL works, so if you see any completely wrong thing in what I said it's probably the key to resolve my problem.

Can you help me in some way? Thanks a lot!

#

UPDATE 1: the day after :) Since I want to receive an answer first (the company has to test the software), I've decided to force the software to accept every hostname. I want to change this later, but for now I just want it to work. So I've added this (deprecated :) ) line to my code

    SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore, res.get(KEY_STORE_PASS_CONF), trustStore);
    sslSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

Now it passes the hostname verification (surprise surprise :) ) but still gives me a 403-Forbidden error. The service owner says I'm calling from a whitelisted IP, so the problem lies, I suppose, in the fact that the server doesn't recognize me. They say I'm not presenting any certificate.

My way to add the certificate was in the SSLSocketFactory constructor, where I pass the keystore with my certificate inside. Do I have to specificate to HttpClient that I want to self-identify to the server while handshaking?

Thanks :)

CodingMonkey
  • 143
  • 3
  • 14
  • I've found a temporary solution by activating the trustworthy hostname verifier (i.e. the SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER). I know that this is not the correct solution, but I'll have time to fix this problem. The problem is that HTTP keeps returning me a 403-forbidden error. I've called the service support and they say that I call from a whitelisted IP (so the proxy works) but the mutual authentication doesn't work for some reason. – CodingMonkey Apr 05 '13 at 08:41
  • Related: http://stackoverflow.com/q/7256955/435605 – AlikElzin-kilaka Jul 06 '16 at 16:32

1 Answers1

3

It's likely that your server uses Server Name Indication (SNI) to serve two host names on the same IP address (and port).

You can check this using openssl, by specifying the server name explicitly. For example, these two commands will connect to the same IP address but request different host names and serve different certificates:

openssl s_client -connect www.google.co.uk:443 -servername www.google.co.uk

openssl s_client -connect www.google.co.uk:443 -servername www.google.com

Java only supports SNI on the client side at the moment, and only since Java 7.

Bruno
  • 119,590
  • 31
  • 270
  • 376
  • I'm using Java 5 :) but it actually worked once... it just stopped to do so, and I'm still wondering why... – CodingMonkey Apr 05 '13 at 08:53
  • You should really upgrade. Perhaps something changed on the server? – Bruno Apr 05 '13 at 10:15
  • I agree, but... I'm not the one deciding. As all big companies, mine is *a little* not likely to upgrade technologies :) – CodingMonkey Apr 05 '13 at 13:48
  • Thank you for this. Had no idea what was causing the error. – Nufail Sep 23 '14 at 12:02
  • Starting with OpenSSL 1.1.1, the s_client tool automatically configures the servername hence the SNI header Use the -noservername switch to avoid sending hostname information in the TLS handshake. Adding this comment because it is useful while using openssl testing SNI. source: https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html – user2206366 Jul 22 '21 at 20:30