SSL Doesn't Really Exist - How to Set Up TLS

Setting up SSL for a web server (I'm using Nginx in this instance) turns out to be a complex subject. Not least among the mysteries is that when we say "SSL" these days, we actually mean "TLS." And the reason behind this is tied to much of the complexity. "SSL" is short for "Secure Sockets Layer," and it was the first successful attempt to implement server-client encryption in the web browser. When flaws were found in v2.0 (v1.0 was never publicly released), fixes were applied, and v3.0 was pushed out into the world. v2 was officially deprecated in 2011, although it had been known to be damaged for more than a decade. POODLE was the final nail in the coffin of SSL 3.0, which was officially deprecated in June 2015, although it had long ago been replaced by TLS v1. TLS is "Transport Layer Security," and guess what: it's not considered terribly secure. Happily, we have TLS 1.1 and 1.2, which are still considered reasonably secure. The problem is ... lots of people use old browsers that don't speak TLS 1.2 ... or sometimes even TLS, period. And then there are software and websites that are locked in to old versions of SSL because they're difficult or impossible to upgrade (or maybe lazy), but happily I don't have to think about that today.

Initially, my nginx server {} block contained little more than this:

ssl_certificate <filename>;
ssl_certificate_key <filename>;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# and some guessing:
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;

One of the interesting side effects of this is that Internet Explorer 9 under Windows 7 will quite literally not connect to the server, even though it works fine under more modern browsers. Unfortunately, I have to be concerned about that particular demographic, so research was required. It's also problematic because it doesn't degrade gracefully: IE9 simply gives you an ugly error about not being able to connect, at all.

First up, an excellent test suite: https://www.ssllabs.com/ssltest/analyze.html . Put in your server name and find out what you've done wrong. (Note that it will initially take 20-30 seconds. After that the results are cached ... which also means you need to tell it to dump the cache if you've changed your settings and want it tested again.) Having seen first hand how IE9 fails, I was particularly interested in the "Handshake Simulation" section, which showed Androids right up through 4.3 failing. I was less concerned about browsers on Windows XP, but these were less than optimal results. There were also a number of complaints about "DH 1024 bits FS WEAK," with one entry that said "Uses common DH primes: Replace with custom DH parameters if possible."

"DH" is "Diffie Hellman," and I'm not even going to attempt to explain that because I don't really understand it. Although I think it's associated with the initial handshakes and exchange of information that leads up to the creation of the session key. But I kind of understand the solution: the problem has to do with using the same 1024-bit prime numbers used by everyone else, so the solution is to generate your own so they're harder to guess. And while we're at it, we're going to make them longer:

openssl dhparam 2048 -out <filename>

You're probably looking at a couple minutes run-time on a basic machine in 2016 to generate that file, although it's only about 500 bytes in size. That's prime generation for you. Add a line to your nginx.conf server block:

ssl_dhparam <filename>;

Another good step is to enforce Strict Transport Security - essentially, the server sends a message to the client saying "you should ALWAYS use https: when talking to us." This reduces communication overhead if you're redirecting http: queries to https: because they'll come in as https: to start with. It also prevents a form of MITM attack that involves tricking clients into switching from https: back to http: because they'll now refuse to do so.

add_header Strict-Transport-Security "max-age=2592000";

The number is a time in seconds, the one I've chosen is thirty days. You should probably use more, this is for an experimental server. Or, if you want STS enforced on subdomains as well:

add_header Strict-Transport-Security "max-age=2592000; includeSubDomains";

I haven't tried this as I'm not using subdomains heavily - but maybe later.

Another subject I'm not covering in detail because I don't fully understand it is Nginx's Cipher Suite, the "ssl_ciphers" command. As best I understand it, this protects session keys so that a compromise of the private key won't compromise previous sessions. Also known as "Forward Secrecy." The ciphers you choose should be ordered from strongest to weakest (or "most preferred" to "least preferred" - these things may differ because you may opt for a fast "good enough" cipher first to reduce overhead on the server). I've simply followed one several recommendations online: there are hundreds of ciphers, so this line can be very long.

ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

The end result of this is I've gone from a "B" rating in the Qualsys test to an "A" rating, and IE9 on Windows 7 connects without complaint. The only remaining disagreements Qualsys has are:

  • IE6 / XP doesn't work (I really, really don't care)
  • Java 6u45 doesn't support DH parameters larger than 1024 bits (I'm sorry to say I may have to care about this)
  • it's unhappy because 30 days for STS is too short, it should be 180 days or more

Caveat

I'm just learning this stuff: you shouldn't take this as gospel, do some more reading (the links below are my sources, and probably a good starting point). And one piece I know I should add that I haven't addressed here is "OCSP Stapling."