Introduction
About a year ago, the ISRG's Prossimo project approached us to help them to work on a
memory safety initiative called hickory-dns
, a suite of DNS tools to run authoritative name servers and recursive resolvers.
Prossimo is a project of Internet Security Research Group (ISRG), a non profit organization
with focus on Internet security, best known for their service Let's Encrypt, a free and
automated certificate authority (CA) which is used by nearly half a billion web sites and applications.
Ferrous Systems worked with Prossimo last year to develop a memory safe implementation of Sudo/su.
Check out this blog post on the collaboration with our partner
Tweede golf.
We also worked together on rustls
, a TLS library, built as an alternative to OpenSSL.
A three part article series explains the technical details on how the
team achieved their goals.
hickory-dns
, formerly known as trust-dns
, is the third collaboration with ISRG.
It was originally created by Benjamin Fry in 2015 as a way to learn Rust. Since then, the project has
transformed into a mature and production ready set of DNS tools, which users can deploy to run their own DNS services.
One project goal is to reach feature parity with the software unbound
, the
library currently used in Let's Encrypt infrastructure, so that it can be replaced with hickory-dns
in the near future.
Much of the work done on Hickory DNS over the past year was funded by the Sovereign Tech Fund (STF), a program by the German Federal Ministry for Economic Affairs and Climate Action. It funds projects to support the development, improvement and maintenance of open digital infrastructure with the goal to strengthen security, resilience and technological diversity. This project marks the third collaboration on an STF-funded project at Ferrous Systems, which includes the previous ones with Prossimo and another one with our partners at Stackable. Check out the blog post on SBOM support in Rust for more information.
While we started the planning work on hickory-dns
in Fall 2023, we didn't get to business until January 2024
and we will complete the work in December. At Ferrous Systems the work was executed by a team of four people in order to share knowledge
and to balance individual times with other commitments. Our collaboration with the Hickory team followed a lean process,
typical of an open source project, with most of the communication either done via Github issues,
code reviews or on their Discord server. Generally, there was little communication overhead, most contributions were done
in a way that enabled fast feedback from the Hickory team, following an iterative process of discussions and reviews.
Monthly reports were written to give an overview and track progress over the work and the budget to folks at Prossimo.
Improvements to hickory-dns
Prossimo tasked us to expand the existing DNSSEC validation and to add NSEC3 support to the hickory-dns
project.
The repository consists of a suite of tools to run and deploy DNS services, e.g. as an authoritative server.
The Domain Name System, or DNS in short, is a fundamental technology of
the Internet, completing such tasks as converting easily remembered domain names into the website's corresponding IP address.
Even though a number of widely used DNS implementations exist, e.g. unbound
and bind
, the hickory-dns
project
offers an alternative implementation in Rust that leverages its language features, such as memory safety. Older implementations
are mostly written in C, and therefore are more likely to be prone to security vulnerabilities.
DNSSEC is short for Domain Name System Security Extensions, developed in a set of RFCs (most notably in RFC 4035) to improve the security of DNS servers, specifically authentication of data and integrity. NSEC3 support is one important aspect of DNSSEC, with the purpose of solving the issue of domain name enumeration. Both these tasks were quite big in scope, each task spanned multiple months in total until completion.
Our work was threefold: The initial task was to check and learn about the most relevant RFCs, second to understand the Hickory project, its existing support for both DNSSEC and NSEC3 and lastly how to implement and track progress for completion.
Conformance Test Suite
The initial phase consisted of reading the relevant RFCs and determining a good approach on how to tackle the work.
It was quite difficult in the beginning to know which parts of DNSSEC were supported and what it lacked in functionality.
After a technical review of the RFCs and the initial investigation of hickory-dns
, we evaluated our planned approach by
introducing a conformance test suite.
Using this test suite enabled us to confidently makes changes to Hickory.
The main idea of the conformance test suite is to be able to test hickory-dns
, by checking the relevant
DNS setups in isolation, where all tests are mapped to specific sections or paragraphs to their related RFCs. Using
this test suite, all tests ran in isolation, avoiding any communication with publicly accessible DNS servers,
since we would have had limited knowledge of how those servers were actually configured. It further avoided any possible API rate
limitations these DNS servers may impose.
Another purpose of the conformance test suite was to be able to compare the functionality against reference implementations,
specifically unbound
and bind
, to learn their behaviour and to check feature parity. These libraries have been
used in most DNS services for a few decades now. Nevertheless their implementations are not always uniform
in behaviour, because some aspects in the RFCs are marked as MAY
or SHOULD
,
meaning they are somewhat open to interpretation.
When in doubt we tried to follow the behaviour of unbound
, because it's the software used by Let's Encrypt.
Each test marked as ignored in the test suite means there is support in at least one of the reference implementations, but the
functionality or logic has not been implemented in hickory-dns
yet. By using an exhaustive test suite, we were able
to provide a checklist of features any DNS implementation needed to pass in order to provide full RFC support.
After a couple of months of prototyping, we were convinced this approach was feasible and would be a valuable addition
to the hickory-dns
project itself (check the related Pull Request
for more information).
For one, it was easy to track progress on feature support, and, second it helped discover a number of bugs and
differences in behaviour compared to the reference implementations and the RFCs that required further investigation.
DNSSEC Validation
When the tasks were given initially, the DNSSEC-related task was only broadly described.
After discussing the complexity and scope of the issue the team agreed to focus on RFC 4035. This RFC details
the modifications necessary for the Security Extensions. DNSSEC introduces a number of resource records and protocol
changes to add data origin authentication and data integrity to DNS by using signed zones.
DNSSEC answers the question if a chain of domain records can be trusted or is valid. A fair share of these records
were already present in hickory-dns
, including pre-existing validation logic.
Using the conformance test suite we added a number of tests for DNSSEC to cover both roles of Hickory,
as authoritative name server and as recursive resolver. All tests were annotated with Rust's #[ignore]
attribute to mark them as unimplemented in hickory-dns
. After a feature was added or a bug was fixed
in the hickory-dns
library itself, the test got activated afterwards. Once the work was completed, a Pull Request
was opened to check that CI passed all tests, including the conformance test suite.
Hickory as an authoritative name server can be configured
to generate additional DNSSEC-related resource records during start up, such as DNSKEY
, RRSIG
.
A DNSKEY
record either holds information on the key signing key (KSK) or zone signing key (ZSK).
A KSK is used to sign other DNSKEY
records containing the ZSK, while the ZSK is used to sign other records.
A RRSIG
record holds the signature of another DNS resource record.
Authoritative Name Server
To illustrate the DNSSEC features let's set up hickory-dns
as an
Authoritative name server first.
An authoritative name server is a DNS server that has authority over zones and can answer queries for which
it is responsible.
To install the most recent version of hickory-dns
use cargo
:
cargo install hickory-dns@0.25.0-alpha.3 --features=recursor,dnssec-openssl
Hickory uses Cargo features to enable and disable certain functionality. The recursor
feature enables
the recursor
logic, while dnssec-openssl
enables
DNSSEC
support using OpenSSL. Instead of dnssec-openssl
the feature dnssec-ring
can be specified to activate the
alternative cryptographic library, e.g. to support the ED25519 format.
After the installation the hickory-dns
binary is available to start the DNS service. Let's configure Hickory
first to activate the generation of DNSSEC records on startup.
# config.toml
listen_addrs_ipv4 = ["0.0.0.0"]
[[zones]]
zone = "."
zone_type = "Primary"
file = "root.zone"
enable_dnssec = true
[[zones.keys]]
key_path = "zsk.key"
algorithm = "RSASHA256"
is_zone_signing_key = true
This basic configuration consists of the following fields:
listen_addrs_ipv4
- specifies the list of addresses the DNS server will accept connections on.[[zones]]
- A block to define a zone.zone
- The zone to sign, in this case root"."
.zone_type
-Primary
indicates that hickory is the authority.file
- The name of the zone file that contains all DNS records.enable_dnssec
- Whentrue
Hickory generates additional DNSSEC records for all records in the zone file on startup.
[[zones.keys]]
- A block to define a zone key.key_path
- The path to the signing key, e.g.zsk.key
algorithm
- The cryptographic algorithm the key was generated with.is_zone_signing_key
- Whentrue
marks the key as zone signing key.
Please note that enable_dnssec
in this context does not mean DNSSEC validation is active, but rather it's used to generate all relevant
DNSSEC records during startup.
The next step is to provide the zone file itself (e.g. root.zone
). The format of this file is the standard DNS output format.
An example for a zone file can look as follows:
. 86400 IN SOA primary0.example.com. admin0.example.com. 2024010101 1800 900 604800 86400
. 86400 IN NS primary0.example.com.
primary0.example.com. 86400 IN A 127.0.0.1
This file defines a SOA
record (Start of Authority),
a NS
record (Namespace) and an A
record (IPv4 Address).
Before we can start hickory-dns
we need a zone signing key first. We use OpenSSL to generate a sufficiently long
RSA key using:
openssl genpkey -quiet -algorithm RSA -out zsk.key
This generates a new key using the RSASHA256
algorithm and stores the private key in zsk.key
.
To start hickory-dns
using the config file, the example zone file and the private key run:
hickory-dns --port 2345 --debug --config=./config.toml --zone-dir=.
This starts hickory-dns
on port 2345
with debug log level. Feel free to pick a different port, typically port 53
is already
taken by the DNS service of the operating system. The --config
option specifies the location of the config.toml
otherwise it checks the default file path at /etc/named.toml
. The --zone-dir
option specifies the path to check zone
files in, e.g. root.zone
, the default directoy is /var/named
.
The debug log of Hickory should contain output that loads a ZoneConfig
, the authority loads zone records and signs the
zone "."
using the generated zone signing key. The hickory-dns
server should now run and accept DNS queries,
for example via dig
or delv
.
To fetch the A
record for domain primary0.example.com.
use the dig
command:
dig @127.0.0.1 -p 2345 primary0.example.com. +norecurse
which returns:
; <<>> DiG 9.20.2 <<>> @127.0.0.1 -p 2345 primary0.example.com. +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39369
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; OPT=5: 08 0d 0e 0f ("....")
; OPT=6: 08 0d 0e 0f ("....")
;; QUESTION SECTION:
;primary0.example.com. IN A
;; ANSWER SECTION:
primary0.example.com. 86400 IN A 127.0.0.1
;; Query time: 0 msec
;; SERVER: 127.0.0.1#2345(127.0.0.1) (UDP)
;; WHEN: Mon Oct 21 15:15:19 CEST 2024
;; MSG SIZE rcvd: 117
Please note the authoritative name server is configured to not send queries to other servers, therefore the +norecurse
option is passed in. The following lines provide a bit more information on the response:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39369
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
The DNS server responded with status NOERROR
, indicating a valid response. The flags
field is a bitset with
different flags, displayed as qr aa
.
qr
- means it's a query responseaa
- means it's an authoritative answerrd
- (when given) means recursion desired, the name server is allowed to forward the query to other upstream name servers
The dig
CLI sets the +recurse
flag as default, therefore the rd
flag will appear in the response if not deactivated.
In order to check that the DNS server created the associated RRSIG
record to the A
record, use dig
's +dnssec
option to return both records.
dig @127.0.0.1 -p 2345 primary0.example.com. +dnssec +multiline
The +multiline
option will wrap the text to a reasonable width, otherwise the output gets unwieldy.
This will return the associated RRSIG
record in the ANSWERS
section as well.
;; ANSWER SECTION:
primary0.example.com. 86400 IN A 127.0.0.1
primary0.example.com. 86400 IN RRSIG A 8 3 86400 (
20251020134506 20241021134506 57797 .
FyZW3yHIdEfN0eakLvgsZQkzx5MhcLM24h8wNPiEcosX
3TTOr0NwvXAHqtbxYTJssfjR3DZhG3EgBdlZ18FpBKoY
+VA3Vg+NYtuKpGduXU7Dreh3La5L8GlKC6uFc1ay0hR6
qTq8M07JyzlMWE+U6r1n2R9bATKiWufhuDtnoINJbDMi
TwaJ/ZxE7lfttpQ1gUKoNoEcOGkZUP18JlnyXoKrNkVH
DdD0J/K8LTp/lnZ7AuAQ7ixJRNxroth6meeCHAQHNqyL
9H6zKAiSRw4RVi4swodhyzCzn+oXhXjGVDmZlHFz8+QO
S43iTumVhKaI8Fe/8/tgNMGZM+m7Z9N1GA== )
The DNS server has a single zone config for zone "."
. To return the associated DNSKEY
record(s) we can query them:
dig @127.0.0.1 -p 2345 . DNSKEY +dnssec +multiline
The response contains two records in the ANSWERS
section:
;; ANSWER SECTION:
. 86400 IN DNSKEY 257 3 8 (
AwEAAZzIkGf9sTXfFFeHTSNjbw3gr4ESGA5CzPtLKTSW
8rbEpJw2G+goVFRrIS9ieHUna59TEfBkM/8WQ/MVkQQD
pTTP2Rqg/E0aHEBQ2xbQVIveYXcU9absPn+CPjM3+gq0
9bv9CDzxsa0yl9B7xbeAM9V8zXqtXfFaQ3plSUs9Wtqo
nu/mJwEOu8YMiu9K0eZ+Gju1amobaOBXkOwCro7o8wae
MIC0vFjC/ghfEmFAK1V3TFZw/jQXYWG4I6BdULiiMeLL
R6ESPCXMRjBcMiCIPy5WOzQ4iAjpSkLEHqrtc9EwnUCT
C0tmihZPZh3dyy7TgB3YTaHw8KEQhnDmdfjPZpc=
) ; KSK; alg = RSASHA256 ; key id = 57797
. 86400 IN RRSIG DNSKEY 8 0 86400 (
20251020134506 20241021134506 57797 .
Q4CeL96V2NDBJI6jF3wjjLUYrW/jGjOgTuT3D8mRFwPy
0b6suHmIy+1XPSGgYMAu1bpyVUxcpvXSE7DMIO/eYB/E
nA5ArjcuOpKIzN+m75pLOZXb204dD5DptBhgjn04zTDB
ML1rzK5acjp2Lcbo3X5lFABCXpy4diQDZhfCupNVA5JV
mVD2nJ+eXHQXovB1cYyv5/w+1oK/ojZ1BZbMjUBIQjlH
hisc8b5Y+V8fDehau3hIOuSrosJb15ST9J7YNndkt5kT
1nSbAocX3AFWuZEVqwhbou45UAb2NfuvPlbZT5lHReWQ
5E+1JULfak+HDz/blHyBPzALYrOEn0s7MQ== )
One is the DNSKEY
(KSK) and the other the associated RRSIG
for that DNSKEY
record. Nearly all signed records
have an associated RRSIG
record to describe their signature.
Using the +dnsec
and +recurse
flags in dig
we could get all relevant DNSSEC records that are required in order
to validate the chain of trust. It's also possible to declare multiple zone configurations in Hickory's config.toml
.
Recursive Resolver
The second important role hickory-dns
supports, is as a Recursive Resolver.
A recursive resolver is a DNS server that accepts recursive queries and is able to resolve these queries by
fetching additional records from other known authoritative name servers or from its cache. A recursive resolver
can validate the recursive query to answer the question if the chain of trust is valid.
We can configure hickory-dns
to run as a recursive resolver via its config.toml
. In this configuration the [[zones]]
block
contains different fields and values.
[[zones]]
zone = "."
zone_type = "Hint"
stores = { type = "recursor", roots = "/absolute/path/root.hints", dnssec_policy.ValidateWithStaticKey.path = "/absolute/path/trusted-key.key" }
The configuration consists of the following fields:
zone
- The zone to configure.zone_type
- TheHint
value indicates a zone with recursive resolver abilities.stores
- A block that defines a store type.type
- Indicates arecursor
configurationroots
- The file path to the root hints file.dnssec_policy
- Configues the DNSSEC validation policy.ValidateWithStaticKey.path
- The file path to the trusted key used for DNSSEC validation.
The root
path can be used to specify a root hints file. For example the Internet Assigned Numbers Authority (IANA)
provides a few files for the root name servers (see here). IANA is the authority
for the root zone "."
, they are responsible for assigning operators for top level domains (e.g. com
, de
).
Please be aware that both paths, root
and ValidateWithStaticKey.path
, need to be absolute paths.
For our example, we will use the root.hints
file provided by IANA and copy that to a local file.
The last piece is the trusted key. In order to support DNSSEC validation, the ValidateWithStaticKey.path
needs to point
to the file with the right key information. The content is the list of DNSKEY
records for the root zone.
To get these records directly, we can fetch them via dig
.
dig DNSKEY . +answer
returns two DNSKEY
records (abbreviated) in the ANSWERS
section of the response.
. 19347 IN DNSKEY 256 3 8 AwEAAc0SunbHdS0KFEyZbYII/+tzsrNzIwurKxmJA+0fhAYlTPA/5LrM ...
. 19347 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3 ...
Create a new file, e.g. trusted-key.key
, copy the content of the ANSWERS
section into it.
Once the configuration is in place let's start hickory-dns
as recursive resolver.
hickory-dns --port 2345 --debug --config=./config.toml
This runs the DNS server on port 2345
with the resolver configuration. This time the DNS server
can forward requests to the root name servers specified in the root.hints
file.
In order to answer a recursive query the client needs to send
the RD
(Recursion Desired) flag.
Using dig
we can see that for an existing domain no records will be returned if the flag is disabled.
dig @127.0.0.1 -p 2345 example.com. +norecurse
By setting the RD
flag in dig
(default) the query will return the A
record for domain example.com
.
The command
dig @127.0.0.1 -p 2345 example.com. +recurse
returns
; <<>> DiG 9.20.2 <<>> @127.0.0.1 -p 2345 example.com. +recurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30300
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; OPT=5: 08 0d 0e 0f ("....")
; OPT=6: 08 0d 0e 0f ("....")
;; QUESTION SECTION:
;example.com. IN A
;; ANSWER SECTION:
example.com. 3600 IN A 93.184.215.14
;; Query time: 851 msec
;; SERVER: 127.0.0.1#2345(127.0.0.1) (UDP)
;; WHEN: Tue Oct 22 11:09:47 CEST 2024
;; MSG SIZE rcvd: 83
Specifiying the +dnssec
option in the dig
command will additionally return the RRSIG
record.
Check the hickory-dns
log to learn how the recursive query is processed, its DNSSEC validation, and what
other name servers were involved.
The recursive resolver performs DNSSEC validation for each client query to validate the chain of trust.
This validates all records that are involved in the query resolution, and returns the appropriate response.
The DNS server automatically sets the ad
flag
(Authentic Data) in its response.
To return all records even when signature validation fails set the checking disabled
flag CD
using dig
's +cdflag
option.
For example the following query uses dig
to fetch the A
record for a domain that fails DNSSEC validation
dig @127.0.0.1 -p 2345 www.dnssec-failed.org. +dnssec +cdflag
Without the +cdflag
the query would not return any records, because DNSSEC validation fails.
The flags
field in the answer will not contain the authenticated data (AD) bit to indicate there was a
problem with DNSSEC validation.
Another useful tool is delv
to query DNS servers. It can be used to return more information on the
DNSSEC validation process. Let's try the same query as above using delv
:
delv @127.0.0.1 -p 2345 example.com. A
that returns
; fully validated
example.com. 3447 IN A 93.184.215.14
example.com. 3447 IN RRSIG A 13 2 3600 20241102170341 20241012065317 19367 example.com. XMyTWC8y9WecF5ST67DyRUK3Ptvfpy/+Oetha9r6ZU0RJ4aclvY32uKC ojUsjCUHaejma032va/7Z4Yd3Krq8Q==
The important line here is ; fully validated
, the remainig output is similar to dig
, the returned records are the same.
Let's query a record for a domain that does not exist.
delv @127.0.0.1 -p 2345 doesnotexist.com. A
returns
;; no valid RRSIG resolving 'doesnotexist.com/DS/IN': 127.0.0.1#2345
;; broken trust chain resolving 'doesnotexist.com/A/IN': 127.0.0.1#2345
;; resolution failed: broken trust chain
This is a good start, but it doesn't really provide the full picture of what's happening under the hood. To figure
out more about the validation process either check the output of hickory-dns
or alternatively display the
intermediate steps using delv
as well.
To display a brief list of validation steps use the +rtrace
option:
delv @127.0.0.1 -p 2345 example.com. A +rtrace
that ouputs
;; fetch: example.com/A
;; fetch: example.com/DNSKEY
;; fetch: example.com/DS
;; fetch: com/DNSKEY
;; fetch: com/DS
;; fetch: ./DNSKEY
; fully validated
example.com. 2641 IN A 93.184.215.14
example.com. 2641 IN RRSIG A 13 2 3600 20241102170341 20241012065317 19367 example.com. ...
The query returns information on intermediate steps, with the chain of trust fully validated.
To get the full picture including all intermediate DNS queries and responses use the +mtrace
option
delv @127.0.0.1 -p 2345 example.com. A +mtrace
Both roles, the authoritative name server and the recursive resolver can be combined into a single DNS server, which RFC 4035 names as Recursive Name Servers. That means the DNS name server has authority over some zones, but can query additional records recursively from other authoritative name servers.
NSEC3
The last task to highlight here is the added NSEC3 support based on RFC 5155. The DNS Security Extensions introduced
the NSEC resource record for authenticated denial of existence. That means a DNS server could respond with
a record to indicate that the requested domain does not exist. Or if it does, there are no records of the requested
type for this name. NSEC record lists two domains that are
ordered canonically, basically providing both domain names the non-existent domain falls in between.
For example the non-existent domain abc.example.com
could lie
between two existing domains aaa.example.com
and bbb.example.com
. In this way it's fairly easy to
enumerate existing domains using queries to non-existent ones.
NSEC3 was introduced as an alternative to mitigate this side-effect. A query to a non-existent domain returns an NSEC3 record, but it does not list the domain names directly anymore, but rather the next owner name as cryptographic hash, prepended as a single label to the name of the zone. Sometimes several NSEC3 records are necessary to proof the non-existence of a name or a specific record type, especially when the requested name is deeply nested or when wildcard names are involved.
This is the same core idea as NSEC but we're only obfuscating the domain names to make zone enumeration harder. Appendix B of RFC 5155 lists a number of examples how NSEC3 is used.
The following example picks up the configuration of the authoritative name server above, but adds a setting for NSEC3.
listen_addrs_ipv4 = ["0.0.0.0"]
[[zones]]
zone = "."
zone_type = "Primary"
file = "root.zone"
enable_dnssec = true
# New
nx_proof_kind = { nsec3 = { iterations = 10 } }
[[zones.keys]]
key_path = "zsk.key"
algorithm = "RSASHA256"
is_zone_signing_key = true
The new field nx_proof_kind
is part of the zone config.
The value iterations = 10
inside the nsec3
block defines the number of hashing iterations applied to the algorithm.
Currently only the SHA1
hashing algorithm is supported. Optionally a salt
field can be specified as well.
Once hickory-dns
starts with the new configuration, its output contains entries to create NSEC3 records
for the the existing records too. Let's check again the output of the following query using dig
.
dig @127.0.0.1 -p 2345 primary0.example.com. A
The A
record for domain primary0.example.com.
is returned, because the DNS name server has authority
over it as defined in zone file root.zone
. The output is different when the query tries to request
an answer to a non-existent domain.
dig @127.0.0.1 -p 2345 primary1.example.com. A +dnssec +adflag
will return with status NXDOMAIN
and with an empty ANSWERS
section. The AUTHORITY
section on the other hand
contains multiple records, e.g. NSEC3
, RRSIG
. To illustrate one NSEC3
record, here is an example from the section:
;; AUTHORITY SECTION:
g0mpt7h1tti5g0gjmik3k5njcia3n1s8. 86400 IN NSEC3 1 0 10 - (
GQMPMJ04C1K2M91EUKE181POK7RUG3EH )
g0mpt7h1tti5g0gjmik3k5njcia3n1s8. 86400 IN RRSIG NSEC3 8 1 86400 (
20251021130222 20241022130222 38270 .
AcVS0/9jRVYBolzreCBLaL2L3FOl7J2LjIh41BFryn+t
kE0tKm7JqsvI7T6MHVQDct8wvt2Cn9gTp0j+Lx9g3U9t
Pl4vIEd0FgAuKWIocyotCcuv0LOlGMiYk9NvNitAVtbS
GVjxcXaIhn43xKl4hjXV0ebWc97dLNgPZIY6GC0aj8V6
CeSJg8owi5VvPH2w0YzBRb2oB+RvsgBYxp9SFoORk/+5
8kbq21sRtdKZ6bqyXDzBSsfZU2hCWpOSxknO11kEZpZZ
KChQZKx3m2fl72DKA99PIBgMzeIfkN0i1p8S8NNM09VW
oiAmDIp81MlKF2mlc7bO1O8Qx3I62+dbbQ== )
The NSEC3
uses a calculated hash as the prefixed label to zone "."
, the associated signature record RRSIG
is
returned alongside.
This is just one of the possible scenarios where authenticated denial of existence is useful. However, the full extent of RFC 5155 was implemented to cover all the scenarios.
For example: in a zone example.com.
where www.example.com.
has records if we request extra.www.example.com.
we may receive a set of three NSEC3 records showing that:
* www.example.com.
exists
* extra.www.example.com.
does not exist
* and *.www.example.com.
does not exist either
Depending on what names and record types are defined in a zone, the exact combination of NSEC3 records received can differ for different DNS requests, but the end goal is the same: proof the non-exiistence of a record without exposing other names in the zone. The full extent of RFC 5155 was implemented to cover all the scenarios.
Future Work
There are a number of RFCs that can further improve the security of hickory-dns
.
We would like to highlight RFC 8914, which introduces Extended DNS errors (EDE)
to DNSSEC validation in a recursive resolver. Query responses would contain one or more additional error codes
in case DNSSEC validation fails, which improves diagnostics to clients and other DNS servers.
Other implementations offer some support for EDE codes, for example the unbound
library.
Recently we discovered the website extended-dns-errors.com that maintains a list
of misconfigured authoritative test servers. Every test has their own subdomain that can be queried to check that the DNSSEC
validation logic returns the expected EDE response code. A separate end-to-end test suite has been added in
this PR to hickory-dns
to check all these scenarios.
Each test queries the name server of a subdomain using unbound
and hickory-dns
, then compares
their status and flags from the returned responses. If these fields match hickory-dns
is on par with unbound
.
Currently hickory-dns
does not supprt EDE return codes yet, but the test suite is in place to expand it.
Another issue we would like to mention is that hickory-dns
currently accepts configurations that don't make much
sense and require a more ergonomic approach. For example this proposal
suggests changes on how the DNSSEC feature for the different roles can be improved.
Outcome
Over the last few months DNSSEC validation (RFC 4035) and NSEC3 support (RFC 5155) have been successfully expanded
or added to hickory-dns
to improve its security and resilience. During the development we learnt a lot about DNS
and its related RFCs. By introducing the conformance test suite we were able to show concrete progress on the
implementations themselves. As a side effect a fair number of bugs and issues have been found due to the test coverage,
not all were related to DNSSEC or NSEC3, which we consider an extra benefit for the project.
The work with the Hickory team since the beginning was very pleasant, most issues were discussed immediately. We would like to thank Benjamin Fry, Dirkjan Ochtman and Marcus Butler for their valuable feedback, patience and extensive knowledge. Overall, our contributions profited immensely from their reviews and attention to detail. Discussions were productive and often led to improvements on a technical level.
We would also like to thank Sarah Gran and Josh Aas from ISRG, who supported the collaboration in ways that allowed us to do our best work efficiently.
Thanks to Jorge Aparicio, Andrei Listochkin, Christian Poveda and Sebastian Ziebell who worked on this project from Ferrous Systems. A special kudos goes to Jorge who came up with the conformance test suite which enabled us to work in a more structured and efficient way.