Moving to TLSv1.2 or newer: Nginx, Apache, Postfix, Dovecot

Moving on from “should we do it?” (with the answer to most real-world scenarios being “yes, and as a bonus it can help block a lot of spambots“), here’s how to restrict several Internet services — Nginx, Apache, Postfix, and Dovecot — to TLSv1.2 or newer.

As usual, these are not complete guides for any of those servers; I’m assuming you already have them working fine (including TLS encryption), and just want to disable any TLS protocols lower than v1.2. (If you need to add TLS to a non-TLS server, see instructions for Nginx, Apache, Postfix, and Dovecot.)

Nginx:

In each virtual host’s server section — or, even better, if you’re using Let’s Encrypt, in /etc/letsencrypt/options-ssl-nginx.conf or its equivalent –, add the following (or replace any existing ssl_protocols entry):

ssl_protocols TLSv1.2 TLSv1.3;

Restart Nginx, and test it on SSL Labs. You should get something like this1:

TLS v1.2 and v1.3 only

(Note: if you’re using a very old version of Nginx, it may not accept the “TLSv1.3” parameter and refuse to start; in such a case, remove it — or, better yet, upgrade your system. 🙂 )

Apache:

Similarly to Nginx, you can add one of the following to each Virtual Host, or to the global HTTPS configuration (typically in /etc/httpd/conf.d/ssl.conf), or, if using Let’s Encrypt, to /etc/letsencrypt/options-ssl-apache.conf :

SSLProtocol            +TLSv1.2 +TLSv1.3

or:

SSLProtocol             all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

Right now, they’ll do the same thing: allow TLSv1.2 and v1.3 only. Personally, I like the second version (which disables older protocols) better, for two reasons: 1) it’ll work even with some ancient Apache version that doesn’t recognize “TLSv1.3”, and 2) when future TLS versions are added, they’ll be enabled, making it more future-proof.

Again, you can test the new configuration on SSL Labs.

Postfix:

Add the following to /etc/postfix/main.cf (replacing any equivalent entries, if they exist).

smtp_tls_mandatory_protocols=!SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols=!SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols=!SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols=!SSLv2, !SSLv3, !TLSv1, !TLSv1.1

After restarting Postfix, you can test its available protocols with Immuniweb’s SSL Security Test (specify something like yourhostname:25, or yourhostname:465 if not using STARTTLS).

Dovecot:

Add the following (or replace it if it exists) to your SSL configuration (typically /etc/dovecot/conf.d/10-ssl.conf):

ssl_min_protocol = TLSv1.2

Restart Dovecot, and test it on Immuniweb’s test (use something like yourhostname:143 , or yourhostname:993 if not using STARTTLS).

Moving to TLSv1.2 or newer: is now the time?

TLSv1.0, used not only for HTTPS but also for secure SMTP, IMAP, etc., has been around for a while — 1999, in fact –, and, while not “hopelessly broken” like SSL 2 and 3 are, there have been many successful attacks/exploits against it in the past two decades, and while most implementations (in terms of both servers and clients) have been patched to work around those, there may always be another one just around the corner. Also, TLSv1.0 came from a time where it was desirable to support as many different ciphers as possible, which means that, by default, some ciphers later found to be insecure may still be enabled.

Therefore, the end of TLSv1.0 has been talked about for a while, and, indeed, the main browser vendors — Google, Mozilla, Apple, and Microsoft — have announced that they’ll deprecate TLSv1.0 (and 1.1, which is little more than a revision of 1.0) by early 2020.

Now, until a few days ago I still had TLSv1.0 enabled on my servers (including Nginx, Postfix and Dovecot); my belief then being that some encryption was always better than no encryption, and besides we were mostly talking about public websites, that didn’t ask for user credentials or anything. Furthermore, any modern browsers/email clients default to secure protocols (TLSv1.2 or better), so there would never be, “in real life”, a situation where important/sensitive data (say, me logging on to my own mail server, or to one of my WordPresses) was being transmitted in an insecure way.

On the other hand… doesn’t the above mean that, “in real life”, nobody will really be using TLSv1.0 “legitimately”? Doesn’t it mean that setting up TLSv1.2 as a minimum will have no adverse consequences?

So I investigated it a little:

  • in terms of browsers/clients, requiring TLSv1.2 or above means: 1) no Internet Explorer before version 11; 2) no Safari before 6.x; 3) no default Android browser before Android 4.4.x (but those users can still use Chrome or other non-default browsers). (You can check this out on SSL Labs). I don’t have a way to check email clients that old, but you can assume that anything released after the browsers just mentioned will be OK;
  • in terms of my own server logs (mostly Nginx, but also Postfix, in terms of opportunistic encryption), apparently, only spambots and such are using TLSv1.0; any “legitimate” email servers use TLSv1.2 or 1.3. The same thing with my websites — any TLSv1.0 entries in my logs are always from some bot (and not one for a “well-known” search engine), not actual visitors. 1

In short: you can probably move to requiring TLSv1.2 or above now (and, indeed, I’ve done so since a few days ago), unless you’re in some very peculiar situation — maybe your site needs to be accessed by users of a company whose IT policy includes not using software newer than 20 years old? Or you have a CEO who’s always refused to upgrade his beloved Symbian phone, but who’d flip out if he couldn’t access your company’s webmail with it? 🙂 Otherwise, my advice is: go TLSv1.2 and don’t look back.

However, and since this post is already quite long, I’ll leave the specific instructions for disabling TLS before 1.2 in Nginx, Apache, Postfix, and Dovecot for the next one

Nginx: How to prioritize ChaCha20 for devices without hardware AES support

Some time ago, I noticed something like this on a site (not mine)’s rating on SSL Labs:

SSL Labs - "P"

According to SSL Labs’ test, that little P means:

(P) This server prefers ChaCha20 suites with clients that don’t have AES-NI (e.g., Android devices)

Naturally, my curiosity was piqued, and a bit of investigating followed…

First (and briefly), the theory:

  1. both AES and ChaCha20 ciphers are thought to be equally secure.
  2. many modern CPUs provide hardware acceleration for AES, in which case it’s faster…
  3. … however, if the CPU doesn’t accelerate AES, then ChaCha20 is faster.

Therefore, setting a web server (or any other kind of server, such as IMAP or SMTP, but those are outside this article’s scope) to use either option all the time means that some clients won’t have the optimal cipher(s) (in terms of performance) for their device. It won’t probably be noticeable in 99% of cases, but, hey, I’m a geek, and geeks optimize stuff for fun, not just for real-world performance. 🙂

Fortunately, as the SSL Labs test shows, it’s possible to configure a server to use/prefer one cipher for non-hardware accelerated devices, and another cipher (or, more precisely, a list of preferred ciphers) for the rest.

(For the curious, the selection process works like this: if the client’s preferred cipher is ChaCha20, then the server assumes it’s a device without hardware AES and uses that. If the client’s top cipher is anything else, then the server uses its own cipher list.)

This guide shows how to do it in Nginx, using Let’s Encrypt certificates. I’m using an Ubuntu 19.04 server, but any relatively recent Nginx should work, as long as you’re using OpenSSL 1.1.0 or newer, or LibreSSL. If your distro still provides only OpenSSL 1.0.x or older by default, you can always compile a newer version to a separate directory, and then compile Nginx to use it: here are instructions for OpenSSL 1.1.x or LibreSSL.

This is actually relatively simple, though some specific distros/servers may require some other changes:

1- Give Nginx a list of ciphers that doesn’t have ChaCha20 in first place (you need to specify some AES ciphers first). The list I’m using (currently specified in /etc/letsencrypt/options-ssl-nginx.conf , since I’m using Let’s Encrypt) is:

ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA::!DSS:!3DES';

2- Edit your OpenSSL/LibreSSL configuration file (in Ubuntu/Debian it’s /etc/ssl/openssl.cnf) and, just before the first section in square brackets (in Ubuntu it’s “[ new_oids ]“), add the following:

openssl_conf = default_conf

[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = ServerPreference,PrioritizeChaCha

3- Restart Nginx, and test it on SSL Labs. If you see the superscript “P” at the right of each ChaCha20 line, everything went well. (Problems? Ask here, and maybe I, or some other reader, can help.)

You can also set up your Nginx to log ciphers (possibly to a separate log file), using something like:

log_format logciphers '$remote_addr ' '$ssl_protocol/$ssl_cipher ' '"$request" $status $body_bytes_sent ' '"$http_user_agent"';

access_log /var/log/nginx/servername-ciphers.log logciphers;

And then access the site with some of your devices, noting what ciphers they use. For instance, both my (2015) i7 laptop and my (2016) ZenFone 3 Android phone use AES (yes, apparently the Snapdragon 625 chip includes AES acceleration), but a 2013 Nexus 7 tablet and a 2015 iPad Pro (Safari, in this case — all other examples used Chrome) went for ChaCha. This shows that everything’s working correctly — the devices that specifically want to use ChaCha are indeed using it, while the rest aren’t. And, yes, this means that the very same browser (in this case, Chrome on Android) has different cipher preference defaults depending on the hardware it’s running on.

How to get a 100% score on SSL Labs (Red Hat/CentOS 7.x, Apache, Let’s Encrypt)

There’s an Nginx version of this tutorial already, but since some people prefer/have to use Apache, here’s how to get 100% on SSL Labs’ server test with it.

For extra fun, instead of Debian/Ubuntu, we’ll be using Red Hat/CentOS (more precisely, CentOS 7.6 with the latest updates as of January 10th, 2019, but this shouldn’t change for any other 7.x versions of either CentOS or RHCE). And we’ll keep firewalld and SELinux enabled 1, even though, as far as I know, in most companies (well, at least the ones I’ve worked at) it’s, typically, official policy to disable both. 🙂

So, let’s assume you already have a CentOS/RHEL machine, with internet access and the default repositories enabled. First, we’ll enable the EPEL repository (to provide Let’s Encrypt’s certbot):

yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

Now we’ll install certbot (which will also install Apache’s httpd and mod_ssl as dependencies) and wget (which we may need later):

yum install python2-certbot-apache wget

To be run certbot and allow it to verify your site, you should first create a simple vhost. Add this to your Apache configuration (e.g. a new file ending in .conf in /etc/httpd/conf.d/):

<VirtualHost *:80>
    DocumentRoot "/var/www/html"
    ServerName mysite.mydomain.com
</VirtualHost>

Now add Apache’s ports to firewalld, and start the service:

firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload

systemctl enable httpd ; systemctl restart httpd

Run certbot to create and begin using the new certificate:

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

You should now have the virtual host configured in /etc/httpd/conf.d/ssl.conf . Let’s add a few lines to it (inside the <VirtualHost></VirtualHost> section):

Header always set Strict-Transport-Security "max-age=15768000"
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite          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
SSLCipherSuite          TLSv1.3 TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384
SSLHonorCipherOrder     on
SSLCompression          off

(Don’t mind the “TLSv1.3” line for now, it won’t be used — if at all  — until later in this post, and won’t have any effect if it isn’t.)

This should get you an A+ rating, with all bars at 100% except for Key Exchange. A good start, right? 🙂 However, that final bar can be a bit tricky. The problem here is that SSL Labs limits the Key Exchange rating to 90% if the default ECDH curve is either prime256v1 or X25519, the first of them being the OpenSSL default… and Apache 2.4.6 (the version included in all 7.x RHELs/CentOSes) doesn’t yet include the option to specify a different list of curves (of which secp384r1 will give a 100% rating, and won’t add any compatibility issues).

Assuming you don’t want to install a non-official Apache, then the only way (as far as I know) to specify a new curve is to add the results of running:

openssl ecparam -name secp384r1

to your certificate file. But Let’s Encrypt certificates are renewed often, so you may need some trickery here — such as adding something like this to root’s crontab:

*/5 * * * * grep -q "EC PARAMETERERS" /your/certificate/file.crt || openssl ecparam -name secp384r1 >> /your/certificate/file.crt

Ugly, I know. 🙂

Alternatively, you can upgrade Apache to a newer version. CodeIT (for instance) provides replacement packages for RHEL and CentOS, which you can use by doing:

cd /etc/yum.repos.d && wget https://repo.codeit.guru/codeit.el`rpm -q --qf "%{VERSION}" $(rpm -q --whatprovides redhat-release)`.repo
yum -y update httpd mod_ssl

This will update Apache from 2.4.6 to 2.4.37 (as of January 10th, 2019), and so you can add the following to the vhost:

SSLOpenSSLConfCmd     Curves secp384r1:X25519:prime256v1

However! This will still not give 100%, since CoreIT’s Apache appears to come with a newer OpenSSL (1.1.1) statically linked to it, which means it’ll enable TLS 1.3 by default. And there’s nothing wrong with it… except that, from all the tests I’ve performed, Apache ignores the specified order of the ECDH curves when using TLS 1.3. With the line above, it’ll use secp384r1 for TLS 1.2, but X25519 for TLS 1.3 — and that’s enough to lower the Key Exchange bar to 90% again. 🙁

Given this, there are now several options:

1- disable TLS 1.3 (it’s still relatively new, and most people are still using 1.2): change the line:

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1

to:

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.3

2- force secp384r1: change the “SSLOpenSSLConfCmd Curves” line to simply:

SSLOpenSSLConfCmd       Curves secp384r1

3- ignore this situation altogether: X25519 is considered to be as secure as secp384r1, if not more; even SSL Labs mentions not penalizing it in the future in their (early 2018) grading guide — they just have been a bit lazy in updating their test. It’s quite possible that a future update of their tool will give a full 100% score to X25519.

Anyway, most of this was done for fun, for the challenge of it.  🙂

How to create an RSA certificate

Whether it’s a web server, an email server (two, in fact, assuming you’re using one for SMTP and another for IMAP, as is common), or other types of applications, to have encrypted connections a TLS certificate 1 is required. It can be self-signed (say, if you’ll be the only one needing to access that server), but browsers and email clients will complain (loudly!), therefore, if you want your server to be universally accessed, a “real” certificate is needed, so let’s show how to get one.

This recipe creates an RSA certificate (strongly suggested for, say, Postfix, since you can’t control what other email servers support, so you should go for the most common option); for web servers (accessed by standard browsers), an ECDSA certificate might be a good alternative.

A few notes:

  • There are alternatives (e.g. key sizes, etc.) for basically every parameter I’m using, but I’m not going into those. This is supposed to be as quick and easy as possible, after all.
  • Similarly, I’m not going into Let’s Encrypt; instead, I’m assuming a “normal” certificate authority such as Comodo. If you do use Let’s Encrypt, just use it to create the certificate instead of the “send the CSR to the certification authority” part.
  • The certificate (and server) will be compatible with most browsers and applications (email clients, etc.), but that “most” won’t include any Microsoft browsers on Windows XP (Firefox or Chrome on that abomination of an OS will still work).

Generating the key and the Certificate Signing Request (CSR):

openssl req -nodes -newkey rsa:4096 -keyout myserver.key -out myserver.csr

The command will ask you for details about your server/company (location, etc.). You should fill in every field, although the only mandatory one is “Common Name” (CN), which must match your server’s public name (not necessarily the machine’s name, but the hostname people will type in the browser/email client, such as “zurgl.com” or “mail.something.com“. Note that a certificate for “domain.com” also includes “www.domain.com” (so don’t include the “www.” in the CN), but if the server is reached at “subdomain.domain.com“, then that’s what the CN needs to be.

Ordering and receiving the new certificate:

Now go to a certification authority (CA), order a new certificate, and when asked for a CSR, send them (usually you can just copy and paste it into a text entry window) that myserver.csr file.

If everything went well, then the CA should email you the new certificate in a short while. Typically they send you two files: the certificate itself, and a couple of “intermediate” certificates. Only the first is really needed, but I’ve had best results with concatenating your certificate (first) and the intermediate certs (last) into a single file, which you might call myserver-full.crt .

That file is, to all intents and purposes, your new certificate, and it’s ready to be used in application servers. You can use the same certificate for several services (e.g. an HTTPS website, an SMTP server, an IMAP server), as long as the hostname matches the certificate’s CN (so a certificate for “mail.myserver.com” won’t work for “www.myserver.com“, and vice-versa, but that’s because the names don’t match — not because they’re different services).

Adding TLS encryption (with your shiny new certificate) to internet services:

Here’s a (growing) list of tutorials:

(Note: most of this post’s content originally appeared in a previous one.  The difference is that that one refers to ECDSA certificates, while this one is about the older, more common RSA certificates.)

How to configure TLS encryption in Postfix

Although Postfix (and the SMTP protocol in general) can function without any kind of encryption, enabling TLS it can be a good idea in terms of both security and privacy, so let’s look at how it can be easily done.

We’ll actually be configuring two separate types of encryption:

  • Opportunistic encryption for regular SMTP (port 25), both incoming 1 and outgoing 2. “Opportunistic”, here, means that the server will ask for encryption and use it if the other side also supports it, but if it doesn’t then it’ll work without encryption. Although forcing it might sound tempting for security reasons, in reality many “smaller” servers around the world still don’t support it, so you would be unable to send or receive mail to/from those. Opportunistic TLS at least means that most “big” email services (e.g. Gmail) will communicate with you (and you with them) with encryption.
  • Mandatory encryption for the submission service (port 587). This is used by your own users (even if it’s just you) to send mail through your server, and typically has different restrictions (e.g. it’s authenticated, but on the other hand it can be used to send mail to the outside (unlike incoming port 25 which doesn’t, as that would make it an open relay)). Both the users’ authentication that would otherwise be in clear text, and the fact that only a limited number of users (your users, too) will be accessing that port through email clients (never other email servers that you don’t control) make forcing encryption here a no-brainer.

NOTE: naturally, this is not an exhaustive Postfix tutorial. I’ll be assuming you already have a working server, and just want to add TLS encryption to it. Also, some parts may not apply to your case.

1. Have/get a TLS certificate for your email host’s public name

If you don’t already have one, see how to create a new TLS certificate. We’ll be using an RSA certificate here (instead of going with ECDSA) since unlike, say, a web server accessed by standard browsers, you can’t control what other email servers support 3, therefore it’s best to choose the most common option. 4

Note that the certificate’s CN must match your email host’s public name (e.g. “mail.domain.com“). That will also probably correspond to (one of) the domain’s MX records.

2. Postfix configuration

Again, I’ll be assuming your non-TLS Postfix is already working fine.

Put your certificate and key in /etc/postfix (for instance). For this example, I’ll be calling them myserver-full.crt and myserver.key.

In /etc/postfix/main.cf, add the following lines:

# TLS configuration starts here

tls_random_source = dev:/dev/urandom

# openssl_path=/usr/local/libressl/bin/openssl
# uncomment and edit the above if you're using a different "openssl" than the system's
# (in this case, LibreSSL)

# SMTP from your server to others
smtp_tls_key_file = /etc/postfix/myserver.key
smtp_tls_cert_file = /etc/postfix/myserver-full.crt
smtp_tls_CAfile = /etc/postfix/myserver-full.crt
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtp_tls_mandatory_protocols=!SSLv2,!SSLv3
smtp_tls_protocols=!SSLv2,!SSLv3
smtp_tls_loglevel = 1
smtp_tls_session_cache_database =
    btree:/var/lib/postfix/smtp_tls_session_cache

# SMTP from other servers to yours
smtpd_tls_key_file = /etc/postfix/myserver.key
smtpd_tls_cert_file = /etc/postfix/myserver-full.crt
smtpd_tls_CAfile = /etc/postfix/myserver-full.crt
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
smtpd_tls_protocols=!SSLv2,!SSLv3
smtpd_tls_loglevel = 1
smtpd_tls_session_cache_database =
    btree:/var/lib/postfix/smtpd_tls_session_cache

# TLS configuration ends here

And in /etc/postfix/master.cf, assuming you already have a “submission” section a bit like this:

submission  inet  n     -       n       -       -       smtpd
    -o smtpd_etrn_restrictions=reject
    -o smtpd_sasl_auth_enable=yes
    -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
    -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
    -o smtpd_helo_restrictions=permit_mynetworks,permit

Add to that section:

  -o smtpd_tls_security_level=encrypt

The above forces encryption for the submission service (remember that it’s not required for normal SMTP, it’s just desired).

In short:

  • your server tries to connect to others using TLS, but falls back to an unencrypted connection if the other side doesn’t support encryption;
  • other servers can connect to yours using TLS, but if they don’t attempt it then the connection will be unencrypted;
  • your users *must* use encryption (and authentication) to send mail through your server.

Restart your server, and check the logs: you should be getting mentions of TLS now. For instance, an email server starting an encrypted connection to yours looks like:

Sep 6 14:25:58 drax postfix/smtpd[22727]: Anonymous TLS connection established from lists.openbsd.org[192.43.244.163]: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)

Questions? Suggestions?

(And what about securing mailbox access? Enjoy: How to configure TLS encryption in Dovecot)

How to create an ECDSA certificate

Whether it’s a web server, an email server (two, in fact, assuming you’re using one for SMTP and another for IMAP, as is common), or other types of applications, to have encrypted connections a TLS certificate 1 is required. It can be self-signed (say, if you’ll be the only one needing to access that server), but browsers and email clients will complain (loudly!), therefore, if you want your server to be universally accessed, a “real” certificate is needed, so let’s show how to get one.

In this tutorial, I’ll be creating an ECDSA certificate with an EC key, instead of the more usual RSA type; ECDSA is more modern and is theoretically more secure even with smaller keys. If you need an RSA certificate (which I’d recommend for Postfix, for instance, since you can’t control what other email servers around the world support, so, you should go for the most common option; for web servers, ECDSA is fine), see How to create an RSA certificate.

A few notes:

  • There are alternatives (e.g. key sizes, etc.) for basically every parameter I’m using, but I’m not going into those. This is supposed to be as quick and easy as possible, after all.
  • Similarly, I’m not going into Let’s Encrypt; instead, I’m assuming a “normal” certificate authority such as Comodo (which supports ECDSA certificates; if you choose another, you should make sure of that in advance). If you do use Let’s Encrypt, just use it to create the certificate instead of the “send the CSR to the certification authority” part.
  • The certificate (and server) will be compatible with most browsers and applications (email clients, etc.), but that “most” won’t include any Microsoft browsers on Windows XP (Firefox or Chrome on that abomination of an OS will still work).

Generating the key and the Certificate Signing Request (CSR):

openssl ecparam -genkey -name secp384r1 | openssl ec -out myserver.key

openssl req -new -key myserver.key -out myserver.csr

The second command will ask you for details about your server/company (location, etc.). You should fill in every field, although the only mandatory one is “Common Name” (CN), which must match your server’s public name (not necessarily the machine’s name, but the hostname people will type in the browser/email client, such as “zurgl.com” or “mail.something.com“. Note that a certificate for “domain.com” also includes “www.domain.com” (so don’t include the “www.” in the CN), but if the server is reached at “subdomain.domain.com“, then that’s what the CN needs to be.

Ordering and receiving the new certificate:

Now go to a certification authority (CA), order a new certificate, and when asked for a CSR, send them (usually you can just copy and paste it to a text entry window) that myserver.csr file.

If everything went well, then the CA should email you the new certificate in a short while. Typically they send you two files: the certificate itself, and a couple of “intermediate” certificates. Only the first is really needed, but I’ve had best results with concatenating your certificate (first) and the intermediate certs (last) into a single file, which you might call myserver-full.crt .

That file is, to all intents and purposes, your new certificate, and it’s ready to be used in application servers. You can use the same certificate for several services (e.g. an HTTPS website, an SMTP server, an IMAP server), as long as the hostname matches the certificate’s CN (so a certificate for “mail.myserver.com” won’t work for “www.myserver.com“, and vice-versa, but that’s because the names don’t match — not because they’re different services).

Adding TLS encryption (with your shiny new certificate) to internet services:

Here’s a (growing) list of tutorials:

(Note: most of this post’s content originally appeared in a previous one.  The reason for its creation is that I’m planning several other TLS-related posts where a certificate will be required and, to avoid repeating the “how to create a certificate” instructions in every single one of them, I’d rather have this post to link to when needed.)

How to set up an Nginx HTTPS website with an ECDSA certificate (and get an A+ rating on SSL Labs)

Most people who maintain web servers (or email servers, or…) have probably had to deal with SSL/TLS certificates in the past, but the process is, in my opinion, typically more complex than it should be, and not that well documented, typically forcing the user to consult several tutorials on the web so that they can adapt parts of each for their needs. Since I had to renew a couple of certificates last week, I thought about writing a quick and easy tutorial for a particular case: HTTPS on Nginx (although the certificate itself could, of course, be used for other services; right now I have one I’m using for IMAP (Dovecot) and SMTP (Postfix) as well). For extra fun, I’ll be creating/using an ECDSA certificate with an EC key, instead of the more usual RSA type; ECDSA is more modern and is theoretically more secure even with smaller keys.

A few notes:

  • There are alternatives (e.g. key sizes, etc.) for basically every parameter I’m using, but I’m not going into those. This is supposed to be as quick and easy as possible, after all.
  • Similarly, I’m not going into Let’s Encrypt; instead, I’m assuming a “normal” certificate authority such as Comodo (which supports ECDSA certificates; if you choose another, you should make sure of that in advance). If you do use Let’s Encrypt, just use it to create the certificate instead of the “send the CSR to the certification authority” part.
  • The certificate (and server) will be compatible with most browsers, but that “most” won’t include any Microsoft browsers on Windows XP (Firefox or Chrome on that abomination of an OS will still work).
  • If you already have a certificate (whether ECDSA or not, whether from Let’s Encrypt or not) and you’re here just for the A+ rating on SSL Labs, skip to “Setting up Nginx” below.

Generating the key and the Certificate Signing Request (CSR):

openssl ecparam -genkey -name secp384r1 | openssl ec -out myserver.key

openssl req -new -key myserver.key -out myserver.csr

The second command will ask you for details about your server/company (location, etc.). You should fill in every field, although the only mandatory one is “Common Name” (CN), which must match your server’s public name (not necessarily the machine’s name, but the host name people will type in the browser, such as “zurgl.com“. Note that a certificate for “domain.com” also includes “www.domain.com” (so don’t include the “www.” in the CN), but if the server is reached at “subdomain.domain.com“, then that’s what the CN needs to be.

Ordering and receiving the new certificate:

Now go to a certification authority (CA), order a new certificate, and when asked for a CSR, send them (usually you can just copy and paste it to a text entry window) that myserver.csr file.

If everything went well, then the CA should email you the new certificate in a short while. Typically they send you two files: the certificate itself, and a couple of “intermediate” certificates. Only the first is really needed, but I’ve had best results with concatenating your certificate (first) and the intermediate certs (last) into a single file, which you might call myserver-full.crt . Put it somewhere Nginx can access (e.g. /etc/nginx), and also the key you generated earlier (in this example, myserver.key). You don’t need the CSR there, by the way; it was just needed for ordering the new certificate.

Setting up Nginx:

This is, of course, not a complete Nginx tutorial (that would take a lot more space than a single post), just a simple recipe for configuring an HTTPS site with your new certificate, and have it be secure (and get a great score at SSL Labs, too). I’m assuming you can take care of all the non-HTTPS bits.

So, inside a virtual host, you need the server section:

server {
        listen 443 ssl http2; # if your nginx complains about 'http2', remove it, or (better yet) upgrade to a recent version
        server_name mysite.com # replace with your site, obviously

        # all the non-HTTPS bits (directories, log paths, etc.) go here

        # the rest of the recipe -- see below -- can go here
}

See the last comment? OK, let’s begin adding stuff there (I won’t indent any configuration lines from now on, but they look better if aligned with the rest of the configuration inside the { } ).

ssl_certificate /etc/nginx/myserver-full.crt; # the certificate and the intermediate certs, as seen above...
ssl_certificate_key /etc/nginx/myserver.key; # ... and the key

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS:!3DES';
# the above comes from Mozilla's Server Side TLS guide

add_header X-Content-Type-Options nosniff;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;	# basic defaults.

Great, you now have a basic HTTPS server! I suggest you try it now: save the configuration, restart Nginx, and test it. Access the URL with a browser (and check if the browser doesn’t complain about the certificate: if it did, something went wrong), and run it through SSL Labs’ Server Test. Check if it complains about something; if so, something needs fixing (ask in the comments, maybe I or someone else can help).

If all went well, you probably got a decent score, maybe even an A. But how to get an A+, like I promised at the beginning?

An A+ score on SSL Labs’ Server Test:

As of now (September 2017), SSL Labs only asks for one more thing: “HTTP Strict Transport Security (HSTS) with long duration”. HSTS is a mechanism to tell browsers: “this site should be accessed through HTTPS only; if you attempt to connect through normal HTTP, then deny access”. This information is actually cached by the browser, it’s not the server that refuses HTTP connections (though it’s, of course, possible to configure Nginx to do just that — or simply do 301 redirects to the equivalent HTTPS URL. But I digress…). In short, it protects against downgrade attacks. As for the “with long duration” part, that’s simply the time that browsers should cache that information.

IMPORTANT: don’t proceed until you have the basic HTTPS site running, and SSL Labs reporting no problems (see the previous section)!

ALSO IMPORTANT: if you *do* want your site to be accessible through both HTTP and HTTPS, stop here, as the rest of the configuration will make it HTTPS-only! That means, however, giving up on the A+ score.

So, add the following:

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

Restart Nginx, try SSL Labs’ test again. Did you get an A+?

If not, or you have any questions, feel free to ask.

For extra fun: have your Nginx use LibreSSL or OpenSSL 1.1.x, and enjoy a few more modern ciphers (look for “ChaCha20” on SSL Labs) without any configuration changes from the above.

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).