Blog coding article

Security in hickory-dns

Sebastian
Article

Security in hickory-dns

Collaboration with Prossimo on the hickory-dns project

Published on 19 min read

    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 - When true 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 - When true 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 response
    • aa - means it's an authoritative answer
    • rd - (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 - The Hint value indicates a zone with recursive resolver abilities.
    • stores - A block that defines a store type.
      • type - Indicates a recursor configuration
      • roots - 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.