DNSSEC and Letsencrypt

A couple of tweets from a colleague over the Christmas period along with some jobs I’d been saving up made me have another look at the DNS and HTTPS set up for a couple of sites I look after.

DNSSEC

I’ve been meaning to play with DNSSEC for a while, especially since I run my own primary DNS and set up DMKIM to verify my mail server identity (yeah, I know in this day and age of cloud running all your own services is a little quaint, but I like to understand how every thing works).

This a good introduction to DNSSEC if you’re not up to speed. TL;DR DNSSEC allows you to tell when people have been messing with your DNS entries.

To set up DNSSEC you need to create 2 sets of keys, a zone signing key and a key signing key you can create them with the following commands respectively.

$ dnssec-keygen -a NSEC3RSASHA1 -b 2048 -n ZONE hardill.me.uk
Generating key pair..................+++ .............+++
Khardill.me.uk.+007+40400
$ dnssec-keygen -f KSK -a NSEC3RSASHA1 -b 4096 -n ZONE hardill.me.uk
Generating key pair....................................................................................................................................................................................................................................................++ ................................................................................++ 
Khardill.me.uk.+007+23880

Key generation requires a lot of random numbers and these are created from the /dev/random, the values for this are generated from the system entropy so can take a long time on a machine that isn’t doing very much, to help with this I can installed the haveged daemon.

Now I have the 2 sets of keys (public and private) I need to add them to the end of my zone file with the following lines:

$INCLUDE Khardill.me.uk.+007+43892.key
$INCLUDE Khardill.me.uk.+007+23880.key

Now we can use these keys to actually sign the zone with the dnssec-signzone command, the NSEC3 setup takes a salt to help with security. The $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) generates a 16 character random string to act as the salt.

$ dnssec-signzone -A -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) -N INCREMENT -o hardill.me.uk -t hardill.me.uk.db
Verifying the zone using the following algorithms: NSEC3RSASHA1.
Zone fully signed:
Algorithm: NSEC3RSASHA1: KSKs: 1 active, 0 stand-by, 0 revoked
                         ZSKs: 1 active, 0 stand-by, 0 revoked
hardill.me.uk.db.signed
Signatures generated:                       25
Signatures retained:                         0
Signatures dropped:                          0
Signatures successfully verified:            0
Signatures unsuccessfully verified:          0
Signing time in seconds:                 1.129
Signatures per second:                  22.143
Runtime in seconds:                      1.274

This generates 2 files, the first is hardill.me.uk.db.signed which is an updated version of the zone file with the signed hashes included for each entry. The second is dsset-hardill.me.uk. which holds the DS hashes for my 2 keys. The DS entries are hosted by the layer above my domain in the DNS hierarchy so that anybody wanting to verify the data can walk from the Signed root zone up the tree checking the level above before moving on. To get the DS entries into the zone above you normally have to go through your Domain Name Registrar who would in this case ask Nominet (as the keep of the me.uk domain) to host them for me, unfortunately my registrar (I won’t name them here) claims unable to be able pass this request on to Nominet. I need to see if I can get Nominet to do it for me, but I’m not confident so I’m currently in the market for a new registrar, any recommendations welcome.

In the mean time I decided to test the rest of it out on the private TLD I run on my lan. I can get round the need for a DS record by telling Bind to trust my key explicitly using the trusted-keys directive in named.conf. To get this far I followed this set of instructions, which are the manual steps for DNSSEC, there are also instructions to get Bind to automatically sign zones, which is especially useful if you are doing Dynamic DNS updates, this page has instructions for that which I’ll be looking at once I get things sorted to have my DS records hosted properly.

Letsencrypt

The letsencrypt project has a goal to provide free SSL certificates for everybody that are signed by a CA in the collection commonly included in modern browsers. It had been in private beta most of last year, but went into public beta at the start of December so I could sign up. Letsencrypt will generate you a certificate for any domain you can prove you own, you do this using a protocol called ACME and they have written a client to help with this. ACME works over HTTP/HTTPs by placing a hash value at a known location. This can be via an existing HTTP server (e.g. Apache) or by a one built into the client. At home I run my own private CA as it allows me to issue certificates for names on my private TLD and for my IP addresses. I also issue client certificates to authenticate users and having them all with the same CA makes things a little easier. When I get some time I will probably move my domain over to a letsencrypt certificate and only use my CA for client certs. In the mean time I needed to set up access to my Dad’s work mail server so my Brother can send/receive email from his iPhone, this needed to be secure so everything needs to be protected by a certificate. Rather than mess about getting the root CA certs for my private CA on to his phone I decided to use letsencrypt. The mail server doesn’t run a webserver so I used the one built into the client.

$ letsencrypt-auto certonly --standalone  --email admin@example.com --agree-tos -d mail.example.com

The command line arguments are as follows

  • certonly – This tells the client to download the certificate (rather than download it and install it
  • –standalone – This tells the client to use it’s built in HTTP server
  • –email admin@example.com – This tells the client who to email if there is a problem (like a cert expires without being renewed)
  • –agree-tos – This stops the client showing the TOS and prompting you to agree to them
  • -d mail.example.com – This tells the client which host name to create the certificate for, you can specify multiple instances

Certificates and Keys are stored under /etc/letsencrypt/ with the current cert under live/[host name]. I configured Postfix and Dovecot to point to these so that they just need to be restarted to pick up the new certs.

Letsencrypt hand out certificates that are only valid for 90days, this is for a couple of reasons, but mainly it means that any compromised certs only expose people for a short time and they can upgrade the supported algorithms/key strength regularly to keep ahead of new vulnerabilities. The downside to this is that you need to renew the certificate regularly. The client is actually pretty good at letting you automate things using a very similar command to the original version. I’ve set up a cron job to run on the first of every second month that renews a new cert every 60ish day and then restart Postfix and Dovecot, this gives plenty of time to fix anything should there be a problem.

25 15 1 1,3,5,7,9,11 * /home/admin/renew-cert.sh
#!/bin/sh
/home/admin/letsencrypt/letsencrypt-auto certonly --standalone --renew-by-default --email admin@example.com --agree-tos -d mail.example.com
sudo service dovecot restart
sudo service postfix restart

I had to add the following to the sudoers file to get everything to work without prompting for passwords

admin ALL= NOPASSWD: /home/admin/letsencrypt/letsencrypt-auto 
admin ALL= NOPASSWD: /usr/bin/service postfix *
admin ALL= NOPASSWD: /usr/bin/service dovecot *