2

I am confused as to where exactly I need to include client certificate. Now, my first issue is that I don't trust the server. I tried using default Java keystore file (cacerts) which has both Thawte and Digicert in it, and those are the root authorities of the server I'm trying to communicate with. I set that cacerts file as keystore using System.setProperty("javax.net.ssl.keyStore", "..."), it didn't work, I set it as truststore, it didn't work. I still got

unable to find valid certification path to requested target

So I sorted that out temporarily by using AlwaysTrustSSLConnectionFactory().

Now the issue is that the server doesn't trust me. I have a client certificate and I tried adding it to both keystore and truststore, but regardless of what I do, after ServerHelloDone I get

Warning: no suitable certificate found - continuing without client authentication

in Java's SSL debug messages and a handshake failure after secret and key messages. Here is the end of my log:

http-bio-8080-exec-3, WRITE: TLSv1.2 Handshake, length = 40
http-bio-8080-exec-3, READ: TLSv1.2 Alert, length = 2
http-bio-8080-exec-3, RECV TLSv1.2 ALERT:  fatal, handshake_failure
%% Invalidated:  [Session-7, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
http-bio-8080-exec-3, called closeSocket()
http-bio-8080-exec-3, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Unknown Source)
    at sun.security.ssl.Alerts.getSSLException(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.recvAlert(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(Unknown Source)

Here is my current code:

System.setProperty("javax.net.ssl.keyStore", "C:/Users/Lovro/Desktop/certs/api/keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "pass");

URL url = new URL(urlRequest);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(new AlwaysTrustSSLContextFactory());
conn.connect();

I recieved my client certificate from API developers in format .p12, so I converted it to .crt and added that to keystore with Keytool. Does anyone know what could be the issue and is my handshake failing because I have not included client certificate properly or if I didn't add it to keystore properly? As far as I understand, truststore needs to contain public keys of trusted root authorities and keystore should have my client certificate. Is this correct? How do I achieve this?

Any suggestion is welcome, I am new to TLS/HTTPS and have no idea what am I doing.

lovrodoe
  • 473
  • 8
  • 18
  • I forgot to say that I tried using client certificate in `Postman` and it works. The certificate is valid. – lovrodoe Sep 25 '18 at 08:01
  • 1
    _no suitable certificate found - continuing without client authentication_ means that the client could not found a matching certificate in the keystore for the CA certificate list sent by server. Then the server probably shutdown the connection because the client certificate has not been sent. Make sure that **all certificates and the private key** of the .p12 are actually imported into the JKS, or set the .p12 directly as the keystore in `javax.net.ssl.keyStore`. If p12 does not work either, get the CA list sent by the server and compare it to the issuer of the p12 certificate – pedrofb Sep 25 '18 at 12:18
  • Thanks Pedro, the issue was that I imported .crt to my keystore created from the original .p12 client certificate, so I effectively only added the public key to my store. Rookie mistake. – lovrodoe Sep 25 '18 at 14:55

2 Answers2

3

I recieved my client certificate from API developers in format .p12, so I converted it to .crt and added that to keystore with Keytool

This is where you went wrong. Converting it to .crt extracts the public certificate. What you need to do is convert the .p12 file to a java keystore. There are many examples on the net. See this SO answer for how.

To confirm that it's worked, run keytool -list -keystore <yourkeystore>.jks and check that you have a PrivateKeyEntry in there.

While you're checking things, add the -v flag to the keytool -list command and check that the Issuer field is CN=test, O=test because we can see from your log file that your server requires a client certificate to be issued by that authority.

Also check that your JDK is configured with the Unlimited Strength Jurisdiction Policy Files because the cipher you're being asked to use requires it.

Andy Brown
  • 11,766
  • 2
  • 42
  • 61
  • Thanks, but it appears that the issue lies somewhere else, I'm assuming in the way that I include these cert stores because I'm still getting `Warning: no suitable certificate found - continuing without client authentication`. Just found out that I didn't even have to convert .p12 to .jks because they're both keystores. Using pkcs12 file nets me the same results as using .jks. – lovrodoe Sep 25 '18 at 09:05
  • @lovrodoe I don't recommend using the .p12 without conversion to jks. We've had people make that mistake and it affects the ability of java code to extract the certificate part from the private key entry. Have you verified the issuer is correct and matches the single issuer your server is requesting? – Andy Brown Sep 25 '18 at 12:53
  • ...and definitely drop your `setSSLSocketFactory()` call if you haven't already. If you run into problems with a hostname mismatch then use `setHostnameVerifier` with a trivial `return true` implementation. – Andy Brown Sep 25 '18 at 13:01
  • Cheers Andy, thanks for the replies. You've actually set me on path to resolve this, so I'll pick your answer. I'm not setting the SSL socket factory anymore, though I am using the original .p12 keystore they sent me. Could you clarify the part about it affecting the ability of Java code to extract the certificate part? Does it perform worse than it would with Java's proprietary jks? I haven't noticed any major issues using it today, at least it works. – lovrodoe Sep 25 '18 at 14:42
  • 1
    I phrased that badly. There's a lot of code out there, including a product we use, that does `KeyStore.getInstance("JKS")`, calls `load()` and then manipulates the keystore content. Obviously this fails when the format isn't JKS. Just something to be aware of in the future because there's a fair bit of code out there that assumes everything java is JKS. – Andy Brown Sep 25 '18 at 15:29
-1

From the log, it seems that TLS cipher TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 is invalidated for TLS client. You may need to check which cipher list client supports. If the cipher TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 is not included in the cipher list, you may need to add support for it.

http-bio-8080-exec-3, READ: TLSv1.2 Alert, length = 2
http-bio-8080-exec-3, RECV TLSv1.2 ALERT: fatal, handshake_failure
%% Invalidated: [Session-7, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]

leiyc
  • 903
  • 11
  • 23
  • 1
    According to [ssllabs](https://ssllabs.com), `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` is server's favorite cipher. It's listed as first in `Cipher` Suites section. The thing is, my client sends list of ciphers it supports in `ClientHello` and this is the one that server picked in ServerHello. From log: `*** ServerHello, TLSv1.2 RandomCookie: GMT: -752143001 bytes = Session ID: Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` – lovrodoe Sep 25 '18 at 08:31
  • Yeah. If the cipher is not matched, an error like `no shared cipher ` will occur. – leiyc Sep 25 '18 at 09:06