How to get a 100% score on SSL Labs (Nginx, Let’s Encrypt)

This post should be titled “How to get a 100% score on SSL Labs (Nginx, Let’s Encrypt) as of April 2018“, since SSL Labs‘s test evolves all the time (and a good thing it does, too.) But that would be too long a title. 🙂

The challenge:

As we’ve seen before, it’s relatively easy to get an A+ rating on SSL Labs. However, even that configuration will only give a score like this:

Zurgl.com's current SSL Labs score
Zurgl.com’s current SSL Labs score

Certainly more than good enough (and, incidentally, almost certainly much better than whatever home banking site you use…), but your weirdo of a host can’t look at those less-than-100% bars and not see a challenge. 🙂

First, note that all of the following will be just for fun, as the settings to get 100% will demand recent browsers (and, in some cases, recent operating systems/devices), so you probably don’t want this in a world-accessible site. It would be more applicable, say, for a webmail used by you alone, or by half a dozen people. But the point here isn’t real-world use, it’s fun (OK, OK, and learning something new). So, let’s begin.

Getting that perfect SSL Labs score:

For this example, I’ll be using a just-installed Debian 9 server, in which I did something like:

apt -y install nginx
echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list.d/stretch-backports.list # if you don't have this repository active already
apt -y install python-certbot-nginx -t stretch-backports

(The reason for the above extra complexity is the fact that the default certbot in Debian Stretch is too old and tries to authenticate new certificates in an obsolete way, but there’s a newer one in stretch-backports.)

I also created a simple index.html file on /var/www/html/ that just says “Hello world!”. Right now, the site doesn’t even have HTTPS.

So, let’s create a new certificate and configure the HTTPS server in Nginx:

certbot --nginx --rsa-key-size 4096 --no-redirect --staple-ocsp -d mysite.mydomain.com

(replacing mysite.mydomain.com, obviously.)

Testing it on SSL Labs, it gives… exactly the same bars as in the image above, but with an A rating instead of A+. Right, we need HSTS to get A+ (and currently certbot doesn’t support configuring it automatically in Nginx), so we add this to the virtual host’s server section:

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

(Note: remove “includeSubDomains;” if you have HTTP sites on any subdomain. Also, HSTS makes your site HTTPS-only, so you can’t use it if you want to keep an HTTP version — in which case you can’t get better than an A rating.)

This upgrades the rating to A+, as expected, but the bars still didn’t change. Let’s make them grow, shall we?

Certificate:

It’s already at 100%, so yay. 🙂

Protocol Support:

Just disable any TLS lower than 1.2. In this case, edit /etc/letsencrypt/options-ssl-nginx.conf and replace the ssl_protocols line (or comment it and add a new one) with:

ssl_protocols TLSv1.2;

WARNING: any change you make to the file above will affect all virtual hosts on this Nginx server. If you need some of them to support older protocols, it’s better to just comment out that option in that file, and then include it in each virtual host’s server section.

EDIT: actually, it turns out that ssl_protocols affects the entire server, with the first option Nginx finds  (first in /etc/nginx.conf, then in the default website (which possibly includes /etc/letsencrypt/options-ssl-nginx.conf)) taking precedence. So just choose whether you want TLS 1.0 to 1.2 (maximum compatibility) or just 1.2 only (maximum security/rating); if you need both, use two separate servers.

Key Exchange:

We’ve already created the new certificate with a 4096-bit key (with “rsa-key-size 4096“, see above), and according to SSL Labs’ docs this should be enough… but it isn’t. After some googling, I found out that you also need to add the following option inside the server section in Nginx:

ssl_ecdh_curve secp384r1;

Since the default Nginx+OpenSSL/LibreSSL setting, either “X25519” or “secp256r1” (actually “prime256v1“), also lowers the score.

EDIT: again, ssl_ecdh_curve affects the entire server, so you can’t use different default curves for each virtual host. So I’d suggest (in /etc/letsencrypt/options-ssl-nginx.conf):

ssl_ecdh_curve secp384r1:X25519:prime256v1;

if you want the 100% rating, or:

ssl_ecdh_curve X25519:prime256v1:secp384r1:

otherwise. This one doesn’t affect compatibility, by the way; it’s just a question of the preferred order.

The certificate’s key size (4096 or 2048) is, like the certificate itself, specific to each virtual host.

Cipher Strength:

For 100% here, you need to disable not only any old protocols, but also any 128-bit ciphers. I started from Mozilla’s SSL tool (with the “Modern” option selected), then removed anything with “128” in its name, then moved ChaCha20 to the front just because, and ended up with this line, which I added to /etc/letsencrypt/options-ssl-nginx.conf (replacing the current setting with the same name):

ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384::ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384';

(This time, this setting can indeed be specified for each virtual host, though you’d need to comment it out in the file above (which affects all virtual hosts with Let’s Encrypt certificates) if you want to do so, otherwise they’ll conflict.)

The result:

100% on SSL Labs

As said near the beginning, this is not something you likely want to do (yet) for a production, world-accessible site, as it will require relatively modern browsers, and you typically can’t control what visitors use1. According to SSL Labs’ list, it’s not actually that bad: all versions of Chrome, Firefox, or Safari from the past couple of years are fine, but no Microsoft browser older than IE11 will work, nor will Android’s default browser before 7.0 (Chrome on Android, which is not the same thing, will do fine.) I’d suggest it for a site where you can “control” the users, such as a small or medium-sized company’s webmail or employee portal (where you, as a sysadmin, can and should demand up-to-date security from your users).

But the main point of this exercise was, of course, to see if I could do it. Challenging yourself is always good. 🙂 And if you can share what you learned with others, so much the better.

Linux: Creating and using an encrypted data partition

Encrypted disks and/or filesystems are nothing new on Linux; most distributions, these days, allow you to encrypt some or all your disk partitions, so that they can only be accessed with a password. In this tutorial, however, we’re going to add a new encrypted partition to an existing system, using only the command line. I’ve found that tutorials on the web seem to make this issue more complex than it actually is, so here’s mine — hopefully it’ll be easier than most.

In this guide, we’ll be making a few assumptions. First, as said above, it’ll be an existing system, to which we’ve just added a new disk, /dev/sdb . Adapt to your situation, of course. If you’re not using an entire disk, just create a new partition with fdisk (e.g. /dev/sdb1) and use it instead. Second, we’ll have the machine boot with the disk unmounted, then use a command to mount it (asking for a passphrase, of course), and another to dismount it when you don’t need it. The obvious usage of such a partition is for storing sensitive/private data, but theoretically, you could run software on it — as long as you don’t automatically attempt to start it on boot, and don’t mind keeping it mounted most or all of the time, which perhaps defeats its purpose: if it’s mounted, it’s accessible.

So, without further ado…

Continue reading “Linux: Creating and using an encrypted data partition”

Linux: How to Compile Stuff

A couple of days ago I was surprised when someone who’s worked as a Linux sysadmin for several years asked me how to compile a program that was only available in source format (it was a cryptocurrency miner, for the curious), since he’d never compiled anything in his life. How was that possible? But then I realized: compiling stuff on Linux hasn’t really been a necessity 1 for the last, oh, 15-20 years or so. Back in 1993 or so (yikes!), when I started using Linux, things were of course quite different.

So, without further ado: how to compile stuff on Linux. Assuming you’ve already downloaded and uncompressed a program’s source, here’s what works 99% of the time (from the program’s directory, of course):

./configure --help  # to see available options

./configure # followed by the desired options, if any

make && make install

By default, compiled programs are then installed to /usr/local (e.g. binaries go to /usr/local/bin, libraries to /usr/local/lib , etc.). If you want to change that, just add –prefix=path (e.g. –prefix=/usr/local/test) to the ./configure options. (In that example, it’ll install binaries to /usr/local/test/bin, and so on. There are ways (i.e. different ./configure options) to install to different sub-paths, but those are beyond this basic tutorial. The default /usr/local/bin, lib, etc. paths also have the advantage of being in the default executable and library paths in typical distributions, so that you don’t have to add to those.)

If the above commands worked for you, congratulations, you’ve compiled your first program, which should now be ready to run! 🙂 However, if you’ve never compiled anything before on that system, it’s more likely that the ./configure command complained about missing libraries, missing utilities, or even a missing compiler; after all, as the first paragraph showed, it’s possible to use Linux for years, even work with it for a living, without needing to compile anything, which in turn has caused most modern distributions to not install compilers and development stuff by default.

How to do so? On Debian/Ubuntu, start with:

apt-get install build-essential

And on Red Hat/CentOS/Fedora, use:

yum groupinstall “Development Tools”

The above should install the C/C++ compiler and common development tools. However, a particular program may ask for other libraries’ development files, which may not be installed even though the library itself is. It’s impossible to give you an exhaustive list, but typically you can use yum search or apt-cache search to find what you need. Debian-based development packages typically end in -dev, while Red Hat-based ones end in -devel . For instance, suppose you’re on Ubuntu and ./configure complains about not having the zlib development files. A quick search would point you (at least on Ubuntu 17.04) to zlib1g-dev, which you’d install with apt-get install zlib1g-dev .

And what if there is no configure script (unlikely these days, but possible if it’s a very old source)? If there is a file named Makefile, then try typing make, and see if it compiles. Otherwise, your best bet is to read any provided documentation (e.g. a README or INSTALL file).

Any questions, feel free to ask.

Ubuntu/Debian: Installing Nginx/Postfix/Dovecot using OpenSSL 1.1.x

OpenSSLSo, let’s say you have a Ubuntu or Debian server, using one or more of Nginx, Postfix, and Dovecot, and you’d like to have them link to OpenSSL 1.1.x instead of the default OpenSSL (as of Ubuntu 17.04, it’s version 1.0.2g). (Reasons may include wanting to use modern ciphers such as ChaCha20, or trying out support for the most recent TLS 1.3 draft. Also, if you want to try out LibreSSL instead of OpenSSL 1.1, please check out the previous post.)

So, here’s a relatively simple way, that doesn’t change the system’s default OpenSSL (believe me, that wouldn’t be a good idea, unless you recompiled everything):

Install dependencies:

apt-get install build-essential
apt-get build-dep openssl nginx dovecot postfix

Install OpenSSL 1.1.x:

  • download the latest 1.1.x source from www.openssl.org
  • compile and install it with:
./config --prefix=/usr/local/openssl11 --openssldir=/usr/local/openssl11 && make && make install

Install Nginx:

rm -rf /usr/local/src/nginx-openssl11
mkdir /usr/local/src/nginx-openssl11
cd /usr/local/src/nginx-openssl11
apt-get source nginx # ignore the permissions error at the end
  • edit nginx-<version>/debian/rules:  add

–with-openssl=/usr/local/src/openssl-

to the beginning of the common_configure_flags option (note that that’s the source directory you used to compile OpenSSL 1.1.x, not where you installed it to);

debuild -uc -us -b
  • install the required packages in the parent directory with “dpkg -i ” (do dpkg -l | grep nginx to see which you have installed; typically you’ll want to install the newly created versions of those);
apt-mark hold nginx* # to prevent nginx from being updated from Ubuntu / Debian updates

Done! You can now play around with the Mozilla TLS Guide to add support for modern ciphers to your Nginx’s configuration, and use SSLLabs’s SSL Server Test tool to check if they are correctly enabled.

Install Postfix:

It’s just like Nginx (replacing “nginx” with “postfix” in every command / directory name, of course), except that the changes to debian/rules are these:

  • find -DHAS_SSL, add -I/usr/include/openssl11/include/openssl in front of it;
  • find AUXLIBS += , add -L/usr/local/openssl11/lib in front of it
  • find the line with dh_shlibdeps -a, add –dpkg-shlibdeps-params=–ignore-missing-info to it
  • don’t forget the apt-mark hold postfix* at the end.

Install Dovecot:

Again, use the Nginx instructions, using “dovecot” instead of “nginx” everywhere, except that the changes to debian/rules should be:

  • after the line:
export DEB_BUILD_MAINT_OPTIONS=hardening=+all

add:

export SSL_CFLAGS=-I/usr/local/openssl11/include
export SSL_LIBS=-L/usr/local/openssl11/lib -lssl -lcrypto
  • after the section:
override_dh_makeshlibs:
# Do not add an ldconfig trigger; none of the dovecot shared libraries
# are public.
        dh_makeshlibs -n

add:

override_dh_shlibdeps:
        dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info

NOTE: the indentation in the second line needs to be a tab, don’t use spaces.

Again, remember to apt-mark hold dovecot* after installation.

How to check if your new installations of Postfix and/or Dovecot are using OpenSSL 1.1.x instead of the default OpenSSL 1.0.x? You could use ldd to check what SSL / TLS libraries your binaries and/or libraries link to, but the best way is probably to use a tool such as sslscan, which you can use to check what ciphers your SMTP, IMAP, etc. support (including with STARTTLS). If you see ChaCha20 in there, everything is fine. 🙂

If you ever want to go back to “normal” versions of these servers, just do apt-mark unhold nginx* (for instance).

Ubuntu/Debian: Installing Nginx/Postfix/Dovecot using LibreSSL

LibreSSLSo, let’s say you have a Ubuntu or Debian server, using one or more of Nginx, Postfix, and Dovecot, and you’d like to have them link to LibreSSL instead of the default OpenSSL. (I won’t go much into the possible reasons for it; maybe you’re bothered because modern distros are still sticking with OpenSSL 1.0.x, which is ancient and doesn’t support modern ciphers such as ChaCha20, or you trust the OpenBSD developers more than you trust the OpenSSL ones, or — and there’s nothing wrong with that — you want to do it just for fun. You could also use OpenSSL 1.1.x — check out this (very similar) post.)

So, here’s a relatively simple way, that doesn’t change the system’s default OpenSSL (believe me, that wouldn’t be a good idea, unless you recompiled everything):

Install dependencies:

apt-get install build-essential
apt-get build-dep openssl nginx dovecot postfix

Install LibreSSL:

  • download the latest portable source from www.libressl.org
  • compile and install it with:
./configure --prefix=/usr/local/libressl --with-openssldir=/usr/local/libressl && make && make install

Install Nginx:

rm -rf /usr/local/src/nginx-libressl
mkdir /usr/local/src/nginx-libressl
cd /usr/local/src/nginx-libressl
apt-get source nginx # ignore the permissions error at the end
  • edit the file nginx-<versão>/debian/rules:  add
-I/usr/local/libressl/include

to the line beginning with “debian_cflags:=“, and:

-L/usr/local/libressl/lib

to the line that begins with “debian_ldflags:“. After that, enter the nginx-<version> directory and compile the packages with the command:

debuild -uc -us -b
  • install the required packages in the parent directory with “dpkg -i <package names>” (do “dpkg -l | grep nginx” to see which you have installed; typically you’ll want to install the newly created versions of those);
apt-mark hold nginx* # to prevent nginx from being updated from Ubuntu / Debian updates

Done! You can now play around with the Mozilla TLS Guide to add support for modern ciphers to your Nginx’s configuration, and use SSL Labs’s SSL Server Test tool to check if they are correctly enabled.

Install Postfix:

It’s just like Nginx (replacing “nginx” with “postfix” in every command / directory name, of course), except that the changes to debian/rules are these:

  • find -DHAS_SSL, add -I/usr/include/libressl/include/openssl in front of it;
  • find AUXLIBS += , add -L/usr/local/libressl/lib in front of it
  • find the line with dh_shlibdeps -a, add –dpkg-shlibdeps-params=–ignore-missing-info to it
  • don’t forget the apt-mark hold postfix* at the end.

Install Dovecot:

Again, use the Nginx instructions, using “dovecot” instead of “nginx” everywhere, except that the changes to debian/rules should be:

  • after the line:
export DEB_BUILD_MAINT_OPTIONS=hardening=+all

add:

export SSL_CFLAGS=-I/usr/local/libressl/include
export SSL_LIBS=-L/usr/local/libressl/lib -lssl -lcrypto
  • after the section:
override_dh_makeshlibs:
# Do not add an ldconfig trigger; none of the dovecot shared libraries
# are public.
        dh_makeshlibs -n

add:

override_dh_shlibdeps:
        dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info

NOTE: the indentation in the second line needs to be a tab, don’t use spaces.

Again, remember to apt-mark hold dovecot* after installation.

How to check if your new installations of Postfix and/or Dovecot are using LibreSSL instead of the default OpenSSL? You could use ldd to check what SSL / TLS libraries your binaries and/or libraries link to, but the best way is probably to use a tool such as sslscan, which you can use to check what ciphers your SMTP, IMAP, etc. support (including with STARTTLS). If you see ChaCha20 in there, everything is fine. 🙂

If you ever want to go back to “normal” versions of these servers, just do apt-mark unhold nginx* (for instance).

I’ve also added /usr/local/libressl/bin to the beginning of my PATH environment variable, so that the LibreSSL binaries are used by default (e.g. to generate keys, CSRs, etc.), although this isn’t necessary for Nginx, etc. to work.