DNSSEC with NLnetLabs' LDNS and NSD

There are two TV series adaptations of Johannes Mario Simmel's "Es
muss nicht immer Kaviar sein": the first with [1]O.W. Fischer in 1961,
and the second with [2]Siegfried Rauch in 1977. I think I saw the
second, but I digress: the name of the book occurred to me when
thinking about this blogticle.

[3]NLnet Labs make DNS software (and more), and amongst their
best-known utilities are probably the recursive [4]Unbound server and
the authoritative [5]NSD server, one which was originally written to
drive a root DNS server.

I'm occasionally asked whether [6]NSD can be used to serve a
DNSSEC-signed zone (the answer is `yes'), but NSD isn't a signer: it
requires other utilities to actually sign zones which it can then
serve. I've occasionally mentioned [7]ldns, specifically when I wrote
about [8]using a SmartCard-HSM for DNSSEC and again when I wrote about
[9]DNSSEC signing with an offline KSK.

But let's look at the task of signing one or more zones and serving
them with [10]NLnet Labs programs.

signing

We start with an unsigned zone file, here for the domain example.aa
(aa is a [11]user-assigned code element.)

$TTL 3600
@       SOA     nsd  jp  1 3H 1H 1W 1H
       NS      nsd
nsd     A       192.0.2.43
       AAAA    2001:0db8:0:0:0202:b3ff:fe1a:8329

I'd like to sign this zone with a single Combined Signing Key (CSK),
also called a Single Signing Key (SSK), and I chose [12]algorithm
ECDSA Curve P-256 with SHA-256 (number 13) for the key, and I specify
the key will have flags 257 on it (KSK), though that is not strictly
necessary:

$ ldns-keygen -a13 -k example.aa
Kexample.aa.+013+22967

$ ls -l K*
-rw-r--r--  1 jpm  staff   94 Nov  6 13:16 Kexample.aa.+013+22967.ds
-rw-r--r--  1 jpm  staff  153 Nov  6 13:16 Kexample.aa.+013+22967.key
-rw-------  1 jpm  staff  114 Nov  6 13:16 Kexample.aa.+013+22967.priv
ate

The key filenames begin with a capital K (for "key"), are followed by
the zone name ("example.aa", the key algorithm (13), the key ID or key
tag, and the file extension:
 * the file containing the Delegation Signer record(s)
 * the public key (later queryable via the DNSKEY resource record)
 * the private key

I keep the private key safely (ldns has removed group and other
permissions, and I tend to chmod 400 the file to ensure I don't
overwrite it accidentally).

The *.ds file contains the Delegation Signer (DS) record which must be
added to the parent zone. I can reproduce this file at will with a
different [13]ldns utility, and with the -n option I decide whether I
want the DS printed to stdout or not:

$ ldns-key2ds -2 Kexample.aa.+013+22967.key
Kexample.aa.+013+22967
$ cat Kexample.aa.+013+22967.ds
example.aa.     3600    IN      DS      22967 13 2 cfb29a4218cf608d93c
08c0d14c3e315b9ab85797bf20269b820ed3a2c1d8f47

$ ldns-key2ds -n -2 Kexample.aa.+013+22967.key
example.aa.     3600    IN      DS      22967 13 2 cfb29a4218cf608d93c
08c0d14c3e315b9ab85797bf20269b820ed3a2c1d8f47

Having generated the required key(s), I can now sign the zone. I
specify -u to have the SOA serial number set to the Unix epoch time,
the -o origin (i.e. zone name) of the zone I'm signing, and the key
filename of the key [14]ldns should use to sign the zone (without its
file extension).

$ ldns-signzone -u \
               -o example.aa \
               example.aa \
               Kexample.aa.+013+22967

$ ls -l *.signed
-rw-r--r--  1 jpm  staff  1762 Nov  6 13:22 example.aa.signed

ldns-signzone uses NSEC by default. Use -n to have it create NSEC3
records compliant with RFC 9276 -- SHA-1, no extra iteration, empty
salt and opt-out not set.

The ldns-read-zone utility is very practical. I can use it to strip
DNSSEC data from a zone file - something I do when I want to
concentrate on the actual zone data. Here I've used it to print out
just one record type.

$ ldns-read-zone -E SOA example.aa.signed
example.aa.     3600    IN      SOA     nsd.example.aa. jp.example.aa.
1667737361 10800 3600 604800 3600

Finally, to ensure the signed zone is valid, I can use another utility
to verify that, though I prefer using something written by a different
developers to ensure they agree that the zone is valid.

$ ldns-verify-zone -V4 example.aa.signed
Zone is verified and complete

$ validns -p all example.aa.signed
example.aa.signed:3: there should be at least two NS records per name
example.aa.signed:5: No KSK found

[15]validns is a bit of an older utility but still quite useful. Here
it complains that my zone has only one NS record (correct, it does),
and that no KSK was found. This latter has to do with validns not
realizing that a zone can be signed with a single key. I ignore this
warning because I know the zone is otherwise valid.

serving

I configure [16]NSD to load my zone from the *.signed file and serve
it:

zone:
   name: "example.aa"
   zonefile: "example.aa.signed"
   provide-xfr: 127.0.0.1/8 NOKEY

After reloading the server I query it and see the signatures (I use
+nocrypto to omit the dreary base64-encoded stuff):

$ dig @::1 example.aa SOA +dnssec +multi +nocrypto +norec
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65367
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;example.aa.            IN SOA

;; ANSWER SECTION:
example.aa.             3600 IN SOA nsd.example.aa. jp.example.aa. (
                               1667737361 ; serial
                               10800      ; refresh (3 hours)
                               3600       ; retry (1 hour)
                               604800     ; expire (1 week)
                               3600       ; minimum (1 hour)
                               )
example.aa.             3600 IN RRSIG SOA 13 2 3600 (
                               20221204122241 20221106122241 22967 ex
ample.aa.
                               [omitted] )

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Sun Nov 06 12:37:16 UTC 2022
;; MSG SIZE  rcvd: 188

Signatures (RRSIG) have a default validity of 30 days, but I can
easily change that. I've created [17]eitime which prints a time stamp
in UTC YYYYMMDDHHMMSS for RRSIG expiration/inception times and makes
my life easier:

$ ldns-signzone -e $(eitime -z 180) ...

Don't forget to re-sign and reload the zone in a timely manner before
its signatures expire. Cron is your friend.

parent

There's one thing missing in order to complete the chain of trust to
my zone: submit the DS record to the parent, and all I can do here,
unfortunately, is to say "that's your problem". The method by which
you do this varies widely.

Regarding caviar: signing with [18]ldns and serving with [19]NSD might
well be very appealing to you, and I know people for whom this is
true.

further reading

 * [20]DNSSEC (nsd servers), using ldns and NSD

References

  1. https://de.wikipedia.org/wiki/Es_mu%C3%9F_nicht_immer_Kaviar_sein_(Film)
  2. https://en.wikipedia.org/wiki/Es_muss_nicht_immer_Kaviar_sein_(TV_series)
  3. https://nlnetlabs.nl/
  4. http://unbound.net/
  5. https://www.nlnetlabs.nl/projects/nsd/about/
  6. https://www.nlnetlabs.nl/projects/nsd/about/
  7. https://nlnetlabs.nl/projects/ldns/about/
  8. https://jpmens.net/2021/06/04/using-a-smartcard-hsm-for-dnssec-with-bind/
  9. https://jpmens.net/2022/09/22/dnssec-signing-with-an-offline-ksk/
 10. https://nlnetlabs.nl/
 11. https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#AA
 12. https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
 13. https://nlnetlabs.nl/projects/ldns/about/
 14. https://nlnetlabs.nl/projects/ldns/about/
 15. https://github.com/tobez/validns
 16. https://www.nlnetlabs.nl/projects/nsd/about/
 17. https://github.com/jpmens/eitime
 18. https://nlnetlabs.nl/projects/ldns/about/
 19. https://www.nlnetlabs.nl/projects/nsd/about/
 20. https://w00t.eu.org/m/dnssec.html