The Objectives
One of the major hurdles hampering the deployment of HTTPS on smaller websites like this one, has always been the price of certificates. As much I would have liked to get one, I could hardly justify the cost. That’s why a year ago, when the Let’s Encrypt project was announced, with the promise of free domain certificates, I was particularly excited. I decided to migrate my websites as soon as the project reached the public beta phase, last week.
Even if your site does not require HTTPS for security reasons, it is worth considering:
-
to provide additional privacy to your users. Unscrupulous ISPs have started to inject javascript or cookies into third party HTTP pages. They cannot do this with HTTPS pages.
-
to benefit from the performance improvements of HTTP/2. Even though in theory HTTP/2 does not require TLS, all major browsers have decided to boycott the “plaintext” version of the protocol.
In this blog post, I intend to explain how I migrated this Octopress blog hosted with nginx from HTTP to HTTPS and obtained an A+ Grade from SSL Labs.
Intall Let’s Encrypt
This part of the process is very well covered by the Let’s Encrypt documentation. Since there are no packages for Ubuntu Server LTS at the moment, I used the source code approach. In this case, we end up using the letsencrypt-auto
command instead of letsencrypt
directly. letsencrypt-auto
is a wrapper script that ensures that the tool and its dependencies are up to date, prior to running any letsencrypt
commands.
1 2 3 4 5 6 7 8 9 |
|
When I did this, I happened to have some kind of InsecurePlatform Python warning.
1 2 3 4 5 |
|
This warning comes from urllib3. I ignored it and everything was fine!
Getting the Certificate
At this stage you are ready to request a certificate for the website. Let’s Encrypt uses the ACME protocol, which requires serving a bespoke generated file on the web server to confirm ownership of a domain. The letsencrypt
tool aims to automate the whole setup, so that writing ./letsencrypt-auto --apache -d blog.barthe.ph
should be sufficient to get the entire web server up and running, if you happen to use Apache.
Unfortunately, the plugin for nginx is not stable yet and cannot be used. The setup will be slightly more complicated. We will use the --webroot
switch, telling letsencrypt
where to find our web server files, and afterwards, we will have to modify the nginx configuration files ourselves.
Getting the Let’s Encrypt certificate is the easy part:
1
|
|
You are asked to provide an email address (used to warn you when your certificate is about to expire), and agree to the terms and conditions.
Afterwards, you need to manually edit the nginx configuration files using your favorite editor (e.g. nano -w /etc/nginx/sites-available/blog.barthe.ph
). The TLS certificate and the private keys are located in /etc/letsencrypt/live/blog.barthe.ph
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
You’ll notice that in the configuration, I chose to support TLS 1.0 and greater. I wanted to be more aggressive but TLS 1.1 is not that well supported. It does not work on OS X 10.8 for instance. However, I did drop support for the older SSLv3 protocol (which preceded TLS 1.0) and is still supported by most websites. The consequence is that my site is no longer compatible with older versions of Internet Explorer (IE6 to IE8). Supporting SSLv3 without weakening the security of modern TLS 1.2 browsers is becoming increasingly difficult because of downgrade attacks such as POODLE.
In order for the changes to take effect you should invoke sudo service nginx reload
. If you botched the configuration file, the site will continue running with the existing configuration and you should get an error in /var/log/nginx/error.log
.
Once that’s done, you should have a web server running with HTTPS, and that redirects older HTTP URLs to their HTTPS equivalents. W00t!
A+ Grade Security
When testing the Octopress server on different browsers, I got some warnings about mixed content. It means that although my website was configured to serve its content through HTTPS, it still referenced URLs that used HTTP, either for internal URLs or externals URLs (such as Google Fonts, etc…). Chrome in particular was the most strict of all the browsers about this. After spending some time searching and replacing all references to HTTP URLs, I managed to fix all the warnings.
The next step was to edit /etc/nginx/sites-available/blog.barthe.ph
to improve performance, by enabling TLS session caching:
1 2 |
|
Let’s now focus on improving security and get an A+ grade on SSL Labs.
Let’s start by overriding the default prime used for Diffie Hellman. The default prime is usually too small, and also since it is a shared prime, some pre-computed attacks like Logjam are possible. Beware, the openssl
command will take a long time; from 20 minutes to a few hours.
1 2 3 4 |
|
Now, let’s add support for OCSP stapling. OCSP is a mechanism that can be used by the browser to confirm that a certificate has not been revoked. This normally involves an extra connection to the certificate authority, unless the website uses stapling.
1 2 3 |
|
Let’s enable HSTS. This prevents a browser that connected to the website at least once before, from ever accepting an HTTP connection from the same domain.
1
|
|
Finally let’s massage the cipher suite to only use safe and secure ciphers.
1 2 |
|
In order to obtain the list of ciphers separated by :
, use openssl ciphers -V
to dump all available ciphers. Then do as follow:
-
Delete all references to
DES
,3DES
andRC4
, and just keepAES
ciphers. -
Delete all references to
MD5
. -
Put all ciphers that use SHA1, whose name ends with
_SHA
and areSSLv3
at the bottom. -
Put ciphers with larger key lengths on top.
-
Put ciphers with forward secrecy on top. That would be
ECDHE
orDHE
, the DH stands for Diffie-Hellman, and the E for Ephemeral, which means a new key is negotiated for each connection. Stealing the private key will not allow an attacked to decrypt past TLS sessions. -
Favor
GCM
overCBC
as a mode of operation. This is faster, and CBC brought security issues like POODLE in the past. -
Favor Elleptic Curves (anything with
EC
) over plainRSA
,DSA
, orDHE
. Elliptic curves are faster because they require smaller primes than traditional crypto, and as far as we know the security behind the maths is solid.
Here’s my list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
Automatic Updates of the Certificate
As a matter of policy the certificates emitted by Let’s Encrypt are only valid for a short period of 90 days. This explains why the project focuses so much on automation.
It is possible to renew the certificate by executing the following commands, which I added to a /srv/letsencrypt/cron-update.sh
script
1 2 3 |
|
Unfortunately, you’ll find out that the letsencrypt-auto
command fails with the following error:
1
|
|
I found out that the ACME protocol attempts to validate the ownership using HTTP and is unable to follow the HTTP to HTTPS 301 redirection we set up at the beginning. So I had to slightly change my setup in /etc/nginx/sites-available/blog.barthe.ph
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
The new version let all ACME requests to /.well-known/
be served normally over HTTP by nginx, but redirects all the others to HTTPS. Once that change is done and nginx reloaded, executing the letsencrypt-auto
successfully updates the certificate.
The next is to add a CRON job, by typing crontab -e
(as root
). I set up mine to update the certificate once a month, as follow:
1 2 3 |
|
Conclusion
So far I’m relatively happy with Let’s Encrypt. I have chosen to run the letsencrypt-auto
run with root
privileges, but if that bothers you, there is a project named Let’s Encrypt no sudo which aims to prevent that.
For websites with more traffic, you could delay the HTTP to HTTPS redirection until you have fully tested the HTTPS version of the site. This is a good opportunity to fix all mixed URL scheme content issues.
Finally, I published my final /etc/nginx/sites-available/blog.barthe.ph
config file as a Gist.
Edit. I initially made a mistake and wrote return 301 https://blog.barthe.ph
instead of return 301 https://$server_name$request_uri
in the nginx configuration. The consequence is that URLs starting with http
would be redirected to the home page of the blog, instead of the https
counterpart page. This defect was somehow masked by HSTS, because after browsing once to the site, the browser would redirect to the correct pages.