To solve the chicken-or-egg problem for DNSSEC from the other side, let’s use an authoritative DNS server (BIND) for signing DNS zones. This tutorial describes how to generate the keys and configure the “Berkeley Internet Name Domain” (BIND) server in order to automatically sign zones. I am not explaining many details of DNSSEC at all, but only the configuration and verification steps for a concrete BIND server.
It is really easy to tell BIND to do the inline signing. With this option enabled, the admin can still configure the static database for his zone files without any relation to DNSSEC. Everything with signing and maintaining is fully done by BIND without any user interaction. Great.
Prerequisites
I am assuming that there is a working BIND server in place which is the authoritative name server for at least one zone. The BIND server must at least run with version 9.9! I am using an Ubuntu Server 14.04.4 LTS with BIND version 9.9.5-3ubuntu0.8-Ubuntu. A few days age I published a blog post covering the secure installation of BIND. Refer to it to see how. For this tutorial, I am using my test domain weberdns.de.
Furthermore, please read some general DNSSEC information on other sites, or simply google it and read the Wikipedia, DNSSEC.net, or RFC (4033, 4034, 4035) articles. I am assuming that you are already familiar with KSK & ZSK, RRSIG, DNSKEY, DS, NSEC & NSEC3. If not, come back again in two hours. Ok, in any case, here it is: DNSSEC signes zones with a private key (ZSK and KSK), while the public key is used to verify the signature. The signature for all resource records are stored in additional records, called RRSIG. The “Next SECure record” (NSEC) is used to prove that no entry exists between two other entries (NXDOMAIN).
Design and Keys
One thing to think about is the placement of the keys. The private keys (for both, the KSK as well as for the ZSKs) should NOT be accessible from the Internet. In the best way, a hardware security module (HSM) would be used for them. However, since this is not that trivial, the concept of a hidden primary can be used. With this way, the private keys are only stored on a not-accessible server, while only the slaves (which must not store the private keys) are queried from the Internet:
Another consideration is the usage of NSEC or NSEC3. For small zones that only store a few well-known names such as ns, www, and mail, NSEC can be used. (Remember the zone walking.) For complex zones with many hidden domain names, the usage of NSEC3 should be considered. However, note that “security through obscurity” is not a good design at all.
BIND Configuration
We will first generate the keys followed by the configuration of BIND. A great (hence: small) tutorial is also available at ISC: Inline Signing in ISC BIND 9.9.0 — Examples. Note that I am using /dev/urandom for my key generation. If you are interested in more details, read this or that. And again, note that you must have at least BIND 9.9 for these features to work.
dnssec-keygen
First, create a new directory for the keys:
sudo mkdir /etc/bind/keys
And generate both, the KSK and the ZSK, within that newly created directory. I am using the RSASHA256 algorithm and 2048 bit for the KSK, while 1024 bit for the ZSK:
sudo dnssec-keygen -r /dev/urandom -a RSASHA256 -b 2048 -K /etc/bind/keys/ -f KSK -n ZONE weberdns.de sudo dnssec-keygen -r /dev/urandom -a RSASHA256 -b 1024 -K /etc/bind/keys/ -n ZONE weberdns.de
Since BIND must be able to read the private key, change the permissions for the group (bind) to read the *.private files:
sudo chmod g+r *.private
Now, the /keys directory should look like that. Note that the private files are NOT readable from others:
weberjoh@jw-vm16-ns0:/etc/bind/keys$ ls -ahl Kweberdns* -rw-r--r-- 1 root bind 431 May 20 09:46 Kweberdns.de.+008+57909.key -rw-r----- 1 root bind 1012 May 20 09:46 Kweberdns.de.+008+57909.private -rw-r--r-- 1 root bind 605 May 20 09:46 Kweberdns.de.+008+63202.key -rw-r----- 1 root bind 1.8K May 20 09:46 Kweberdns.de.+008+63202.private
For example, my public KSK for weberdns.de looks like that:
weberjoh@jw-vm16-ns0:/etc/bind/keys$ cat Kweberdns.de.+008+63202.key ; This is a key-signing key, keyid 63202, for weberdns.de. ; Created: 20160205194002 (Fri Feb 5 20:40:02 2016) ; Publish: 20160205194002 (Fri Feb 5 20:40:02 2016) ; Activate: 20160205194002 (Fri Feb 5 20:40:02 2016) weberdns.de. IN DNSKEY 257 3 8 AwEAAdQXI+UfqnGbHdwJtBBStf0CM8Q8nXcriaOysrpGEDNkM//amUBD 2YMWQ3g+htca6tmzfDJMM5D0gOk5d4IdEmdywkcH+0rGLjNiNEPFnZUp wb6XsYD+ZI/WEuSlp+KCEV8vwELq7VltABrFT+9Rz5CEvokTxonzQrvn fclVHaGwO7cglgALsFqJCqBpvDZvEr2Z6dSepjDnFC9BPS6V8PGSNwAY EtzDp/lrQojkCj28xHj2OCjpr0dIQjdjGFJTHIlc9cYTAHjdPDsC8Eqf s5HcL3ruU6cbqjTn+5Lm4RdXpOWVgemVvIGDnUN+v+Tma/WgtQk7U3sa izNJ3epCv3s=
[Note that I had some problems on Ubuntu with the keys for BIND. This post helped me to solve it. But I am not fully sure whether this is the best way:
sudo nano /etc/apparmor.d/usr.sbin.named
and changing the following line to this:
/etc/bind/** rw,
and a restart of apparmor:
sudo service apparmor restart.]
named.conf.options & named.conf.local
It is really simple to tell BIND that it should sign its zones for the proper usage of DNSSEC. The first step is to set the key-directory and to enable dnssec. (Note that dnssec-enable is “yes” per default. However, I am adding the lines anyway.) Open the named.conf.options file:
sudo nano named.conf.optionsand add the following two lines within the options { } section:
dnssec-enable yes; key-directory "/etc/bind/keys";
The second step is to configure the concrete zone to be signed and maintained. That is: BIND will 1) use the existing zone file and sign it in the background and 2) maintains the signed file in order to update any signatures once they expire.
Open the named.conf.local file in which the zones are declared:
sudo nano named.conf.localand add the following two lines to the zone which should be signed:
auto-dnssec maintain; inline-signing yes;
That’s it!!
Reload the server in order to let BIND sign the zone:
sudo service bind9 reload. The following command checks the correct signing, e.g.:
weberjoh@jw-vm16:/etc/bind$ sudo rndc signing -list weberdns.de Done signing with key 57909/RSASHA256 Done signing with key 63202/RSASHA256
Note that you now have two more files within the /etc/bind/ directory, namely the *.signed and *.signed.jnl files. These are the real zone files that are presented to the world, while the original zone file is the file which is still edited by the admin:
weberjoh@jw-vm16-ns0:/etc/bind$ ls -ahl db.weberdns* -rw-r--r-- 1 root bind 8.0K Aug 23 12:41 db.weberdns.de -rw-r--r-- 1 bind bind 34K Aug 23 12:41 db.weberdns.de.jnl -rw-r--r-- 1 bind bind 26K Aug 23 12:53 db.weberdns.de.signed -rw-r--r-- 1 bind bind 387K Aug 23 12:41 db.weberdns.de.signed.jnl
KSK/DS to Parent Zone
In order to have the full DNS tree secured by DNSSEC, you must transfer your KSK or DS record to your registrar. They will sign it with their private key. This is the main part of DNSSEC because afterwards anyone in the world will be able to validate that you really own the private key for your zone.
If the registrar wants the DS record, obtain it from the KSK keys with the tool dnssec-dsfromkey, such as:
weberjoh@jw-vm16-ns0:/etc/bind/keys$ dnssec-dsfromkey Kweberdns.de.+008+63202. weberdns.de. IN DS 63202 8 1 45B896D0F5735832203F18A600A605072596D368 weberdns.de. IN DS 63202 8 2 2F9112FF344BCDF9C6F9A7548F04184AAE73458C7150DE8FFFA3B7D9893C5EEE
If they want the whole public KSK, simply “cat” it from the keyfile, as already shown above (cat Kweberdns.de.+008+63202.key).
After the registrar has received and signed the KSK, it can be queried within the DNS. For the German DENIC, the KSK is also present in the whois query:
whois weberdns.de Domain: weberdns.de Nserver: ns1.weberdns.de 2003:51:6012:110:0:0:a07:53 80.154.108.230 Nserver: ns2.weberdns.de 213.61.29.182 Dnskey: 257 3 8 AwEAAdQXI+UfqnGbHdwJtBBStf0CM8Q8nXcriaOysrpGEDNkM//amUBD2YMWQ3g+htca6tmzfDJMM5D0gOk5d4IdEmdywkcH+0rGLjNiNEPFnZUpwb6XsYD+ZI/WEuSlp+KCEV8vwELq7VltABrFT+9Rz5CEvokTxonzQrvnfclVHaGwO7cglgALsFqJCqBpvDZvEr2Z6dSepjDnFC9BPS6V8PGSNwAYEtzDp/lrQojkCj28xHj2OCjpr0dIQjdjGFJTHIlc9cYTAHjdPDsC8Eqfs5HcL3ruU6cbqjTn+5Lm4RdXpOWVgemVvIGDnUN+v+Tma/WgtQk7U3saizNJ3epCv3s= Status: connect Changed: 2016-05-17T17:01:42+02:00
The DS record can be queried with a normal DNS request. In the following example, I am using the +trace and +dnssec options in order to see the DS returning from the parent zone (in my case .de) and to see some RRSIG signatures for all answers:
weberjoh@jw-nb12-lx:~$ dig weberdns.de ds +multi +dnssec +trace ; <<>> DiG 9.10.3-P4-Ubuntu <<>> weberdns.de ds +multi +dnssec +trace ;; global options: +cmd . 451618 IN NS i.root-servers.net. . 451618 IN NS d.root-servers.net. . 451618 IN NS l.root-servers.net. . 451618 IN NS g.root-servers.net. . 451618 IN NS h.root-servers.net. . 451618 IN NS j.root-servers.net. . 451618 IN NS b.root-servers.net. . 451618 IN NS e.root-servers.net. . 451618 IN NS c.root-servers.net. . 451618 IN NS a.root-servers.net. . 451618 IN NS m.root-servers.net. . 451618 IN NS f.root-servers.net. . 451618 IN NS k.root-servers.net. . 518356 IN RRSIG NS 8 0 518400 ( 20160902050000 20160823040000 46551 . C2NM1S7/NzZQDbx9H1tWLl1VotgaC0+YG0LtkW6Gfy5l 91WCLoK7RZJfl2joYyU4KnB/4/jRJhV4tbfvB4+GA7pp c0V/v00KLipbCB+i39RFP5WVy9nlBgyXbqy8AqzJKnPH BePme3m4q36RESbBxl3fENeoPPUWtO4/iSbxgsg= ) ;; Received 397 bytes from 192.168.120.22#53(192.168.120.22) in 1 ms de. 172800 IN NS a.nic.de. de. 172800 IN NS f.nic.de. de. 172800 IN NS l.de.net. de. 172800 IN NS n.de.net. de. 172800 IN NS s.de.net. de. 172800 IN NS z.nic.de. de. 86400 IN DS 24220 8 2 ( FFE926ACA67ED94089390250F1F294AC84A6D84F9121 DF73A79E439F42E820C2 ) de. 86400 IN DS 39227 8 2 ( AAB73083B9EF70E4A5E94769A418AC12E887FC3C0875 EF206C3451DC40B6C4FA ) de. 86400 IN RRSIG DS 8 1 86400 ( 20160901170000 20160822160000 46551 . YNFLS/92zyUl+kiulpePRyyqc33LqbAO2PSCQvzBup+Q 5HfhS4KpoXPf7nuLB4BuVMGyjg0pmeFhqpk+yY7Ny8b1 UYoy3jTZ82z0Kx8pxTdkU/25cLBn7HHxGIakOupcd0cF LpalPqLFzmvmWATy6G2CBRKj73mfCBFK4JEr9b0= ) ;; Received 609 bytes from 2001:503:c27::2:30#53(j.root-servers.net) in 165 ms weberdns.de. 86400 IN DS 63202 8 2 ( 2F9112FF344BCDF9C6F9A7548F04184AAE73458C7150 DE8FFFA3B7D9893C5EEE ) weberdns.de. 86400 IN RRSIG DS 8 2 86400 ( 20160830060000 20160823060000 56953 de. pwtHpsI4NLVHDPP43PvQNJqZ0idkAMt0PACex8+d3et7 f2laUbWydCkQwlFymWl+UGelXBk249vENMPUdyb5p0Je 9juE0n+ruvf83YNXNjFoNjMloOwc7qDA0VB/tJr0o/kX 56dkBHzPM4Vba4fvOBXdp4IbM16u7PWXYeMxW50= ) ;; Received 250 bytes from 194.246.96.1#53(z.nic.de) in 9 ms
In the same way, the dnskeys (KSK and ZSK) can be queried:
weberjoh@jw-nb12-lx:~$ dig weberdns.de dnskey +multi ; <<>> DiG 9.10.3-P4-Ubuntu <<>> weberdns.de dnskey +multi ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44554 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;weberdns.de. IN DNSKEY ;; ANSWER SECTION: weberdns.de. 448 IN DNSKEY 256 3 8 ( AwEAAc1G9dfFSBL+/NxRd7//80J8Hx8hjUyaQ7oaJV0t OErEc1+To0BGp3FOB7fQFQ6dJIHzfRD4YO0KoJjaH4P7 fWO6Qs+05U6dnf2BGPy91m/4LHqM+6jzFMMf566GHxuZ YB/8OkKcyRU7IS+vubtYL8jT7hqzqg+XXpQbDRlbtNgP ) ; ZSK; alg = RSASHA256; key id = 57909 weberdns.de. 448 IN DNSKEY 257 3 8 ( AwEAAdQXI+UfqnGbHdwJtBBStf0CM8Q8nXcriaOysrpG EDNkM//amUBD2YMWQ3g+htca6tmzfDJMM5D0gOk5d4Id EmdywkcH+0rGLjNiNEPFnZUpwb6XsYD+ZI/WEuSlp+KC EV8vwELq7VltABrFT+9Rz5CEvokTxonzQrvnfclVHaGw O7cglgALsFqJCqBpvDZvEr2Z6dSepjDnFC9BPS6V8PGS NwAYEtzDp/lrQojkCj28xHj2OCjpr0dIQjdjGFJTHIlc 9cYTAHjdPDsC8Eqfs5HcL3ruU6cbqjTn+5Lm4RdXpOWV gemVvIGDnUN+v+Tma/WgtQk7U3saizNJ3epCv3s= ) ; KSK; alg = RSASHA256; key id = 63202 ;; Query time: 1 msec ;; SERVER: 192.168.120.22#53(192.168.120.22) ;; WHEN: Tue Aug 23 10:27:18 CEST 2016 ;; MSG SIZE rcvd: 464
Tests (AD & RRSIG)
Now, lets check the correct signing of the DNSSEC secured zone. You must use a DNSSEC validating name server, such as BIND or Unbound, as I showed in the past two blog posts. If you do not have a validating server yourself, you can use the Google public DNS server at 2001:4860:4860::8888 or 8.8.8.8 because it already does validating DNSSEC answers. (Note that your authoritative server won’t ever reply with the “ad” flag, but only with the “aa” flag. This is by design. The authoritative server must not prove his own records, because he has direct access to the source and trusts it.)
The main part when using dig is the “ad” flag within the DNS answer. This indicates the “Authentic Data” being transferred. (Ref: IANA, DNS Header Flags.)
weberjoh@jw-nb12-lx:~$ dig mail.weberdns.de ; <<>> DiG 9.10.3-P4-Ubuntu <<>> mail.weberdns.de ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48243 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;mail.weberdns.de. IN A ;; ANSWER SECTION: mail.weberdns.de. 3529 IN A 80.154.108.237 ;; Query time: 1 msec ;; SERVER: 192.168.120.22#53(192.168.120.22) ;; WHEN: Tue Aug 23 10:43:33 CEST 2016 ;; MSG SIZE rcvd: 61
In order to see the signatures (RRSIG), use dig with the +dnssec and +multi options. Now, the DNSSEC related information are displayed, as well as listed within multiple lines with some explanatory notes. Note that my domain name “mail.weberdns.de” has an A and an AAAA record. Therefore this example shows two answers along with two signatures:
weberjoh@jw-nb12-lx:~$ dig mail.weberdns.de any +dnssec +multi ; <<>> DiG 9.10.3-P4-Ubuntu <<>> mail.weberdns.de any +dnssec +multi ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53578 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 4096 ;; QUESTION SECTION: ;mail.weberdns.de. IN ANY ;; ANSWER SECTION: mail.weberdns.de. 3505 IN RRSIG AAAA 8 3 3600 ( 20160915070035 20160816062828 57909 weberdns.de. UQCnmReXJ0jqU7u0frFR9Nj+vTPxlXB/k2h2yc9pNZht GvhNYbRzvslI9P/XH1cBXphqs1YNSntMGcl/GDpR5ZK6 Mn0PN5LGJ+Q/SI/mVgZSSRrnFRaZn1/w0muAcintv8kq Mkhh47WT2QJ2JAiaUcB8P3P/w2X57oeTr1hrawg= ) mail.weberdns.de. 3505 IN AAAA 2003:51:6012:110::15 mail.weberdns.de. 3451 IN RRSIG A 8 3 3600 ( 20160915070035 20160816062828 57909 weberdns.de. aOublailZ4XyYZEO5Wq1ucexwYrIs6LDjiwbav2wBRq1 ALNT4+0w6BqZ+so5xvYpH0bW9bHcXH+oJx7yUZEZi0Ka IlrU96PvMKJGluqpZiuzS8jGhVQgKAfwLqKJOQzBB+4M s21YkDAOu/oDiso42yP9GuFakK1JzZPhE5Z5t6Y= ) mail.weberdns.de. 3451 IN A 80.154.108.237 ;; Query time: 1 msec ;; SERVER: 192.168.120.22#53(192.168.120.22) ;; WHEN: Tue Aug 23 10:44:51 CEST 2016 ;; MSG SIZE rcvd: 431
In both cases, note the AD flag.
Congratulations. You’re done. You have correctly secured your DNS zone!
Tools
There are some great online tools to verify the DNSSEC signatures. Have a look at DNSViz. It reveals and visualizes any DNS name, for example for mail.weberdns.de. Another tool is the DNSSEC Analyzer for iPhone. Here are two screenshots of these tools:
Further Reading
One key word that was not mentioned until now is: key rollover. I have only generated a single ZSK without any further expiration dates or following keys. In an upcoming blogpost I will show how to rollover the ZSK.
Furthermore, there are some cool things to come with DNSSEC: DANE and SSHFP, which I will cover in upcoming posts, too.
And keep in mind that you MUST have accurate date and time settings on your DNSSEC servers. If you’re drifting too much, all signatures will become invalid. Consider the usage of an own NTP server with different sources.
Some more links: