Monday, April 13, 2015

Java 7 and TLSv1.2 - supported, but not enabled by default

Lately, I had to investigate how to make a Java 7 client connect to a server resource (in my case a JMS broker server) using sockets or http traffic over secure TLS v1.2 protocol (and only TLS v1.2... all other communication protocols should be refused). This would all have been a breeze if my client app was on Java 8...but being on Java 7, it was a completely different story.

At first, I thought it'd be easy since my JMS broker server (Terracotta Universal Messaging in this case) already supports TLS v1.2, lets you enforce it through system property (-DSSLProtocols), and also lets you pick which cipher(s) to use. So I thought all I needed was to set that system property on the server side and disable in the UI (called Enterprise Manager) all ciphers for this communication interface BUT the TLS v1.2 ciphers I wanted to use (the "SHA256" cipher suites in this case -- check full list of available ciphers for TLS v1.2 at https://www.openssl.org/docs/apps/ciphers.html)...as shown below in screenshot:


And everything would work fine, right? The client would start the secure handshake with the server, figure out that TLSv1.2 is the only protocol that can be used, and use the right secure protocol version and ciphers as defined by the server.

Well, this theoretical scenario is partially true: Everything is setup properly on the server side, as it rightly says "I only accept TLSv1.2 sessions with the following ciphers". But unfortunately, the Java 7 client does not seem to be able to start a TLSv1.2 secure connection by default (if you're on Java 8, stop reading right now -- unless you're curious of course -- as it would all work fine out of the box)

And yes, after quick research, Java 7 introduced support for TLS v1.2 (refer to http://docs.oracle.com/javase/7/docs/technotes/guides/security/enhancements-7.html) BUT does not enable it by default. In other words, your client app must explicitly specify "TLS v1.2" at SSLContext creation, or otherwise will just not be able to use it.

This is a pretty important nuance that you can test yourself very easily with the following 2 test cases:

1) Output for Gist - Java 7 + SSLContext context = SSLContext.getDefault()
Supported Protocols: 5
 SSLv2Hello
 SSLv3
 TLSv1
 TLSv1.1
 TLSv1.2
Enabled Protocols: 1
 TLSv1
Enabled Ciphers: 80
... list of available ciphers omitted here ...
2) Output for Gist - Java 7 + SSLContext context = SSLContext.getInstance("TLSv1.2")
Supported Protocols: 5
 SSLv2Hello
 SSLv3
 TLSv1
 TLSv1.1
 TLSv1.2
Enabled Protocols: 3
 TLSv1
 TLSv1.1
 TLSv1.2
Enabled Ciphers: 80
... list of available ciphers omitted here ...
So this is great! All you and I need is to update the client app's code to specify TLSv1.2 at SSL Context creation! 
But wait...what if I'm using a client library to perform all that low level SSLContext connection etc...in that case, it's going to be tricky to update the code, unless specifying the SSL protocol versions and ciphers are exposed to client app in some way (eg. system properties).
If you reach this situation, here are 3 solutions I found (well, I found 1 and 3...solution 2 is credited to a smarter developer :) )
1) If all you want is to create a HTTPS connection (over TLSv1.2) from your client app to the server, you're in luck: all you need is add the following 2 system properties to your client app and the SSL context will be created properly as specified:
  • https.protocols ==> -Dhttps.protocols=TLSv1.2 (comma-separated list of you want to use several protocols)
  • https.cipherSuites ==> -Dhttps.cipherSuites=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (comma-separated list of you want to use several ciphers)
2) If you want to use the secure socket protocol (as opposed to HTTPs traffic), the above properties won't work unfortunately...but you could create a "TLSv1.2" SSLContext at application startup and use the "SSLContext.setDefault(ctx)" call to register that new context as the default one.

3) Upgrade your client to JAVA 8, which enables TLSv1.2 protocol by default (at this time, JAVA 8 should be already used anyway, or on most project roadmaps hopefully)

That's it! Another fun investigative work day at the office!

9 comments :

Santhosh Kumar said...

Hi Fabien,

The article was really helpful and I appreciate your efforts to present your research outcomes.

Among the three solutions you provided, 3rd one (Upgrading to JAVA 8) is absolutely working fine. But my application cannot be upgraded to Java 8 now.

Solution one is not working for me. My standalone.xml file does not have a HTTPS protocol connector. I added the properties you gave as JAVA VM arguments but still it is picking TLSv1. I am using JBOSS EAP 6 server, can you please give me more information, if I am missing anything.

If you have any example code for Solution-2 please provide.

Thanks,
Kumar

girish said...

Paypal has upgraded SSL certificates on their sandbox API endpoints to use "sha256" algorithm instead of sha1 (https://www.paypal-knowledge.com/infocenter/index?page=content&widgetview=true&id=FAQ1766&viewlocale=en_US&direct=en)

The client certificate uses "sha256withrsa".I used this new certificates to connect to their sandbox endpoints and encountered handshake errors. Paypal also uses tlsv1.2

Getting error "Unsupported SignatureAndHashAlgorithm in ServerKeyExchange Message"
I tried the above things u mentioned but no success. Do you have any pointers on this?

Thanks,
Girish

arnieSSSS said...

Your stated https.cipherSuites VM argument very help me and solve my unsupported ciphers problem. Very thanks!

Aniket Karhadkar said...

I am trying to hit my service from mule client with TLSv1.2 protocol.
I tried system properties mentioned above to enable this protocol.

But when I hit my service - getting below exception.




soap:Server
These policy alternatives can not be satisfied:
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}TransportBinding: TLS is not enabled
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}HttpsToken
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}TransportToken


Kirill Yunussov said...
This comment has been removed by the author.
Kirill Yunussov said...

For the option #2:

try {
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(null, null, null);
SSLContext.setDefault(ctx);

} catch (Exception e) {
System.out.println(e.getMessage());
}

Fabien Sanglier said...

yes you're correct that's option #2...I should indeed have posted the full code for that...thanks for the comment.

Deepakala said...

I really enjoy the blog.Much thanks again. Really Great.

Java and J2EE Training in Chennai - AmitySoft

Madhavan K said...

Thanks. Solution #2 greatly helped.