The other day I made this blog, galencharlton.com/blog/, HTTPS-only. In other words, if Eve want to sniff what Bob is reading on my blog, she’ll need to do more than just capture packets between my blog and Bob’s computer to do so.
This is not bulletproof: perhaps Eve is in possession of truly spectacular computing capabilities or a breakthrough in cryptography and can break the ciphers. Perhaps she works for any of the sites that host external images, fonts, or analytics for my blog and has access to their server logs containing referrer headers information. Currently these sites are Flickr (images), Gravatar (more images), Google (fonts) or WordPress (site stats – I will be changing this soon, however). Or perhaps she’s installed a keylogger on Bob’s computer, in which case anything I do to protect Bob is moot.
Or perhaps I am Eve and I’ve set up a dastardly plan to entrap people by recording when they read about MARC records, then showing up at Linked Data conferences and disclosing that activity. Or vice versa. (Note: I will not actually do this.)
So, yes – protecting the privacy of one’s website visitors is hard; often the best we can do is be better at it than we were yesterday.
To that end, here are some notes on how I made my blog require HTTPS.
Certificates
I got my SSL certificate from Gandi.net. Why them? Their price was OK, I already register my domains through them, and I like their corporate philosophy: they support a number of free and open source software projects; they’re not annoying about up-selling, and they have never (to my knowledge) run sexist advertising, unlikely some of their larger and more well-known competitors. But there are, of course, plenty of options for getting SSL certificates, and once Let’s Encrypt is in production, it should be both cheaper and easier for me to replace the certs next year.
I have three subdomains of galencharlton.com that I wanted a certificate for, so I decided to get a multi-domain certificate. I consulted this tutorial by rtCamp to generate the CSR.
After following the tutorial to create a modified version of openssl.conf
specifying the subjectAltName values I needed, I generated a new private key and a certificate-signing request as follows:
openssl req -new -key galencharlton.com.key \
-out galencharlton.com.csr \
-config galencharlton.com.cnf \
-sha256
The openssl
command asked me a few questions; the most important of which being the value to set the common name (CN) field; I used “galencharlton.com” for that, as that’s the primary domain that the certificate protects.
I then entered the text of the CSR into a form and paid the cost of the certificate. Since I am a library techie, not a bank, I purchased a domain-validated certificate. That means that all I had to prove to the certificate’s issuer that I had control of the three domains that the cert should cover. That validation could have been done via email to an address at galencharlton.com or by inserting a special TXT field to the DNS zone file for galencharlton.com. I ended up choosing to go the route of placing a file on the web server whose contents and location were specified by the issuer; once they (or rather, their software) downloaded the test files, they had some assurance that I had control of the domain.
In due course, I got the certificate. I put it and the intermediate cert specified by Gandi in the /etc/ssl/certs
directory on my server and the private key in /etc/private/
.
Operating System and Apache configuration
Various vulnerabilities in the OpenSSL library or in HTTPS itself have been identified and mitigated over the years: suffice it to say that it is a BEASTly CRIME to make a POODLE suffer a HeartBleed — or something like that.
To avoid the known problems, I wanted to ensure that I had a recent enough version of OpenSSL on the web server and had configured Apache to disable insecure protocols (e.g., SSLv3) and eschew bad ciphers.
The server in question is running Debian Squeeze LTS, but since OpenSSL 1.0.x is not currently packaged for that release, I ended up adding Wheezy to the APT repositories list and upgrading the openssl and apache2 packages.
For the latter, after some Googling I ended up adapting the recommended Apache SSL virtualhost configuration from this blog post by Tim Janik. Here’s what I ended up with:
<VirtualHost _default_:443>
ServerAdmin gmc@galencharlton.com
DocumentRoot /var/www/galencharlton.com
ServerName galencharlton.com
ServerAlias www.galencharlton.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/galencharlton.com.crt
SSLCertificateChainFile /etc/ssl/certs/GandiStandardSSLCA2.pem
SSLCertificateKeyFile /etc/ssl/private/galencharlton.com.key
Header add Strict-Transport-Security "max-age=15552000"
# No POODLE
SSLProtocol all -SSLv2 -SSLv3 +TLSv1.1 +TLSv1.2
SSLHonorCipherOrder on
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+
aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+AESGCM EECDH EDH+AESGCM EDH+aRSA HIGH !MEDIUM !LOW !aNULL !eNULL
!LOW !RC4 !MD5 !EXP !PSK !SRP !DSS"
</VirtualHost>
I also wanted to make sure that folks coming in via old HTTP links would get permanently redirected to the HTTPS site:
<VirtualHost *:80>
ServerName galencharlton.com
Redirect 301 / https://galencharlton.com/
</VirtualHost>
<VirtualHost *:80>
ServerName www.galencharlton.com
Redirect 301 / https://www.galencharlton.com/
</VirtualHost>
Checking my work
I’m a big fan of the Qualsys SSL Labs server test tool, which does a number of things to test how well a given website implements HTTPS:
- Identifying issues with the certificate chain
- Whether it supports vulnerable protocol versions such as SSLv3
- Whether it supports – and request – use of sufficiently strong ciphers.
- Whether it is vulnerable to common attacks.
Suffice it to say that I required a couple iterations to get the Apache configuration just right.
WordPress
To be fully protected, all of the content embedded on a web page served via HTTPS must also be served via HTTPS. In other words, this means that image URLs should require HTTPS – and the redirects in the Apache config are not enough. Here is the sledgehammer I used to update image links in the blog posts:
create table bkp_posts as select * from wp_posts;
begin;
update wp_posts set post_content = replace(post_content, 'http://galen', 'https://galen') where post_content like '%http://galen%';
commit;
Whee!
I also needed to tweak a couple plugins to use HTTPS rather than HTTP to embed their icons or fetch JavaScript.
Finishing touches
In the course of testing, I discovered a couple more things to tweak:
- The web sever had been using Apache’s mod_php5filter – I no longer remember why – and that was causing some issues when attempting to load the WordPress dashboard. Switching to mod_php5 resolved that.
- My domain ownership proof on keybase.io failed after the switch to HTTPS. I eventually tracked that down to the fact that keybase.io doesn’t have a bunch of intermediate certificates in its certificate store that many browsers do. I resolved this by adding a cross-signed intermediate certificate to the file referenced by SSLCertificateChainFile in the Apache config above.
My blog now has an A+ score from SSL Labs. Yay! Of course, it’s important to remember that this is not a static state of affairs – another big OpenSSL or HTTPS protocol vulnerability could turn that grade to an F. In other words, it’s a good idea to test one’s website periodically.