6

It's appeared that we can't configure our java client to correctly handle SSL connection.

We can successfully connect to HTTPS URL using browser with specified client certificate and with curl tool but not with our java client or SOAP UI tool that is also java based.Even when we used the same *.p12 certificate in browser (which works fine) and in the SOAP UI tool (doesn't work).

So, We have following information:

  1. Public Client certificate (pem format)
  2. The client's private key (pem format)
  3. CA which is used by server to validate client certificate;

We converted these pem certificates to jks and to p12 and used apache http client for connection. And as it was mentioned the same certificate is working for browser but doesn't work neither for custom client or for java based for SOAP UI.

First of all I guessed that we have following problem described there:
When the server requests a client certificate (as part of the TLS handshake), it will also provide a list of trusted CA's as part of the certificate request. When the client certificate you wish to present for authentication is not signed by one of these CA's, it won't be presented at all

But it seems if we had such problem It wouldn't resolve this certificate for browser.Because browser by default doesn't allow to use incorrect (with different issuer) certificate by default.

Client I created based on following tutorial:

So, basically I have to questions:

  1. How to know whether client certificate sent at all: we used org.apache.http.conn.ssl.SSLSocketFactory probably in what place to debug;
  2. What the issue might be, probably any good reference that describes differences of browsers SSL configuration and java-based tools (probably java SSL validation more strictly)?

Update 1

We tried to use openssl s_client tool. The output showed that server certificate is as we expected, but there are no CA for client certificate validation:

No client certificate CA names sent

The syntax for openssl s_client if using connection to 443 without specification of the schema (https://). So we decided to try our url in browser without the schema but with the 443 port. Server responded with:

The plain HTTP request was sent to HTTPS port

Next our step in this investigation was to try wireshark. First we got the correct session with browser and then we tried our application. We tried openssl s_client as well.

Difference is in the Server Key Exchange packet. While browser packet has a Certificate Request, but openssl and our application has no such field in that packet. Also if browser doesn't send any certificates this field is still present in the packet.

So only browser gets a request for certificate. Any thoughts about why this is happening are appreciated.

Answers to questions in comments

  • We're using java 1.7
  • Is it initial client cert authentication
  • When we use openssl s_client -connect your.server.name:443 we get a valid server certificate back and No client certificate CA names sent. Also we get verify error:num=2:unable to get issuer certificate.
  • Server certificates we get from s_client and browser are the same
  • There are no list of CA certificates advertised No client certificate CA names sent

Update 2

After some investigation and research we manage to find out the root cause of the problem. So we have SNI (Server Name Indication) enabled on our server and our application is using apache httpclient 4.2.1. This version of client doesn't support the SNI extension and doesn't send the server_name extension in Client Hello packet. After that server doesn't advertise client to authenticate with client certificate. This issue was fixed in 4.3.2 release.

Right now we are trying to check if the 4.3.2 version of httpclient is correctly sending server_name extension.

Community
  • 1
  • 1
user1459144
  • 4,439
  • 5
  • 28
  • 35
  • Which version of Java are you using? Is your server set up with Server Name Indication (SNI)? Is it initial client cert authentication, or is it done via renegotiation? What happens when you connect with `openssl s_client -connect your.server.name:443`? Do the name in the server certificate match what you would expect? Do you get to see the list of CA certificates advertised? Could you put the relevant fragment of code in your question so that we know what you're talking about? – Bruno Jul 28 '14 at 22:46
  • @d3day "*we decided to try our url in browser without the schema but with the 443 port*" It doesn't make sense to try what you did (and you got the expected error). Could you try to address all the questions in my previous comment? Is the certificate you get via `s_client` the same as what you get in the browser? – Bruno Jul 29 '14 at 10:56
  • The "unable to get issuer certificate" error indicates a server certificate validation error. The "correct" way to fix this is to specify the trusted CA for the server cert using the "-CAfile" or "-CApath" tags for s_client, but you could also try adding "-verify 2" to your s_client command. This tells s_client to verify the server certificate chain to depth 2, but the part that is more interesting is that according to the -verify tag portion of the man page for s_client: "As a side effect the connection will never fail due to a server certificate verify failure." – juhraffe Jul 29 '14 at 13:40
  • (Server Key Exchange and Certificate Request are different messages, they're not necessarily always on the same line in Wireshark, perhaps try to expand the ones around.) If you expand the Certificate Request when you see it from the browser, is there a list under `certificate_authorities`? Does any of this work better if you also add `-servername your.server.name` to that `s_client` command? Any difference in the TLS version used (as suggested by @juhraffe)? – Bruno Jul 29 '14 at 14:31

2 Answers2

3

I'd suggest doing a packet capture (e.g., Wireshark or tcpdump) while sending your request. In Wireshark you can filter on "SSL" and the source and destination IP addresses (SSL && ip.src==X.X.X.X && ip.dst==X.X.X.X) to isolate the traffic of interest, and then look for the following sequence of SSL Handshake messages in the "Info" column (or something similar):

Client -> Server: Client Hello
Server -> Client: Server Hello, Certificate, Certificate Request, Server Hello Done
Client -> Server: Certificate, Certificate Verify, Client Key Exchange

The key things to look for are the Certificate Request (make sure the server is asking for the certificate, and check the list of trusted CAs), and then the Client -> Server Certificate message, which includes the client's certificate.

Other things you can check in the packet capture:
* The list of supported cipher suites by the client. If the server does not accept any of them the connection will fail.
* The TLS version. The client and server must support a common version for communication to take place. It appears you restricted to TLS v1.0, so check that this is supported on the other end.
* Other "Alert" messages, which indicate a fatal error or a warning.

juhraffe
  • 545
  • 6
  • 16
3

Finally the solution for this problem was found.

So preconditions for this problem to happen are:

  • Java version < 7
  • Apache httpclient version < 4.3.2
  • SNI is enabled on the server

Root cause of the problem is in the old version of httpclient was used. Clients before the version 4.3.2 are not capable of dealing with SNI (Server Name Indication) and don't send necessary information in Client Hello packet (field name: server_name). Here is a issue link.

Basically SNI is used to run multiple virtual domains on the same ip-address using different server certificates for each domain. When SNI enabled server is getting Client Hello packet without server_name then it can't determine what certificate it has to use and therefore is not advertising the client with CA list.

Solution is to use Java version >= 7 and apache httpclient version >= 4.3.2.
Also wireshark is really helpful for debugging such issues and the filter example to use with it are posted by juhraffe:

ssl && (ip.dst == ip_or_domain_of_server || ip.src == ip_or_domain_of_server)

Also while using it don't forget to make wireshark collect packets on correct interface.

d3day
  • 872
  • 8
  • 12
  • 1
    Comparing the output of `openssl s_client -connect your.server.name:port -servername your.server.name` with and without `-servername your.server.name` is also a quick way to compare the certificates you get with and without SNI. (If needed, you can pipe this through ` | openssl x509 -text -noout`. – Bruno Jul 30 '14 at 15:27