dnssec validation / authoritative server
The delv(1) tool is the standard way to validate DNSSEC signatures. By default it will validate up to the DNS root zone, for which it knows and trusts the DNSKEY. If you want to validate only a part of a chain, you'll need to know a few things.
Regular DNSSEC validation
Using delv is normally as simple as this:
$ delv -t A @1.1.1.1 dnssec.works.
; fully validated
dnssec.works. 3600 IN A 5.45.107.88
dnssec.works. 3600 IN RRSIG A 8 2 3600 20220408113557 20220309112944 63306 dnssec.works. O+...
(1.1.1.1
is the IP of Cloudflare's free recursive resolver. If you
don't know the difference between a recursive and authoritative DNS
server,
you may want to look that up now.)
For unsigned hostnames:
$ delv -t A @1.1.1.1 apple.com.
; unsigned answer
apple.com. 900 IN A 17.253.144.10
And for badly signed hostnames:
$ delv -t A @1.1.1.1 fail01.dnssec.works.
;; resolution failed: SERVFAIL
(The DNSSEC signature for fail01.dnssec.works.
hostname is invalid
by design. This aids in testing.)
Sidenote: we add the period (".") to the end of the hostname so
additional domain searches are not tried — see domain or search in
/etc/resolv.conf. Without it, a system resolver might also try
apple.com.yourdomain.tld
.
Trace DNS lookups with dig
When using dig with +trace
, we can see how a lookup would be
performed by a recursing DNS server (like 1.1.1.1
).
$ dig -t A fail01.dnssec.works. +trace
...
. 6875 IN NS k.root-servers.net.
...
works. 172800 IN NS v0n3.nic.works.
...
dnssec.works. 3600 IN NS ns5.myinfrastructure.org.
...
fail01.dnssec.works. 3600 IN A 5.45.109.212
As you can see, dig will not do DNSSEC validation. The recursor (at
1.1.1.1
) does though. It rightly responded with a SERVFAIL because
there is something wrong.
In this case, the problem being that a DS record for the hostname exists but the nameserver did not provide an RRSIG at all:
$ dig -t DS @ns5.myinfrastructure.org. fail01.dnssec.works. +short
41779 8 2 A73A4215B94FD90C2E6B94BD0513C7A82C4A1E592FD686420573E611 A1D29DE1
$ dig -t A @ns5.myinfrastructure.org. fail01.dnssec.works. +dnssec |
awk '{if($4=="RRSIG"&&$5=="A")print}'
(no response)
Trace DNS lookups with delv
So, how does delv do this validation?
For a valid hostname, things look like this:
$ delv -t A @1.1.1.1 dnssec.works. +rtrace
;; fetch: dnssec.works/A
;; fetch: dnssec.works/DNSKEY
;; fetch: dnssec.works/DS
;; fetch: works/DNSKEY
;; fetch: works/DS
;; fetch: ./DNSKEY
; fully validated
dnssec.works. 3600 IN A 5.45.107.88
delv does not ask other nameservers than the supplied server. But it will ask for all relevant information to be able to verify the hostname signatures.
From the output above, we see that the validation happens bottom-up (contrary to a DNS query which happens top-down): we get a record, look for the DNSKEY, look for the DS, get the next DNSKEY, etc., all the way to the root DNSKEY.
If we try this on an authoritative nameserver — one that explicitly does not recurse — we'll get an error.
$ dig -t NS @1.1.1.1 dnssec.works. +short
ns3.myinfrastructure.org.
ns5.myinfrastructure.org.
$ dig -t A @1.1.1.1 ns3.myinfrastructure.org. +short
5.45.109.212
(We looked up the IP 5.45.109.212
of the authoritative nameserver
manually, so as not to clutter the following output.)
$ delv -t A @5.45.109.212 dnssec.works.
;; chase DS servers resolving 'dnssec.works/DS/IN': 5.45.109.212#53
;; REFUSED unexpected RCODE resolving 'works/NS/IN': 5.45.109.212#53
;; REFUSED unexpected RCODE resolving './NS/IN': 5.45.109.212#53
;; REFUSED unexpected RCODE resolving 'works/DS/IN': 5.45.109.212#53
;; no valid DS resolving 'dnssec.works/DNSKEY/IN': 5.45.109.212#53
;; broken trust chain resolving 'dnssec.works/A/IN': 5.45.109.212#53
;; resolution failed: broken trust chain
As promised, an error.
The nameserver at 5.45.109.212
(that knows dnssec.works.) refuses to
answer requests for which it is not the authority: in this case the DS
record, which is supposed to be in the parent zone. That is correct
behaviour. But that is annoying if we want to test the validity of
records returned by an authoritative nameserver. Can we work around
that?
Creating the delv anchor-file
As we saw above, delv validation starts by looking up the DNSKEY and DS records for the hostname. Your authoritative nameserver will have the DNSKEY, but not the DS record(s):
$ dig -t A @5.45.109.212 dnssec.works. +short
5.45.107.88
$ dig -t DNSKEY @5.45.109.212 dnssec.works. +short
257 3 8 AwEAAePcoDyvYNNO/pM4qLxDQItc...
$ dig -t DS @5.45.109.212 dnssec.works.
...
;; WARNING: recursion requested but not available
The DS record can be found at the nameserver of the parent zone:
$ dig -t NS @1.1.1.1 works. +short
v0n0.nic.works.
$ dig -t DS @v0n0.nic.works. dnssec.works. +short
41779 8 2 A73A4215B94FD90C2E6B94BD0513C7A82C4A1E592FD686420573E611 A1D29DE1
As expected, there it is.
So, in order for us to validate only the behaviour/responses of the
5.45.109.212
nameserver, we have to "pre-load" the DS key. We'll
whip up a small shell script for that:
make_trust_anchors() {
local recursor='1.1.1.1'
local awk='{printf " \"%s\" %s %s %s %s \"%s\";\n",D,N,$1,$2,$3,$4}'
echo "trust-anchors {"
for name in "$@"; do
# DNSKEY: Contains the public key that a DNS
# resolver uses to verify DNSSEC signatures
# in RRSIG records.
delv -t DNSKEY @$recursor "${name%.}." +short +split=0 |
awk -vD="${name%.}." -vN=static-key "/^257 /$awk"
# DS: Holds the name of a delegated zone.
# References a DNSKEY record in the sub-delegated
# zone. The DS record is placed in the parent
# zone along with the delegating NS records.
delv -t DS @$recursor "${name%.}." +short +split=0 |
awk -vD="${name%.}." -vN=static-ds "$awk"
# (delv requires one of the above)
done
echo "};"
}
(By using delv to look up the DNSKEY and DS, we even validate those against our trusted root zone key.)
If we run that snippet, we see this:
$ make_trust_anchors dnssec.works.
trust-anchors {
"dnssec.works." static-key 257 3 8 "AwEAAa+YwrBlCwfJzwmsSK87hKFAm+yz03z5pZwZWpMRJu33+GQLswgZJJX/iOTcjwHdpQXvbAHwNhLtTJ1Pp46b55Q8+zH7DkvqQAJyDTfjVXEyX/745e/5CCPAkVGnaZihj9jqichokDfWkAOJvGxqg9HdqsLmXH3a2GrxFfvwsdSPuBwQmSVzURIyZMMxRC+GH2B+ADGWxJNvrspS0lf9svfkrdMvG4hjLhwNViDSjdx9yb4yRH/+TgvTAkYS/6iB8FLBKnltYtsXuveovKp9Dwq+xllqvUQTkRK90aUQEQa8G8ukecJbIliCrPJH7JK2IaDX8ezoYZ4QMZPc2y/K8FHK0G7EVDcgwskGj/NdfEHUuBdw+Vr9eHu8x6aoU/tnTRI7qI2HmCUqcVLSEGJAmKu4A7lqVP2Xw6cpROGviS6Z";
"dnssec.works." static-ds 41779 8 2 "A73A4215B94FD90C2E6B94BD0513C7A82C4A1E592FD686420573E611A1D29DE1";
};
And — using Bash process subtitution — we can feed that output to delv:
$ delv -t A @5.45.109.212 dnssec.works. \
-a <(make_trust_anchors dnssec.works.) \
+root=dnssec.works.
; fully validated
dnssec.works. 3600 IN A 5.45.107.88
dnssec.works. 3600 IN RRSIG A 8 2 3600 20220408113557 20220309112944 63306 dnssec.works. O++...
Cool. Now we can ask an authoritative server and validate its response.
NOTE: You do need bind9-dnsutils 9.16 or newer for this to work.
Otherwise you'll get a unknown option 'trust-anchors'
.
Validating authoritative server responses with an anchor-file
Using the make_trust_anchors
snippet works for all subdomains served
by the same DNS server:
$ delv -t A @5.45.109.212 www.dnssec.works. \
-a <(make_trust_anchors dnssec.works.) \
+root=dnssec.works.
; fully validated
www.dnssec.works. 3600 IN A 5.45.109.212
www.dnssec.works. 3600 IN RRSIG A 8 3 3600 20220420081240 20220321074251 63306 dnssec.works. 2Pq...
Let's check the invalid one:
$ delv -t A @5.45.109.212 fail01.dnssec.works. \
-a <(make_trust_anchors dnssec.works.) \
+root=dnssec.works.
;; insecurity proof failed resolving 'fail01.dnssec.works/A/IN': 5.45.109.212#53
;; resolution failed: insecurity proof failed
Or another invalid one:
$ delv -t A @5.45.109.212 fail02.dnssec.works. \
-a <(make_trust_anchors dnssec.works.) \
+root=dnssec.works.
;; validating fail02.dnssec.works/DNSKEY: verify failed due to bad signature (keyid=2536): RRSIG has expired
;; validating fail02.dnssec.works/DNSKEY: no valid signature found (DS)
;; no valid RRSIG resolving 'fail02.dnssec.works/DNSKEY/IN': 5.45.109.212#53
;; broken trust chain resolving 'fail02.dnssec.works/A/IN': 5.45.109.212#53
;; resolution failed: broken trust chain
The purpose
Why would you do this?
For one, out of curiosity. But if you're moving your DNS data to a new authoritative server, it is wise to confirm that the signatures are still correct.