Thoughts

30 Jul: DNSSEC Negative Answers and Telling Lies

DNSSEC adds verfication to DNS ensuring that the response you've received is legitimate, i.e. it hasn't been modified in some way. It does this by adding a bunch of extra record types (RRSIG, DNSKEY, DS, NSEC) to provide signing of records, and to sign the zone. This follows a hierarchical chain of trust where the parent domain validates the signing key of the child chained right up to the root domain. One of the requirements along with ensuring an existing record hasn't been tampered with along the way to the client (e.g. a request for www.example.com has the IP 1.1.1.1 instead of the real 9.9.9.9), is to validate whether a record exists at all. NSEC records do this by returning the alphabetically previous and next valid names effectively defining the gaps in the DNS entries. For example if you have only two A records f.example.com and s.example.com, for DNSSEC you'd have an NSEC record which says previous: f.example.com, next: s.example.com. When a client queries for "nonexistant.example.com" the response would include that NSEC record. (There an some extra logic to deal with working out what the record type is, but I'll stick to the one topic here). Here's how that looks on ietf.org:
# dig ietf.org. +dnssec
ietf.org.		1800	IN	NSEC	_dmarc.ietf.org. A NS SOA MX TXT AAAA RRSIG NSEC DNSKEY SPF
So this tells us the alphabetically first domain name under ietf.org is _dmarc.ietf.org. We can continue walking through the domain by requesting the next NSEC record:
# dig NSEC _dmarc.ietf.org +dnssec
_dmarc.ietf.org.	1800	IN	NSEC	ietf1._domainkey.ietf.org. TXT RRSIG NSEC
So we know here there's no records alphabetically between _dmarc.ietf.org and ietf1._domainkey.ietf.org.
# dig ietf1._domainkey.ietf.org. +dnssec
ietf1._domainkey.ietf.org. 1787	IN	NSEC	apps.ietf.org. TXT RRSIG NSEC
and so on. Now this does solve the problem. As those NSEC records are signed, I can tell for sure which records do and don't exist. However this also reveals the complete set of domain names. There are many cases where this may not be desirable. A solution, NSEC3, is to hash the next and previous entries to obfuscate them. This is implemented for example in the .com domain:
# dig thisisadomainthatdoesntexist.com +dnssec
CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 900 IN NSEC3 1 1 0 - CK0Q1GIN43N1ARRC9OSM6QPQR81H5M9A NS SOA RRSIG DNSKEY NSEC3PARAM
So this is telling us that there's no record for thisisadomainthatdoesntexist.com, but the client can't tell what the previous and next records are. Is is of course theoretically possible that you could crack that hash with enough time, so even this may not be enough. An option is for the DNS server to lie. The DNS server could dynamically compute an alphabetically previous and next value and return those[1]. Or it could just not bother and always return the same lie every time[2] - which is what Cloudflare does:
# dig thisisadomainthatdoesntexist.cloudflare.com +dnssec
thisisadomainthatdoesntexist.cloudflare.com. 300 IN NSEC \000.thisisadomainthatdoesntexist.cloudflare.com. RRSIG NSEC
Here, they've returned an obviously false value "\000" for the next domain. For the client, this doesn't matter - it still has been able to verify that there's no entry for thisisadomainthatdoesntexist.cloudflare.com.
© 2017