DNSSEC Key Rollover with BIND

In a previous article I covered the basic steps to make DNSSEC work at the most minimal level. While it works, it's not a good idea to just set and forget. Like all things security it needs regular maintenance. In the case of DNSSEC your keys need to be rolled regularly.

The Problem

DNSSEC keys are purposely short. They need to fit in a DNS query, which is largely limited to 512 bytes for historical reasons. The relative shortness of the keys (per the algorithm) means it takes a manageable amount of time to break on modern hardware. RSA-768 can be broken in 1500 compute-years on a modern PC. If we throw cloud computing into the mix, something around a quarter to half a million cloud virtual machines could break an RSA-768 key in a day. Using AWS it would cost about $32 million currently. As with all security, you should evaluate your situation to determine the appropriate level of protection necessary: would someone spend $32m just to hack your DNS? Probably not. There are alternatives on the horizon. DNSCurve is a promising example of an ECC upgrade to the DNSSEC principles, but no such proposal is standardized or widely deployed.

Longer key lengths take much longer to break. But there are two undeniable facts. First, it's possible for someone with the right resources to break RSA keys in a timespan that might be useful to the attacker. As mentioned above it takes significant resources, well out of the reach of your average hacker, so this is not an imminent problem. Second, computing power is expanding, it will take less and less resources in the future. Quantum computers are not as far off as some people think. Already we have 4-qubit processor, still a far cry from the thousand or so we would need to completely break RSA and ECC. D-Wave is making a slightly different quantum processor, in arrangements up to 512-qubits. Eventually technology will make breaking RSA and ECC trivial, though that is likely decades away still.

The Solution

The immediate and relatively easy solution is to rotate your keys. Even with great advances in technology it will always take time to break a key. Configure your systems to automatically rollover keys now and the worst you will have to do in the intermediate future is change the time period of the rollover. RFC 4641 discusses a couple rollover schemes, but only one resonates with me and applies to most situations - the Pre-Publish Key Rollover (PPKR) scheme.
Remember that there are two keys in a key set which is used to sign a zone. A KSK is used to sign other keys. A ZSK is used to sign the individual records within the zone. In PPKR you have at least two valid key sets in your zone at any given time, and usually three. The first is the key set that is currently signing your zone; this one is required so it should come as no surprise. The second key required by the scheme is the next keys that will be used to sign the zone. There is a bit of wiggle room here as the "next" keys only need to be included at least the "expire" time from the SOA record of the domain. This guarantees that RFC compliant DNS servers will be able to cache a copy of the new signing key so that requests made after the rollover will be verifiable by the previous key. This forms a chain of authority over time which could be verified by resolving DNS servers. The third key would be the previous key, and should be kept in the domain at least as long as the "minimum" time from the SOA record. This guarantees that a cached DNS record, signed by the previous key set, is still verifiable after the rollover as the key set is still available.

PCN Scheme

The BIND tools have built-in rollover functionality, however that functionality does not work with this rollover scheme; the tools demand a more complicated scheme utilizing additional automation and logic. While the aforementioned description is accurate, it's also a bit complicated. To make the PPKR scheme a bit easier I'm introducing my PCN Scheme simplification. First pick a rollover time frame, it has to be longer than the "expire" time from the SOA. As a default, my zones have an "expire" time of one week. I'll eventually want to automate this rollover via a cron job. Given that, I'm going to pick one calendar month as my rollover time. Within each time frame I will publish three ZSK keys - the ZSK key previously used, the ZSK currently used to sign the zone, and the ZSK key that will be used in the next time frame. Similarly when KSK keys are rolled the key for the next time frame will appear in the zone a month before it is used, and the previous key will appear for a month after.

I could have picked a shorter time frame, since the "expire" time from the SOA record is only a week I could have used that. This only slightly more complicated, but largely unnecessary. With a 768-bit (or more) ZSK and 2048-bit KSK it should be safe to use the ZSK for a month, and the KSK for a year without any reasonable risk of the keys being broken.

When signing a zone for the first time we will need to generate a current KSK and ZSK similar to what we did in the previous article. In addition we'll need to generate a "next" ZSK and specify the various dates for publishing, activation, inactivation, and deletion. I'm also going to keep all the keys in the default keys subfolder of BIND's installation (by default in /etc/named) for sanity sake. Cron will be running the rollover script on the 1st of each month, hence the -v1d in the following date commands. Finally, I'm going to create a couple symlinks with human readable names to help keep my sanity.

Creating the initial ZSK

The following is a segment from the script I've been developing to manage DNSSEC key rollovers. The script itself really isn't ready to be released, but I will post it someday when it has a bit more polish.
KEYDIR="/etc/namedb/keys"
INADATE=`date -v1d -v+1m +%Y%m%d`
DELDATE=`date -v1d -v+2m +%Y%m%d`

newkey=`dnssec-keygen -a RSASHA1 -b 768 -n ZONE \
        -I ${INADATE} \
        -D ${DELDATE} \
        -K ${KEYDIR}  \
        ${1}`

ln -s ${newkey}.key     ${KEYDIR}/K${1}.+ZSK+current.key
ln -s ${newkey}.private ${KEYDIR}/K${1}.+ZSK+current.private
What the above does... Setting the key directory variable is straight forward. The next two lines compute dates, mainly the Inactivation and Deletion dates for the key. The key's creation, publication, and activation dates will all be set to whatever time you run the command. It then runs the dnssec-keygen command, generating a 768-bit RSA key pair with SHA1 hash, for signing a zone, passing the dates calculated above, the key directory to dump the output, and finally the name of the domain. After that it symlinks the generated keys. If you already generate an intial key without specifying inactivation and deletion dates you can modify and existing key with the dnssec-settime command to modify existing keys. it takes the same -I and -D style arguments with the keyfile as the final argument.

Creating the "next" ZSK

Creating a new "next" key follows a very similar structure as creating the initial key. The main difference is that we will specify the publication and activation dates so that they coincides exactly with "previous" key's deletion and the "current" key's inactivation dates respectively. Before we generate the new key however, we need to update our symlinks:
if [ -f ${KEYDIR}/K${1}.+ZSK+retired.key ]; then
        rm ${KEYDIR}/K${1}.+ZSK+retired.*
fi

mv ${KEYDIR}/K${1}.+ZSK+current.key     ${KEYDIR}/K${1}.+ZSK+retired.key
mv ${KEYDIR}/K${1}.+ZSK+current.private ${KEYDIR}/K${1}.+ZSK+retired.private

mv ${KEYDIR}/K${1}.+ZSK+next.key        ${KEYDIR}/K${1}.+ZSK+current.key
mv ${KEYDIR}/K${1}.+ZSK+next.private    ${KEYDIR}/K${1}.+ZSK+current.private
Once the symlinks are updated we can generate the new "next" key.
PUBDATE=`date -v1d +%Y%m%d`
ACTDATE=`date -v1d -v+1m +%Y%m%d`
INADATE=`date -v1d -v+2m +%Y%m%d`
DELDATE=`date -v1d -v+3m +%Y%m%d`

newkey=`dnssec-keygen -a RSASHA1 -b 768 -n ZONE \
    -K ${KEYDIR} \
    -P ${PUBDATE} \
    -A ${ACTDATE} \
    -I ${INADATE} \
    -D ${DELDATE} \
    ${1}`

ln -s ${newkey}.key     ${KEYDIR}/K${1}.+ZSK+next.key
ln -s ${newkey}.private ${KEYDIR}/K${1}.+ZSK+next.private
This was almost the same procedure as generating the initial key. Note that this relies on being run every month or the dates become invalid (an issue I will address in the script before releasing it).

Signing the Zone

With the above setup (re)signing the zone becomes a one-liner, which I follow with a quick command to let BIND know the zone has changed.
dnssec-signzone -S -K ${KEYDIR} -o ${1} -N unixtime ${ZONEDIR}/${1}
rndc reload ${1}
The dnssec-signzone command will automatically find the correct keys in the key directory. It will automatically pick-up that just one key should be used to sign the zone, that there are up to two others which should be included in the zone for the reasons mentions above. And it will use the Unix time (seconds since Epoch) as the signed zone's serial (it does not modify the serial of the existing zone file, that should be maintain manually per your usually proceedures). The last line uses rndc to notify BIND that the zone file has changed and should be reloaded. If you have notifications enabled, as is the default, then any secondaries listed in the zone will automatically be notified of the new signed zone with new keys. I have not written the relevant code for updating the KSK keys, but the proceedure will be extremely similar to the one shown here for the ZSK. The only major differences is that with the above scheme I will have three ZSKs in a zone at any given time; while I will only have a maximum of two KSKs, and usually one.

Also, when a new KSK is included in a zone you need to run dnssec-dsfromkey to get the relevant DS records for the new KSK. The new DS record has to be uploaded to the parent zone (usually your registrar will take care of this) before the new KSK is used to sign the ZSKs.

BIND's Tools

I don't use BIND's built-in tools at this time. The tools use a number of days exclusively for time frames. You can not specify that the dates should always be the "first day of the month" for example. With all the different numbers of days in each month, you are guaranteed that the effective dates would be a different day of each month with no easily foreseeable repetition. You could write a script that checks if a key interval has expired and needs to be rolled, but then you would have to run this check script every day, or use a system which allows you to schedule in arbitrary increments.

Essentially the tools do not fit well with simple cron jobs. This is exactly how I, and many others, will be rolling keys for the near future. In the long term I intend on switching to a system backed by database, hopefully all this can be done through that single storage mechanism, and keeping text files will be a thing of the past. Until then, human readable text files and cron jobs are keeping things running smoothly.

I'll update this at some point in the near future with directions for using the built-in tools. Until then there's always the manpage and the hint: use the -S argument.