Tuesday, November 26, 2013

Tomcat SSL Hardening

If you are using SSL with your Java web application, and you are using Tomcat, the default configuration could be made more secure by by disallowing weak cipher suites. You can test your server configuration using Qualis SSL Server Test. With APR, you can use the cipher string as suggested in Hardening Your Web Server’s SSL Ciphers. Note that RC4 is not in that list, because it is broken. Standard Diffie-Hellman is slow, so I would remove those cipher suites from the string, making it

ECDH+AESGCM:ECDH+AES256:ECDH+AES128:ECDH+3DES:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS

If you are not using APR, you need slightly different directives. SSL is configured in conf/server.xml, and there's a default configuration commented out:

<!--
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
           maxThreads="150" scheme="https" secure="true"
           clientAuth="false" sslProtocol="TLS" />
-->

After uncommenting it, we need to change a few things. Setting the certificate is beyond the scope of this post, but you'll probably need at least the keystorePass and keyAlias options. Change the protocol option to protocol="org.apache.coyote.http11.Http11NioProtocol", which will enable the NIO connector. The NIO connector is non-blocking for better performance with slow clients, and doesn't support client-initiated renegotiation, which mitigates some DOS attacks. If you don't care about IE6 and Java 6, you can remove SSLv3 and only leave the TLS protocols enabled using sslEnabledProtocols="TLSv1.2,TLSv1.1,TLSv1".

Let's convert the cipher string into something Tomcat can understand. The option to configure is ciphers, using the JSSE naming convention, as specified in the documentation. In order to convert the OpenSSL cipher suite names to JSSE names, we can use the standard TLS codes for each cipher suite. The full list can be downloaded from the IANA registry, where you can download a tls-parameters-4.csv file. Now we can use a little bit of shell to convert the list:

STRING='ECDH+AESGCM:ECDH+AES256:ECDH+AES128:ECDH+3DES:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS'
CODES=`/usr/local/ssl/bin/openssl ciphers -V "$STRING" | cut -d- -f1`
CIPHERS=`for CODE in $CODES; do grep "^\"$CODE\"" tls-parameters-4.csv | cut -d, -f3; done`
echo $CIPHERS | sed 's/ /,/g'

Which gives us the string TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA.

Set your ciphers parameter to that. Note that even though the list contains GCM (Galois/Counter Mode) suites, the default Java 7 Sun JSSE provider does not implement them, and so they won't be used under Java 7. GCM suites should be implemented for Java 8 - see JEP 115. Also, if you are in a country that does not prohibit strong encryption, you need to install the JCE Unlimited Strength Jurisdiction Policy Files, otherwise you won't have any 256 bit ciphers.

With the above cipher string and Oracle Java 7, the SSL Server Test provides a score of 90 for key exchange and 90 for cipher strength. It also says that BEAST is not mitigated server-side, but that attack is mitigated on the client side for most clients - see BEAST information. Apple has also finally fixed this in Mavericks - see this post. However, Tomcat does not enforce the order of the ciphers in the list, and so for some reason all version of IE that are tested choose the RSA key exchange, even though they support ECDHE. Of course, RSA key exchange does not provide forward secrecy. We can further improve this by removing RSA key exchange from the cipher string:

ECDH+AESGCM:ECDH+AES256:ECDH+AES128:ECDH+3DES:!aNULL:!MD5:!DSS

Which results in this list of ciphers:

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA.

With this list, IE is forced to choose ECDHE for key exchange, which provides forward secrecy. IE6/XP, IE8/XP, and Java6u45 still fail because SSLv3 is disabled. However, Bing Oct 2013 handshake now also fails, as well as OpenSSL 0.9.8y (released February 5 2013). OpenSSL 1.0.1e works fine. If you are ok with forcing some clients to upgrade, leave out the RSA key exchange.

2 comments:

Richard Harris said...

Any chance you could update this post with the exact string to put into server.xml for APR in tomcat 7?

Gary said...

Sorry, I don't have this set up any more, and I wasn't using APR anyway.