Play TLS Example With Client Authentication

This is part of a series of posts about setting up Play WS as a TLS client for a "secure by default" setup.

Previous posts are:

This post is where the rubber meets the road – an actual, demonstrable activator template that shows off the WS SSL, provides the scripts for certificate generation, and provides people with an out of the box TLS 1.2 using ECDSA certificates.

Want to download it? Go to https://github.com/typesafehub/activator-play-tls-example or clone it directly:

git clone https://github.com/typesafehub/activator-play-tls-example.git

It's an activator template, so you can also install it from inside Typesafe Activator by searching for "TLS".

Be sure to read the README. This project is as lightweight as possible, but takes a little configuration to get started.

Certificate Generation

The biggest part of any demo application is setting up the scripts. I didn't find anything that was really hands free, so I wrote my own. They are exactly the same as the ones described Certificate Generation section of the manual.

There's various shortcuts that you can use for defining X.509 certificates, but I found it a lot more useful to go through the work of setting up the root CA certificate, defining the server certificate as having an EKU of "serverAuth" and so on.

Play Script

The actual script to run Play with all the required JVM options is… large. Part of this is the documentation on every possible feature, but sadly, there are far too many lines which are "best practices" that are very rarely practiced.

Also, the note about rh.secure is a reference to the RequestHeader class in Play itself. Ironically, even when we set HTTPS up on the server, Play itself can't tell the protocol it's running on without help.

I will admit to being gleefully happy at setting disabledAlgorithms.properties on startup, so that at last AlgorithmConstraints is enabled on the server:

jdk.tls.disabledAlgorithms=RSA keySize < 2048, DSA keySize < 2048, EC keySize < 224
jdk.certpath.disabledAlgorithms=MD2, MD4, MD5, RSA keySize < 2048, DSA keySize < 2048, EC keySize < 224

CustomSSLEngineProvider

The CustomSSLEngineProvider is responsible for Play's HTTPS server. More details can be found in Configuring HTTPS.

Setting up an SSLEngineProvider with client authentication is pretty easy, once you know the magic incantations needed to get the trust managers and the key managers set up. After that, it's a question of ensuring that the SSLEngine knows how trusting it should be.

override def createSSLEngine(): SSLEngine = {
  val sslContext = createSSLContext(appProvider)

  // Start off with a clone of the default SSL parameters...
  val sslParameters = sslContext.getDefaultSSLParameters

  // Tells the server to ignore client's cipher suite preference.
  // http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#cipher_suite_preference
  sslParameters.setUseCipherSuitesOrder(true)

  // http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLParameters
  val needClientAuth = java.lang.System.getProperty("play.ssl.needClientAuth")
  sslParameters.setNeedClientAuth(java.lang.Boolean.parseBoolean(needClientAuth))

  // Clone and modify the default SSL parameters.
  val engine = sslContext.createSSLEngine
  engine.setSSLParameters(sslParameters)

  engine
}

Connecting to the server with Play WS

Setting up the Play client was pretty easy, but it's worth repeating that Play WS can be run outside of an application with the right setup:

def newClient(rawConfig: play.api.Configuration): WSClient = {
  val classLoader = Thread.currentThread().getContextClassLoader
  val parser = new DefaultWSConfigParser(rawConfig, classLoader)
  val clientConfig = parser.parse()
  clientConfig.ssl.map {
    _.debug.map(new DebugConfiguration().configure)
  }
  val builder = new NingAsyncHttpClientConfigBuilder(clientConfig)
  val client = new NingWSClient(builder.build())
  client
}

The configuration on the ws.conf file was also intentionally strict. The NSA recommends some nice cipher suites Suite B Profile for TLS – the WS client will refuse to talk to the server with anything less than full on TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 and isn't going to look at any ECDSA signature less than EC keySize < 384.

Client authentication on the client side was a little trickier than expected, but eventually I remembered that the client should be using a key store containing a trust anchor, and it worked itself out.

Conclusion

I think I'm done with JSSE for now. I was interested in exploits at one point, but the fixes amount to "Upgrade to JDK 1.8, use ECDSA certificates, and TLS 1.2" – all of which are demonstrated in this application.

I may at some point go back and look at HSTS or public key pinning in Play WS, but it really comes down to utility. Many of the use cases of pinning involve browsers or unknown clients. I've not heard of any demand for the feature, and it's unclear that anyone would find it all that useful.

With Play and with these blog posts, I'm very pleased to have written something that people find useful. Thanks.