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.

Tuesday, May 14, 2013

Encrypting Database Passwords In Ruby

Most web applications connect to a database, which means that they need to store the database password in a file. While that file should only be readable by the user running the application server, security can be further improved by encrypting the password in the file. The encryption passphrase is then stored in the code of your application. This means that an attacker would not only have to get access to the password file, but also to the application code to decrypt the password. Here's a simple implementation of AES 256 encryption/decryption in Ruby. I've run it in JRuby using Java 1.7, and it requires you to install "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" for your JRE. On Mac OS, after extracting the zip file I had to copy US_export_policy.jar and local_policy.jar to /Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home/jre/lib/security/.

# file_crypto.rb

require 'openssl'

class FileCrypto
  def initialize(passphrase, iv_passphrase, salt)
    @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(passphrase, salt, 2000, 32)
    @iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(iv_passphrase, salt, 2000, 16)
  end

  def read(filepath)
    decrypt(IO.read(filepath))
  end

  def write(filepath, plaintext)
    IO.write(filepath, encrypt(plaintext))
  end

  def encrypt(data)
    cipher = OpenSSL::Cipher::AES256.new(:CBC)
    cipher.encrypt
    cipher.key = @key
    cipher.iv = @iv
    cipher.update(data) + cipher.final
  end

  def decrypt(data)
    cipher = OpenSSL::Cipher::AES256.new(:CBC)
    cipher.decrypt
    cipher.key = @key
    cipher.iv = @iv
    cipher.update(data) + cipher.final
  end
end

Then, you can encrypt test password like this:
require_relative 'file_crypto'

fc = FileCrypto.new(
  'j7U2 k92M%qc mw}f;T@6cAlH',
  'cZd !W7Xf2PMQY#Xprt)n',
  '28Ca kDl^vYuO'
)

fc.write('/tmp/encpassword', 'test password')

And decrypt it back like this:
require_relative 'file_crypto'

fc = FileCrypto.new(
  'j7U2 k92M%qc mw}f;T@6cAlH',
  'cZd !W7Xf2PMQY#Xprt)n',
  '28Ca kDl^vYuO'
)

puts fc.read('/tmp/encpassword')

Concurrent Tasks In JRuby

Sometimes you need to execute long running tasks (like database queries on multiple shards) concurrently. JRuby can easily make use of Java's built-in Executor class for this purpose.

# Since java.util.concurrent.ThreadPoolExecutor.submit has multiple signatures,
# we force JRuby to use the one signature we want, and stop a warning from being emitted.
java.util.concurrent.ThreadPoolExecutor.class_eval do
  java_alias :submit, :submit, [java.util.concurrent.Callable.java_class]
end

# Start the executor service.  Note that this won't create any threads right away,
# but will create new threads as the jobs are submitted, until it gets to 5 threads.
# Then it will reuse the threads.
executor = java.util.concurrent.Executors.new_fixed_thread_pool 5

# Submit jobs, get back java.util.concurrent.FutureTask objects.
tasks = [1, 3, 4, 6].map do |s|
  executor.submit do
    # run database query on shard s
    # return query result
  end
end

# We get control back immediately, while the queries are being executed by the thread pool.
# We can do some other things here while the queries run.

# Get the results of all queries and combine them into an array.  FutureTask.get will wait
# for the task to complete if it's not completed yet, so it's safe to call this right away.
# If for some reason we wanted to check if a task is complete, we can call t.done? .
results = tasks.map do |t|
  begin
    t.get
  rescue java.util.concurrent.ExecutionException => e
    # Convert the exception to one actually thrown inside the task.
    raise e.cause.exception
  end
end

# Shut down the thread pool.
executor.shutdown_now