转载一篇很不错的文章,对dig这个命令的基本使用。dig在分析网络情况还是非常有用的。

Introduction

dig is a DNS lookup utility, and a flexible tool for interrogating DNS name servers. (dig manual page)

I would like to show in this post, how DNS and DNSSEC basically works, using dig. It is from a user point of view, I am not going to talk about things like DNS Zone Transfer (AXFR), DNS NOTIFY or DNS UPDATE. I will use dig in this post, so it is also going to be a very brief tutorial of dig as well.

I will not explain the details of DNSSEC in this post, as I am already writing another one for that.

The reference for all information in this post are DNS and DNSSEC related RFCs and the manual page of dig.

Domain Name System (DNS)

Many many years ago, before DNS, all the information, like name to address mappings, was kept in a single file that have been shared with all the computers connected to the Internet (ARPANET). As you might guess, this became quickly unmaintainable. DNS is the solution to this problem.

DNS has three major components:

  • Domain Name Space and Resource Records
  • Name Servers
  • Resolvers

Domain Name Space

Domain Name Space is a (distributed) tree data structure, or in other words, an hierarchical distributed database. Each node in this tree has a label. The root node has an empty label, and no other label can be empty. A label can only contain ASCII letters A-Z, digits 0-9, and hyphen -. Labels are case-insensitive.

The domain name of a node in the Domain Name Space, by convention, is the list of labels, of the label of this node and the labels of its parents going upwards, reading from left to right in the same way. When a domain name need to be presented to user or read from a user input, the labels are joined by a dot.

Domain Name Space for metebalci.com:

         +-----------+ 
         |empty label|
         +-----------+
               |
               |	                   
               V 
            +-----+
            | com | 
            +-----+
               |
               V
          +-----------+
          | metebalci |
          +-----------+

Below each node contains the domain name (not the label) of each node:

         +-----------+ 
         |           |
         +-----------+
               |
               |	                   
               V 
            +------+
            | com. | 
            +------+
               |
               V
          +------------+
          | metebalci. |
          +------------+

Each node in the Domain Name Space can keep information in the form of resource record sets containing resource records.

Resource Record (RR)

A resource record (RR) has five components:

  • owner: of this record. It is the domain name of the node which keeps this information.
  • type: a 16-bit value, specifying what this resource record is about
  • class: a 16-bit value, identifying a protocol
  • TTL: a 32-bit Time-To-Live value, in units of seconds, indicating how long this resource record can be cached
  • rdata: data of the resource

A real example is:

metebalci.com. 300 IN A 151.101.1.195
metebalci.com. 300 IN A 151.101.65.195

This RR has:

  • owner: metebalci.com.
  • TTL: 300 (seconds)
  • type: A (Address)
  • class: IN (Internet)
  • rdata: 151.101.1.195 and other record is 151.101.65.195

These two records form a resource record set (RRset). All resource records in a resource record set has the same name, type and class.

There are very few classes, and the important ones are IN (Internet) and ANY (used only in queries).

There are many types, a few important ones are:

  • SOA: Start of (a zone of) Authority, identifies the start of a zone
  • NS: DNS server responsible (authoritative) for a zone
  • A: IPv4 host address
  • AAAA: IPv6 host address

The contents of the resource data (rdata) depends on the resource type. As seen above, for a resource type=A, rdata contains an IPv4 address.

Name Servers

Name servers are server programs, which hold partial information about the Domain Name Space. Since the Domain Name Space is a distributed database, each name server may have complete and authoritive information only about a small part of the Domain Name Space, and possibly cache some other data in the Domain Name Space.

Resolvers

Resolvers are user programs, which are used to send queries to name servers, thus extract information from the Domain Name Space.

Iterative vs. Recursive Queries

Because DNS is a distributed database, and a name server has only partial information, there are two ways to extract information:

  • Iterative approach: where the resolver/client repeatedly queries different servers until it finds the answer
  • Recursive approach: where the resolver/client sends the query only to a single server, but the server repeatedly queries different servers until it finds and returns the answer to the resolver

I think it is now common to use the recursive approach. For example, you use a public DNS like Google’s, and it always return the answer if there is any.

DNS Protocol

The ascii diagrams in this section are taken from RFC 1035.

A resolver (user program, such as dig) sends a query to a name server, and server returns a response. This is called a DNS lookup, since the purpose of the query is looking up for a specific information stored as a resource record.

By default, DNS lookups are done over UDP at port number 53. It is assumed that a query and a response fits into a single UDP datagram. It is usually not a problem for a query, but the responses can be large. If this happens, DNS lookup can be retried over TCP.

DNS message always has the same format, it does not matter if it is a query or response. Its structure is:

+------------+
| Header     | 
+------------+
| Question   | the question for the name server
+------------+
| Answer     | RRs answering the question
+------------+
| Authority  | RRs pointing toward an authority
+------------+
| Additional | RRs holding additional information
+------------+
  • A fixed length Header, containing an ID, flags, information about the other sections
  • Question section contains query domain name (QNAME), query type (QTYPE) and query class (QCLASS). Response contains the same Question section as the Question.
  • Answer section may contain a list of resource records, each containing QNAME, QTYPE, QCLASS, TTL, the length of RDATA and RDATA
  • Authority section may contain a list of resource records, pointing to authoritative name servers
  • Additional section may contain a list of resource records, that may contain additional information to answers.

and the details of the Header section is:

  1  1  1  1  1  1
  0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA| Z|AD|CD|   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

ID is an 16-bit value, that is kept same in the response. Because UDP is connectionless, this is needed.

Flags:

  • QR: 0=Query, 1=response
  • OPCODE: 0000=query
  • AA: Authoritative Answer (set by the server)
  • TC: message is TrunCated (set by the server)
  • RD: Recursion Desired (set by the client and copied to the response)
  • RA: Recursion Available (set by the server)
  • AD: Authenticated Data, DNSSEC flag (set by the server)
  • CD: Checking Disabled, DNSSEC flag (set by the client, copied to the response)
  • RCODE, 4-bits: 0000=NOERROR.

Each count field (QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT) shows the number of records in each section.

dig

The information above will be very helpful to understand the output of dig.

dig supports many options and a few arguments, but there is a default for everthing. So the most simple usage is without any arguments and options:

$ dig

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>>
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32684
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			516341	IN	NS	m.root-servers.net.
.			516341	IN	NS	b.root-servers.net.
.			516341	IN	NS	c.root-servers.net.
.			516341	IN	NS	d.root-servers.net.
.			516341	IN	NS	e.root-servers.net.
.			516341	IN	NS	f.root-servers.net.
.			516341	IN	NS	g.root-servers.net.
.			516341	IN	NS	h.root-servers.net.
.			516341	IN	NS	i.root-servers.net.
.			516341	IN	NS	a.root-servers.net.
.			516341	IN	NS	j.root-servers.net.
.			516341	IN	NS	k.root-servers.net.
.			516341	IN	NS	l.root-servers.net.

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Dec 07 13:21:37 CET 2018
;; MSG SIZE  rcvd: 239

The default values above:

  • default QNAME=.(root domain), QTYPE=NS, default QCLASS=IN
  • default name server that the query has been sent to is the system default name server (e.g. Linux /etc/resolv.conf)
  • you only see the answer, query is hidden by default

Keep in mind that the default QTYPE=NS is used only for the root domain, if you specify a domain name, then the default QTYPE is A.

Recursive Query

Lets specify all these defaults explicitly, enable displaying the query +qr and ask for a specific domain name metebalci.com:

$ dig @8.8.8.8 metebalci.com IN A +qr

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 metebalci.com IN A +qr
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37069
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 5f798fcd227baee1
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; QUERY SIZE: 54

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37069
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; ANSWER SECTION:
metebalci.com.		299	IN	A	151.101.1.195
metebalci.com.		299	IN	A	151.101.65.195

;; Query time: 33 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 13:28:03 CET 2018
;; MSG SIZE  rcvd: 74

It displayed the query first, then the answer.

The defaults above are:

  • By default, rd (recursion desired) and ad (authenticated data) are set.
  • By default, EDNS (Extension mechanisms for DNS) is used, and the default EDNS version is set to 0 (also called EDNS0). EDNS is a mechanism to be able to add extra information to a DNS message, since the header is fixed, nothing can be added there. EDNS defines a pseudo-resource record (pseudo-RR) (it is called pseudo because it is actually not a resource record like others) called OPT.
    • The OPT pseudo-RR above sets the largest UDP payload sender can support. OPT pseudo-RR also extends the RCODE in the Header from 4-bits to 12-bits by carrying 8-bits in the OPT pseudo-RR. OPT pseudo-RR is carried in the Additional section.
    • Another option of EDNS is DNS COOKIE Option, that you can also see in the dig output. Client cookie is generated randomly from client IP, server IP and a minimum 64-bit secret only known to client. Client cookie is always 8-bytes. Server cookie is generated randomly from client IP, a secret known only to server and the client cookie. Server cookie is minimum 8-bytes, maximum 32-bytes. DNS cookies are used to provide some protection against certain attacks. Cookie is added before, because +cookie option is added by default, if you specify +nocookie, this is not sent. Google Public DNS (8.8.8.8) does not respond with a server cookie.

You can see in the answer, four flags are set: qr, rd, ra, ad.

  • QR is actually not a flag like others, if it is 0 (so not set), it is a query, here it is QR=1, so this is a response.
  • RD (recursive desired): set by the client, if the server is not authoritative, it will do a recursive query to find the answer.
  • RA (recursive available): set by the server, indicating recursive query is supported.
  • AD (authenticated data): set by the client to ask server to return the answer and authority sections only if all records are validated as secure. This is a DNSSEC feature.

Lets see the effect of Recursion Desired (RD) by not setting it with +nordflag:

$ dig @8.8.8.8 metebalci.com IN A +qr +nordflag

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 metebalci.com IN A +qr +nordflag
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59269
;; flags: ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 88c8ed8cc552cbd0
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; QUERY SIZE: 54

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 59269
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; Query time: 11 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 13:48:46 CET 2018
;; MSG SIZE  rcvd: 42

Because Google’s Public DNS (8.8.8.8) is not authoritative for metebalci.com, if you do not ask for recursion, it basically returns nothing, actually SERVFAIL. Independent of RD is set or not, server replied with Recursion Available (RA).

Iterative Query

If you do not want to use recursive query, what can you do ? First, you should find the authoritative name server of the domain name. In order to do that, you can start from the root domain.

You saw above the root name servers, so you can ask the name servers of com to the root name servers:

$ dig @a.root-servers.net com NS +qr +nordflag
...

;; AUTHORITY SECTION:
com.			172800	IN	NS	e.gtld-servers.net.
com.			172800	IN	NS	b.gtld-servers.net.
com.			172800	IN	NS	j.gtld-servers.net.
com.			172800	IN	NS	m.gtld-servers.net.
com.			172800	IN	NS	i.gtld-servers.net.
com.			172800	IN	NS	f.gtld-servers.net.
com.			172800	IN	NS	a.gtld-servers.net.
com.			172800	IN	NS	g.gtld-servers.net.
com.			172800	IN	NS	h.gtld-servers.net.
com.			172800	IN	NS	l.gtld-servers.net.
com.			172800	IN	NS	k.gtld-servers.net.
com.			172800	IN	NS	c.gtld-servers.net.
com.			172800	IN	NS	d.gtld-servers.net.

;; ADDITIONAL SECTION:
e.gtld-servers.net.	172800	IN	A	192.12.94.30
e.gtld-servers.net.	172800	IN	AAAA	2001:502:1ca1::30
b.gtld-servers.net.	172800	IN	A	192.33.14.30
b.gtld-servers.net.	172800	IN	AAAA	2001:503:231d::2:30
j.gtld-servers.net.	172800	IN	A	192.48.79.30
j.gtld-servers.net.	172800	IN	AAAA	2001:502:7094::30
m.gtld-servers.net.	172800	IN	A	192.55.83.30
m.gtld-servers.net.	172800	IN	AAAA	2001:501:b1f9::30
i.gtld-servers.net.	172800	IN	A	192.43.172.30

...

This also shows a good use of Additional section. Since you need the IP addresses to query the name servers anyway, in order to avoid a second query, the IP addresses of the names servers are also returned in the Additional section. It was not queried (A records are not queried), so this is an additional information.

As you know the name servers of com, now you can ask for the nameservers of metebalci.com:

$ dig @a.gtld-servers.net metebalci.com NS +qr +nordflag
...

;; AUTHORITY SECTION:
metebalci.com.		172800	IN	NS	ns-cloud-b1.googledomains.com.
metebalci.com.		172800	IN	NS	ns-cloud-b2.googledomains.com.
metebalci.com.		172800	IN	NS	ns-cloud-b3.googledomains.com.
metebalci.com.		172800	IN	NS	ns-cloud-b4.googledomains.com.

;; ADDITIONAL SECTION:
ns-cloud-b1.googledomains.com. 172800 IN AAAA	2001:4860:4802:32::6b
ns-cloud-b1.googledomains.com. 172800 IN A	216.239.32.107
ns-cloud-b2.googledomains.com. 172800 IN AAAA	2001:4860:4802:34::6b
ns-cloud-b2.googledomains.com. 172800 IN A	216.239.34.107
ns-cloud-b3.googledomains.com. 172800 IN AAAA	2001:4860:4802:36::6b
ns-cloud-b3.googledomains.com. 172800 IN A	216.239.36.107
ns-cloud-b4.googledomains.com. 172800 IN AAAA	2001:4860:4802:38::6b
ns-cloud-b4.googledomains.com. 172800 IN A	216.239.38.107

...

Now you know the authoritative name servers of metebalci.com. You can ask other resource records (like A):

$ dig @ns-cloud-b1.googledomains.com metebalci.com A +qr +nordflag
...

;; ANSWER SECTION:
metebalci.com.		300	IN	A	151.101.1.195
metebalci.com.		300	IN	A	151.101.65.195

...

DNSSEC Checking Disabled (CD) and Authenticated Data (AD) Flags

You can see an example of Checking Disabled (CD) and Authenticated Data (AD) with a domain specifically configured with wrong DNSSEC signatures, that is dnssec-failed.org.

$ dig @8.8.8.8 www.dnssec-failed.org

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 www.dnssec-failed.org +qr
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51067
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 97d64f27b6c7173b
;; QUESTION SECTION:
;www.dnssec-failed.org.		IN	A

;; QUERY SIZE: 62

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 51067
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.dnssec-failed.org.		IN	A

;; Query time: 204 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 14:01:57 CET 2018
;; MSG SIZE  rcvd: 50

Because the records that would be returned by this query cannot be validated, it returns no records. In order to get the records even if validation fails, you need to use Checking Disabled (CD) bit:

$ dig @8.8.8.8 www.dnssec-failed.org +qr +cdflag

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 www.dnssec-failed.org +qr +cdflag
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20866
;; flags: rd ad cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 5290b2efedb75b2c
;; QUESTION SECTION:
;www.dnssec-failed.org.		IN	A

;; QUERY SIZE: 62

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20866
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.dnssec-failed.org.		IN	A

;; ANSWER SECTION:
www.dnssec-failed.org.	7199	IN	A	68.87.109.242
www.dnssec-failed.org.	7199	IN	A	69.252.193.191

;; Query time: 120 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 14:04:31 CET 2018
;; MSG SIZE  rcvd: 82

With CD set, you receive the records, but pay attention to flags in the answer, there is no Authenticated Data (AD). This indicates that there is a validation problem with one or more records returned.

DNSSEC Resource Records

DNSSEC defines four new record types:

  • DNSKEY: keeps the public key to verify RRSIGs
  • DS: keeps the digest of a DNSKEY RR
  • RRSIG: keeps the digital signature of an RRset
  • NSEC: used for authenticated denial existence, meaning to show an RRset is not part of a signed zone

As you see above, metebalci.com IN A query did return only A records. If you want to get RRSIG record for the A RRset, you have to use +dnssec option:

$ dig @8.8.8.8 metebalci.com +qr +dnssec

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 metebalci.com +qr +dnssec
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37727
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
; COOKIE: a263795b817be1b1
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; QUERY SIZE: 54

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37727
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; ANSWER SECTION:
metebalci.com.		299	IN	A	151.101.1.195
metebalci.com.		299	IN	A	151.101.65.195
metebalci.com.		299	IN	RRSIG	A 8 2 300 20181227144044 20181205144044 59764 metebalci.com. z6FupNLEU/8OcB3rNMkVqVaan05Xu89T8hV6+IC7LGjWPtrD+TlNJd8D cGeq8xJLR8b1Q+gBK0QSxpGvk89GaCTjNtMGHLBBdgpyV4syFv2BNzK7 iAJhA8QJ6i5xVFJdzMSsn3WvQvN1W71sirt8+56r1nQ47aVkBSLJoZKP lgw=

;; Query time: 41 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 14:09:43 CET 2018
;; MSG SIZE  rcvd: 247

As you see, now it returns two A records, and an RRSIG record associated with this RRset. There is an RRSIG for each RRset, not for individual RRs, that is why you have only one RRSIG above.

You might have noticed, in the OPT section, there is a difference after using +dnssec, it shows flags: do. do means DNSSEC OK, meaning the resolver supports DNSSEC, indicating to the server that it should return DNSSEC record types as well. That is why you received now the RRSIG record.

You can also query DNSKEY records:

$ dig @8.8.8.8 metebalci.com DNSKEY +qr

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 metebalci.com DNSKEY +qr
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41000
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 1cbc06703eaef210
;; QUESTION SECTION:
;metebalci.com.			IN	DNSKEY

;; QUERY SIZE: 54

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41000
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;metebalci.com.			IN	DNSKEY

;; ANSWER SECTION:
metebalci.com.		299	IN	DNSKEY	256 3 8 AwEAAfHph+LlPW5tn2zrQ/ka0OUjZGm+S84N+vJxkJ4WT7uBhK87c+bS HR35Bfq3hxjEGAe+qIbTrUQVIWrIVfZjKbD/2gh9CsDlOa3na2tv1QVC cguj0VUQp4dsFubpQXr3nvf2hV8bNO9AOO2adS/SMYuRcQNpyvBAMINL KxtKrTSx
metebalci.com.		299	IN	DNSKEY	257 3 8 AwEAAY9y/KiOTaMf0EqtlfC0PgMxladfiA9NpLtsECO7wk7Br7WkOnib s/qNExkPLAHktgyguhSKcWcAsueh61j1phkGzAh9Y/LtIe8oyf+PhL/q kl0TIjcZpdLVsWOX3bEGNFADosVKEe8QTDyb0z093CSe83Ho5HKryi9T OiVL145CPF7YxtPpm7UcfUtX0XQEDHYUhH64S77Pp64ebVvxjeIaY9wT 8vv3G+zxaL0V+EVCDGr3WlWU/H5ypHVD3D4gIOQRBlJKTxg4Ov9N2t/E O7xVMdbsxrHNDyafNbtQ4v+9GRV8dMkPSRv1QZudwdlbB+XpmQhb6s0G VQR4B3FWH20=

;; Query time: 23 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 14:13:21 CET 2018
;; MSG SIZE  rcvd: 466

and DS records:

$ dig @8.8.8.8 metebalci.com DS +qr

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> @8.8.8.8 metebalci.com DS +qr
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39831
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: b5dafd2648fa583b
;; QUESTION SECTION:
;metebalci.com.			IN	DS

;; QUERY SIZE: 54

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39831
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;metebalci.com.			IN	DS

;; ANSWER SECTION:
metebalci.com.		86399	IN	DS	48260 8 2 9BB9359BFCE4EF3EAC96F6E131D03665C274B47A7F0B2BB45A49AFCF 8F3A2F83

;; Query time: 29 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec 07 14:13:44 CET 2018
;; MSG SIZE  rcvd: 90

DNSSEC

DNSSEC Specifications

The DNS specs are spread across multiple documents which are updated and obsoleted by others and this makes it hard to know where to start reading about it. As far as I know, the first RFC regarding to DNSSEC is RFC 2065: Domain Name System Security Extensions which was published in January 1997. This was obsoleted by RFC 2535: Domain Name System Security Extensions published in March 1999 and this was obsoleted by three RFCs that still form the basic document set of DNSSEC:

  • RFC 4033: DNS Security Introduction and Requirements
  • RFC 4034: Resource Records for the DNS Security Extensions
  • RFC 4035: Protocol Modifications for the DNS Security Extensions

Specifically RFC 4033 says in the introduction that this document set (RFC 4033, 4034 and 4035) obsolete RFC 2535, 3008, 3090, 3445, 3655, 3658, 3755, 3757 and 3845, and updates RFC 1034, 1035, 2136, 2181, 2308, 3225, 3007, 3597 and 3226. The only reason I wrote all these numbers is to show how many different documents are involved (and these are not all, since there are other updates to DNS and DNSSEC). So to learn about DNSSEC seriously - assuming you already know enough about DNS (which involves at least a few other RFCs) -  and to be able to write some protocol level code, at least the three RFCs above should be read, studied and/or consulted.

DNSSEC Authentication Chain

DNSSEC allows one to authenticate an RRset by cryptographically verifying its signature, which is kept in an RRSIG (resource record signature).

A quick reminder. RR means resource record, basically DNS holds many such records, for example A records map names to IP addresses. RRset means the set of A records for the same name (e.g. metebalci.com). RRSIG is a new resource record type introduced with DNSSEC. For example:

$ dig +dnssec metebalci.com A
...
;; ANSWER SECTION:
metebalci.com.		300	IN	A	151.101.1.195
metebalci.com.		300	IN	A	151.101.65.195
metebalci.com.		300	IN	RRSIG	A 8 2 300 20181224151330 20181202151330 59764 metebalci.com. TsC9Sw31HA8JFiEDJG65mIjvjN+BfcaisZkBSrXFULLwqluvedCDS8f3 +ijFGlWXyb2DXlzHZbrwsd7DcbIAumHq4fKk+VkR/0jZjEw4DcmyLSE3 IwV5J+vMaI/RaJAAB1ecJSeb1BgZJJEu8tstAmYVQo1GxjzbMqI3hduK Eh4=
...

If you validate this RRSIG record, you can be sure that the metebalci.com A recordset, containing two A records above, is authentic. This is what DNSSEC aims to achieve.

This signature (RRSIG) is created by a keypair where the public key is kept in a DNSKEY (resource) record in the zone containing the RRset. The authenticity of the DNSKEY record is checked again by an(other) RRSIG (resource record).

You can see this in the authentication chain visualization of metebalci.com below:

metebalci-com-authentication-chain

This post will clarify how all these work. A short summary:

  • DS verifies DNSKEYs.
  • DNSKEY verifies the RRSIGs.
  • The root of trust is the DNS Root Trust Anchors, hosted at IANA (https://www.iana.org/dnssec/files), these are the trusted DS records that verifies the root DNSKEYs.

This is the current DNS Root Trust Anchors file:

<TrustAnchor id="380DC50D-484E-40D0-A3AE-68F2B18F61C7" source="http://data.iana.org/root-anchors/root-anchors.xml">
  <script/>
  <Zone>.</Zone>
  <KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
    <KeyTag>19036</KeyTag>
    <Algorithm>8</Algorithm>
    <DigestType>2</DigestType>
    <Digest>
      49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
    </Digest>
  </KeyDigest>
  <KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
    <KeyTag>20326</KeyTag>
    <Algorithm>8</Algorithm>
    <DigestType>2</DigestType>
    <Digest>
      E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
    </Digest>
  </KeyDigest>
</TrustAnchor>

DNSSEC Flags

As you know, or you should know, DNS message has flags, the ones you can see in the dig output (like rd ra aa tc):

$ dig +qr +dnssec metebalci.com
...
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19466
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
; COOKIE: 9523f674c5cdf5f6
...
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19466
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096

There are three flags (ad, cd, do) for DNSSEC. These are new to DNS, introduced for DNSSEC.

First two, Authenticated Data (AD) and Checking Disabled (CD) is in the DNS Message Header, in the previously reserved (zeroed) 10th and 11th bits. The other is DNSSEC OK (DO) bit in EDNS reserved (zeroed) area part of the Opt RR TTL field.

CD and DO flags are controlled (set) in the query, whereas AD is controlled (set) in the response.

Checking Disabled (CD) means, if the RRs cannot be verified, they should still be returned. Normally, if the name server cannot verify RRs, it does not return them, since it means records are not authentic.

DNSSEC OK (DO) means the resolver is DNSSEC-aware, so the name server should return DNSSEC RRs, if any exists, together with the other RRs asked for. DO flag is in EDNS0, and DNSSEC requires EDNS0 support also because of potentially larger message sizes. That is the reason you have to call dig with +dnssec in order to get RRSIG records.

Authenticated Data (AD) is set if the name server verified all RRs returned. This is independent of the value of CD.

Resource Records for the DNSSEC

This is actually the title of RFC 4034. There are a few new resource types defined for DNSSEC:

  • DNSKEY (DNS Public Key): keeps the the public key used to verify RRsets. Thus, it is the public key associated with the private key used to sign RRsets by the authoritative name server.
  • RRSIG (Resource Record Signature): keeps the digital signature of RRsets. There is only one RRSIG for a particular RRset identified by a name, class and type. So you do not verify the authenticity of single records, but a set of records having same name, class and type (e.g. metebalci.com IN A).
  • NSEC (Next Secure): keeps the next owner name containing the authoritative data or a delegation point NS RRset, and the set of record types present at the owner. It can be used for authenticated denial of existence, meaning an RRset is not part of a signed zone.
  • DS (Delegation Signer): refers to a DNSKEY RR, and holds a (hash) digest of the DNSKEY RR. It is used to authenticate the DNSKEY RR.
$ dig +qr @1.1.1.1 metebalci.com DNSKEY

;; ANSWER SECTION:
metebalci.com.  279 IN DNSKEY 256 3 8 AwEAAaacAUsS7h9WANc87LOyXwi6VmJuUJHV/LNCeDEC4fn6jrKYvRWg Of/xdbEjN0NdN+0852NBKik+e2hHt6mvNHEgV6eZsnMvEj3gBmrjodvb lUZoCGQoN1UPYB3+5jYZ+4Cux9RU/hoYW6wFcUU10Tnt+lMuocg6QsUt XnBOW/Kh

metebalci.com.  279 IN DNSKEY 257 3 8 AwEAAZ4z3QcJgdthQxNzAyaQLXC6nwwM6k+sq6HAOdM8lM1SKSnYCuEg gXZt0LGMT3B68fvVC7m0b0xogj9sWaLrJTFafW5uPMrYIm4T2UWC9/1q lFH7Gd3ShQrdp1jvGMzykSRPmPWXqNXo4QwvFLgcvrAb4PSAlsJvFiSF WoFCR1Dy69/5OFFfnJr2wzYiw/bYddkrbn2UpXQyUwKrQAHDNS9Q/IXB xpsIQfzrAtA+p82OTHUdbbWgmqvHt7KApQBTHnk5t/m9V2hPzS3TgwQv /u6PnPfgMVjhEBMYOmmzT9sGf9kpl9X5YUqt3gvoKyBBGwHhz6x1lF9/ a2P7hilzRw0=

We have two long answers. DNSKEY records contain:

  • Flags, 16-bits, and its values are 256 and 257 above. Bits 0–6 and 8–14 are reserved and should be zero. So only bit 7 and 15 can be one, and possible values are only 0, 256 (bit-7 is set) and 257 (both bit-7 and bit-15 is set). If the bit 7 is 1 (value 256 or 257), DNSKEY holds a DNS zone key; if it is 0 (value 0), it holds another type of key. So both of these hold a DNS zone key. Bit 15 (value 257) indicates a Secure Entry Point. Only DNSKEYs marked as zone keys can sign the DNS records.
  • Algorithm, 8-bits, the public key’s cryptographic algorithm, it is 8 above. 8 means RSA/SHA-256. For this algorithm, the key size must be between 512-bits and 4096-bits (according to RFC 5702).
  • Last field is DNSKEY RDATA in Base64 encoding, the format of public key in the record depends on the algorithm, in this case it contains the size of the exponent, the public exponent and the modulus (according to RFC 5702 and RFC 3110).

Lets deconstruct the DNSKEY RDATA as well, lets look at the first record:

Note: Because the DNSKEY records are long, they are usually entered as multiple strings into resource records, but they are effectively a single string, so I have omitted the spaces returned in the DNSKEY record in the echo above.

$ echo "AwEAAaacAUsS7h9WANc87LOyXwi6VmJuUJHV/LNCeDEC4fn6jrKYvRWgOf/xdbEjN0NdN+0852NBKik+e2hHt6mvNHEgV6eZsnMvEj3gBmrjodvblUZoCGQoN1UPYB3+5jYZ+4Cux9RU/hoYW6wFcUU10Tnt+lMuocg6QsUtXnBOW/Kh" | base64 -d | xxd -g 1 -c 8
00000000: 03 01 00 01 a6 9c 01 4b  .......K
00000008: 12 ee 1f 56 00 d7 3c ec  ...V..<.
00000010: b3 b2 5f 08 ba 56 62 6e  .._..Vbn
00000018: 50 91 d5 fc b3 42 78 31  P....Bx1
00000020: 02 e1 f9 fa 8e b2 98 bd  ........
00000028: 15 a0 39 ff f1 75 b1 23  ..9..u.#
00000030: 37 43 5d 37 ed 3c e7 63  7C]7.<.c
00000038: 41 2a 29 3e 7b 68 47 b7  A*)>{hG.
00000040: a9 af 34 71 20 57 a7 99  ..4q W..
00000048: b2 73 2f 12 3d e0 06 6a  .s/.=..j
00000050: e3 a1 db db 95 46 68 08  .....Fh.
00000058: 64 28 37 55 0f 60 1d fe  d(7U.`..
00000060: e6 36 19 fb 80 ae c7 d4  .6......
00000068: 54 fe 1a 18 5b ac 05 71  T...[..q
00000070: 45 35 d1 39 ed fa 53 2e  E5.9..S.
00000078: a1 c8 3a 42 c5 2d 5e 70  ..:B.-^p
00000080: 4e 5b f2 a1              N[..

The structure of the record is:

  • 1 or 3 bytes for the length of the exponent (if length is > 255 bytes, three bytes are used and the first byte is set to zero), here it is 3=03 in hex, so the length of exponent is 3)
  • variable length public exponent, here it is 3 bytes (we know from length above), so 257=01 00 01 in hex
  • variable length modulus (remaning bytes), here it is the large integer value encoded starting with a6 above

So how does this public key used ? Lets lookup for metebalci.com again but this time ask to return DNSSEC records as well (+dnssec):

$ dig +qr +dnssec @ns-cloud-b1.googledomains.com metebalci.com A

;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26407
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
; COOKIE: 1abaa4dd77f7566d
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26407
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;metebalci.com.			IN	A

;; ANSWER SECTION:
metebalci.com.		300	IN	A	151.101.1.195
metebalci.com.		300	IN	A	151.101.65.195
metebalci.com.		300	IN	RRSIG	A 8 2 300 20190205130903 20190114130903 34924 metebalci.com. A8dbTVpPWHIoloDytRxX5XTZhnGNYHh6a3nOttYm7ab7116f8m/iy6y4 RFtkD2CQtF/E7mkzNdSw6Et9Q4x8gi+2zNyxrbpVESxXdzh7/0I3fRlR MQwX5u5fpWzlUpM4ohQvsKM+agG6BFPMrLxYGFXadFqIHc6GvwDw+y3o qBw=

First you see, in the query and in EDNS, do (DNSSEC OK) flag is set, this indicates that the client/resolver is able to accept DNSSEC resource records. If it is not set, DNSSEC resource records should not be returned by the name server. When set, “do” bit is also copied to the response. Now since it is set and this domain name actually have DNSSEC records, the response contains an additional record in the answer, with the RRSIG record type.

RRSIG record contains:

  • Type Covered: record type this RRset covers, above it is A.
  • Algorithm: 8, RSA/SHA-256, as in the DNSKEY record.
  • Labels: number of labels in the owner name, meaning number of labels in metebalci.com, label is each non wildcard part, so here it is 2 (metebalci and com).
  • 300 is the original TTL value (of the records covered in the signature, so the TTL of A records).
  • 20190205130903 and 20190114130903 are the signature expiration and inception fields.
  • nKey Tag value, 34924 above. Key Tag indicates which DNSKEY (thus which public key) record is used for this signature. There is a specific algorithm to calculate Key Tag number based on DNSKEY data.
  • Signer’s Name is metebalci.com., which is the name of the zone of the covered RRset.
  • Signature is Base64 encoded RSA/SHA-256 signature of concatenation of this RRSIG data (without signature) and the records in the RRset.

Until now we saw the zone has Zone Signing Keys (ZSK) in DNSKEY records, and one of these are used to sign a resource record set (RRset). A client resolver can verify if the resource record set is authentic by checking the signature. However, how do we know ZSK is authentic ?

Similar to other records, ZSK is also signed but this time with what is called Key Signing Keys (KSK).

digsec and How DNS Authentication Works

Until now I used dig to demonstrate how DNS works, but in order to deconstruct the DNSSEC, I need a bit more than that, so I wrote a small Python program digsec that I will use to show the inner details of how DNSSEC works for my domain metebalci.com. It can be installed by pip install digsec and the source code stays on github.

I created this tool to learn about DNSSEC. Comparing to dig, digsec parses the records to display the contents in a more human friendly way, e.g. instead of showing 8, it writes RSASHA256. Also, digsec is more explicit on the default flags, assuming you should know what you are doing.

digsec currently sends queries to Google Public DNS (8.8.8.8) and it is not configurable. If you are sending a query where 8.8.8.8 is not the authoritative server (I believe this is probably all queries), you need to set recursion desired (+rd) flag, otherwise you will get no result.

Also, DNSKEY OK (+do) flag requires +udp_payload_size (because both is in EDNS, and both has to be set to a value). You can use +udp_payload_size=1024 for the moment.

$ digsec query metebalci.com +rd +do +udp_payload_size=1024
<<< Friendly Query >>>

ID: 16491
Flags:
	This is a Question, QR=0
	Opcode: Query
	Recursive Desired for the answer, RD=1
	Recursive Available, RA=1
	RCODE: NoError
Extension (EDNS):
	UDP payload size: 1024
	EXTENDED-RCODE: 0
	VERSION: 0
	DNSSEC OK (DO): True
	Options:
--- Question ---
metebalci.com A IN
--- Answer ---

--- Authority ---

--- Additional (not showing OPT here, see EDNS above) ---


<<< NETWORK COMMUNICATION >>>

<<< Friendly Response >>>

ID: 16491
Flags:
	This is a Reply, QR=1
	Opcode: Query
	Recursive Desired for the answer, RD=1
	Recursive Available, RA=1
	DNSSEC Authenticated Data, AD=1
	RCODE: NoError
Extension (EDNS):
	UDP payload size: 512
	EXTENDED-RCODE: 0
	VERSION: 0
	DNSSEC OK (DO): True
	Options:
--- Question ---
metebalci.com A IN
--- Answer ---
metebalci.com 136 IN A 151.101.1.195
metebalci.com 136 IN A 151.101.65.195
metebalci.com 136 IN RRSIG A RSASHA256 2 300 20190205130903 20190114130903 34924 metebalci.com A8dbTVpPWHIoloDytRxX5XTZhnGNYHh6a3nOttYm7ab7116f8m/iy6y4RFtkD2CQtF/E7mkzNdSw6Et9Q4x8gi+2zNyxrbpVESxXdzh7/0I3fRlRMQwX5u5fpWzlUpM4ohQvsKM+agG6BFPMrLxYGFXadFqIHc6GvwDw+y3oqBw=
--- Authority ---

--- Additional (not showing OPT here, see EDNS above) ---

This is same as dig, just showing some values in a friendly format.

Now, in order to initiate validation, first we need to save this answer, like this (we are going to save some files, so it is better you create a folder and issue the commands below there or use the +save-answer-dir flag):

$ digsec query metebalci.com +rd +do +udp_payload_size=1024 +save-answer
$ ls
metebalci.com.IN.A  metebalci.com.IN.RRSIG.A

The A records and the corresponding RRSIG record is saved to these files.

Now, we need the public key whose private key is used to create the RRSIG, and we get this from DNSKEY record:

$ digsec query metebalci.com DNSKEY +rd +do +udp_payload_size=1024 +save-answer
$ ls
metebalci.com.IN.A  metebalci.com.IN.DNSKEY  metebalci.com.IN.RRSIG.A  metebalci.com.IN.RRSIG.DNSKEY

Like before, now you have the DNSKEY records and its RRSIG record. This is enough to validate the RRSIG of A record set.

digsec validate metebalci.com.IN.A metebalci.com.IN.RRSIG.A metebalci.com.IN.DNSKEY
OK RRSIG (A, RSASHA256) with DNSKEY (34924, RSASHA256)

digsec validates the RRSIG (metebalci.com.IN.RRSIG.A) of the RRset (metebalci.com.IN.A) using the correct DNSKEY in the DNSKEY RRset (metebalci.com.IN.DNSKEY). 34924 is the key tag and RSASHA256 is the algorithm used to create and validate the RRSIG.

In order to validate DNSKEY, we need the DS record.

Note: I am using +show-friendly flag here, because +save-answer disables the output, but I do want to show the record here.

$ digsec query metebalci.com DS +rd +do +udp_payload_size=1024 +show-friendly +save-answer

...
metebalci.com 86345 IN DS 48260 RSASHA256 SHA-256 9bb9359bfce4ef3eac96f6e131d03665c274b47a7f0b2bb45a49afcf8f3a2f83
...

$ ls
metebalci.com.IN.A       metebalci.com.IN.DS       metebalci.com.IN.RRSIG.DNSKEY
metebalci.com.IN.DNSKEY  metebalci.com.IN.RRSIG.A  metebalci.com.IN.RRSIG.DS

DS record above contains the keytag (48260), algorithm (RSASHA256) and the digest type (SHA-256) used to generate the following digest in the DS record for the DNSKEY of metebalci.com. As all other records, there is also RRSIG of DS RRset.

Now lets validate the DNSKEY RRSIG.

$ digsec validate metebalci.com.IN.DNSKEY metebalci.com.IN.RRSIG.DNSKEY metebalci.com.IN.DS
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (48260, RSASHA256)
OK DS (SHA-256) with DNSKEY (48260, RSASHA256)

digsec understands this is DNSKEY record validation, so process differently. First, it validates the DNSKEY RRSIG using the DNSKEY RRset (the key used to sign DNSKEY RRset is inside the same RRset), then it validates the DNSKEY used for DNSKEY RRSIG using the digest in the DS. So simply:

  • anRRSIG = RSASHA256(anRRSET, aDNSKEY)
  • DNSKEY-RRSIG = RSASHA256(DNSKEY-RRSET, aDNSKEY)
  • DNSKEY-DIGEST in DS = SHA-256(aDNSKEY)

At this point, we validated a part of the chain, we need to validate RRSIG of DS RRset next. DS is signed by the parent zone, so RRSIG of DS of metebalci.com is signed by a DNSKEY of com. So, in order to continue, we need DNSKEY of com first.

$ digsec query com DNSKEY +rd +do +udp_payload_size=1024 +save-answer
$ ls
com.IN.DNSKEY        metebalci.com.IN.A       metebalci.com.IN.DS       metebalci.com.IN.RRSIG.DNSKEY
com.IN.RRSIG.DNSKEY  metebalci.com.IN.DNSKEY  metebalci.com.IN.RRSIG.A  metebalci.com.IN.RRSIG.DS
$ digsec validate metebalci.com.IN.DS metebalci.com.IN.RRSIG.DS com.IN.DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (16883, RSASHA256)

We are done with metebalci.com now. We should continue with validating the DNSKEY of com. The process is same.

$ digsec query com DS +rd +do +udp_payload_size=1024 +save-answer
$ ls 
com.IN.DNSKEY  com.IN.RRSIG.DNSKEY  metebalci.com.IN.A       metebalci.com.IN.DS       metebalci.com.IN.RRSIG.DNSKEY
com.IN.DS      com.IN.RRSIG.DS      metebalci.com.IN.DNSKEY  metebalci.com.IN.RRSIG.A  metebalci.com.IN.RRSIG.DS
$ digsec validate com.IN.DNSKEY com.IN.RRSIG.DNSKEY com.IN.DS
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (30909, RSASHA256)
OK DS (SHA-256) with DNSKEY (30909, RSASHA256)

To validate DS of com, we need the DNSKEY of root. But we can also observe something else first:

$ digsec query . DNSKEY +rd +do +udp_payload_size=1024
...
<<< Friendly Response >>>

ID: 4966
Flags:
	This is a Reply, QR=1
	Opcode: Query
	This answer is TrunCated, TC=1
	Recursive Desired for the answer, RD=1
	Recursive Available, RA=1
	RCODE: NoError
...

As you see, the response is indicated as Truncated. That means it did not fit into 1024 bytes we indicated as udp_payload_size. We can increase this to 2048 and try again. This topic is pretty low level, related to network communication, but you should know that you can increase the UDP payload size a bit, and if it still does not work, TCP can also be used. digsec does not support using TCP yet.

$ digsec query . DNSKEY +rd +do +udp_payload_size=2048 +save-answer
$ ls
com.IN.DNSKEY  com.IN.RRSIG.DNSKEY  metebalci.com.IN.A       metebalci.com.IN.DS       metebalci.com.IN.RRSIG.DNSKEY  _root.IN.DNSKEY
com.IN.DS      com.IN.RRSIG.DS      metebalci.com.IN.DNSKEY  metebalci.com.IN.RRSIG.A  metebalci.com.IN.RRSIG.DS      _root.IN.RRSIG.DNSKEY

In order to validate the DNSKEY of root zone, we also need the DS record. This is where the trust starts. The DS of root zone is not queried but it can be downloaded from IANA (https://www.iana.org/dnssec/files).

Note: The DNSSEC Trust Anchor has to be validated as well, but this process is totally different, and documented for example in get-trust-anchor stand-alone tool, see https://github.com/iana-org/get-trust-anchor for more.

Without validation, the trust anchor can be downloaded with digsec.

$ digsec download +save-ds-anchors
Trust-Anchor contains keytags: 19036-8, 20326-8
$ ls
com.IN.DNSKEY        com.IN.RRSIG.DS          metebalci.com.IN.DS            metebalci.com.IN.RRSIG.DS  _root.IN.RRSIG.DNSKEY
com.IN.DS            metebalci.com.IN.A       metebalci.com.IN.RRSIG.A       _root.IN.DNSKEY
com.IN.RRSIG.DNSKEY  metebalci.com.IN.DNSKEY  metebalci.com.IN.RRSIG.DNSKEY  _root.IN.DS

Now we can complete the validation, and we will see something different again:

$ digsec validate _root.IN.DNSKEY _root.IN.RRSIG.DNSKEY _root.IN.DS
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (19164, RSASHA256)
WARNING: no DS for DNSKEY with keytag: 19164
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (20326, RSASHA256)
OK DS (SHA-256) with DNSKEY (20326, RSASHA256)

Until now, when digsec validated a DNSKEY record, there was always a single RRSIG. However, for the root zone, there are two RRSIG, created with two different DNSKEY (19164 and 20326). So, both are tried to be validated. One is invalid, because DNS Trust Anchor does not contain a digest of a DNSKEY with keytag 19164, whereas the other (20326) is valid. I am not sure why there is an (effectively) invalid RRSIG here.

This completes the DNSSEC validation for metebalci.com IN A records. To sum up, below is all the commands and outputs I used to validate metebalci.com IN A, do not forget to run them in a temp folder as it will create a few files.

digsec query metebalci.com A +rd +do +udp_payload_size=2048 +save-answer
digsec query metebalci.com DNSKEY +rd +do +udp_payload_size=2048 +save-answer
digsec query metebalci.com DS +rd +do +udp_payload_size=2048 +save-answer
digsec query com DNSKEY +rd +do +udp_payload_size=2048 +save-answer
digsec query com DS +rd +do +udp_payload_size=2048 +save-answer
digsec query . DNSKEY +rd +do +udp_payload_size=2048 +save-answer
digsec download +save-ds-anchors

digsec validate metebalci.com.IN.A metebalci.com.IN.RRSIG.A metebalci.com.IN.DNSKEY
digsec validate metebalci.com.IN.DNSKEY metebalci.com.IN.RRSIG.DNSKEY metebalci.com.IN.DS
digsec validate metebalci.com.IN.DS metebalci.com.IN.RRSIG.DS com.IN.DNSKEY
digsec validate com.IN.DNSKEY com.IN.RRSIG.DNSKEY com.IN.DS
digsec validate com.IN.DS com.IN.RRSIG.DS _root.IN.DNSKEY
digsec validate _root.IN.DNSKEY _root.IN.RRSIG.DNSKEY _root.IN.DS

Lets try a failing example also (and for a record of a child domain name, not the zone). The domain name is www.dnssec-failed.org and record type A, and as we saw before with dig, this record has an invalid DNSSEC authentication.

First we query the A record.

$ digsec query www.dnssec-failed.org A +rd +do +udp_payload_size=2048
...
RCODE: ServFail
...

but this is failed. You should be I able to guess now the reason, because it has an invalid signature, it is not returning the records. We need to add Check Disabled flag.

$ digsec query www.dnssec-failed.org A +rd +do +cd +udp_payload_size=2048 +save-answer
$ ls
www.dnssec-failed.org.IN.A  www.dnssec-failed.org.IN.RRSIG.A

That worked. Now we need DNSKEY to validate this.

$ digsec query www.dnssec-failed.org DNSKEY +rd +do +cd +udp_payload_size=2048
...
--- Answer ---

--- Authority ---
dnssec-failed.org 1492 IN SOA dns101.comcast.org dnsadmin.comcast.net 2010101965 900 180 604800 7200
dnssec-failed.org 21292 IN RRSIG SOA RSASHA1 2 86400 20190225174426 20190218143926 44973 dnssec-failed.org Ylo3u0edjzL+pTyagF4fVnfgxGv8/nSMelbXBQRcX9TbYfk1SzLO5X0DYq+Gel99ENrW9xEmKrUjpJ6LEhn0PYXbAKMHVPfdPHZFLF2ZGofX8vnIUIdxDdZHdoOSXuYOIMKxd/VJOWvKnK/k52ugKi7Rve17sp92iJnmRGJiwgA=
www.dnssec-failed.org 6892 IN NSEC dnssec-failed.org A TXT RRSIG NSEC
www.dnssec-failed.org 6892 IN RRSIG NSEC RSASHA1 3 7200 20190225174426 20190218143926 44973 dnssec-failed.org zV1raCFV5PKKXmNjCPuNDZzpmL1f5LsX5N6PyIvDPp/BwSvJB0WiHSm8x1zZ4DZS54QeyUY596CPgI9yVHSZ0gDgHXAkHe4k1vxqz38SUbLtX3mAP1Dm5elnbaO+XU0o2yESNLbdDoWY4oaKtyEjaeBBwzurar29EoWt51tiLJo=
...

As you see, there is no answer, because only the zone has the DNSKEY record. On the other hand, this query returns, in the Authority section, dnssec-failed.org SOA record. So we know the zone is dnssec-failed.org which we can query for the DNSKEY record.

$ digsec query dnssec-failed.org DNSKEY +rd +do +cd +udp_payload_size=2048 +save-answer
$ ls
dnssec-failed.org.IN.DNSKEY  dnssec-failed.org.IN.RRSIG.DNSKEY  
www.dnssec-failed.org.IN.A   www.dnssec-failed.org.IN.RRSIG.A

Now lets try to validate:

$ digsec validate www.dnssec-failed.org.IN.A www.dnssec-failed.org.IN.RRSIG.A dnssec-failed.org.IN.DNSKEY
OK RRSIG (A, RSASHA1) with DNSKEY (44973, RSASHA1)

This works, so the problem should be somewhere else, lets check the DNSKEY (and for that we need the DS record too).

$ digsec query dnssec-failed.org DS +rd +do +cd +udp_payload_size=2048 +save-answer
$ digsec validate dnssec-failed.org.IN.DNSKEY dnssec-failed.org.IN.RRSIG.DNSKEY dnssec-failed.org.IN.DS
OK RRSIG (DNSKEY, RSASHA1) with DNSKEY (29521, RSASHA1)
WARNING: no DS for DNSKEY with keytag: 29521
OK RRSIG (DNSKEY, RSASHA1) with DNSKEY (44973, RSASHA1)
WARNING: no DS for DNSKEY with keytag: 44973
ERROR in RRSIG (DNSKEY, RSASHA1) Validation !
None of DNSKEYs could be validated with DS !

We found the problem. The DNSKEY used with RRSIG is not valid, basically its keytag cannot be found in the DS record.

I have been asked if social.soy DNSSEC configuration is correct or why there is a problem sometimes.

First of all, lets see the NS records for social.soy:

$ digsec query social.soy NS +rd

<<< NETWORK COMMUNICATION >>>
Server: 8.8.8.8:53

--- Answer ---
social.soy 3599 IN A ('ns2.r4ns.net', 54)
social.soy 3599 IN A ('ns1.r4ns.com', 80)
social.soy 3599 IN A ('gns2.cloudns.net', 107)
social.soy 3599 IN A ('gns3.cloudns.net', 126)
social.soy 3599 IN A ('gns4.cloudns.net', 145)
social.soy 3599 IN A ('gns1.cloudns.net', 164)

So there are 6 name servers, and it looks like they are from two different providers (Rage4 and ClouDNS).

Lets see if DNSSEC works for Rage4:

$ ./validate_second_level_domain.sh social.soy A /tmp ns1.r4ns.com
testing social.soy A using ns1.r4ns.com

downlading answers
downloading social.soy A
downloading social.soy DNSKEY
downloading social.soy DS
downloading soy DNSKEY
downloading soy DS
downloading . DNSKEY
downloading . DS (trust anchor)
Trust-Anchor contains keytags: 19036-8, 20326-8

validating answers
validating social.soy A with social.soy DNSKEY
OK RRSIG (A, ECDSAP256SHA256) with DNSKEY (13872, ECDSAP256SHA256)
validating social.soy DNSKEY with social.soy DS
OK RRSIG (DNSKEY, ECDSAP256SHA256) with DNSKEY (58596, ECDSAP256SHA256)
OK DNSKEY (58596, ECDSAP256SHA256) with DS (SHA-256)
validating social.soy DS with soy DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (15355, RSASHA256)
validating soy DNSKEY with soy DS
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (37831, RSASHA256)
OK DNSKEY (37831, RSASHA256) with DS (SHA-256)
validating soy DS with . DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (48903, RSASHA256)
validating . DNSKEY with . DS (trust anchor)
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (20326, RSASHA256)
OK DNSKEY (20326, RSASHA256) with DS (SHA-256)

and for ClouDNS:

$ ./validate_second_level_domain.sh social.soy A /tmp gns1.cloudns.net
testing social.soy A using gns1.cloudns.net

downlading answers
downloading social.soy A
downloading social.soy DNSKEY
downloading social.soy DS
downloading soy DNSKEY
downloading soy DS
downloading . DNSKEY
downloading . DS (trust anchor)
Trust-Anchor contains keytags: 19036-8, 20326-8

validating answers
validating social.soy A with social.soy DNSKEY
OK RRSIG (A, ECDSAP256SHA256) with DNSKEY (1070, ECDSAP256SHA256)
validating social.soy DNSKEY with social.soy DS
OK RRSIG (DNSKEY, ECDSAP256SHA256) with DNSKEY (1070, ECDSAP256SHA256)
WARNING: no DS for DNSKEY with keytag: 1070
OK RRSIG (DNSKEY, ECDSAP256SHA256) with DNSKEY (12779, ECDSAP256SHA256)
OK DNSKEY (12779, ECDSAP256SHA256) with DS (SHA-256)
validating social.soy DS with soy DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (15355, RSASHA256)
validating soy DNSKEY with soy DS
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (37831, RSASHA256)
OK DNSKEY (37831, RSASHA256) with DS (SHA-256)
validating soy DS with . DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (48903, RSASHA256)
validating . DNSKEY with . DS (trust anchor)
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (20326, RSASHA256)
OK DNSKEY (20326, RSASHA256) with DS (SHA-256)

it seems there is no problem. However, if we do same using Google Public DNS (8.8.8.8):

$ ./validate_second_level_domain.sh social.soy A /tmp 8.8.8.8
testing social.soy A using 8.8.8.8

downlading answers
downloading social.soy A
downloading social.soy DNSKEY
downloading social.soy DS
downloading soy DNSKEY
downloading soy DS
downloading . DNSKEY
downloading . DS (trust anchor)
Trust-Anchor contains keytags: 19036-8, 20326-8

validating answers
validating social.soy A with social.soy DNSKEY
--- start of /tmp/social.soy.IN.RRSIG.A ---
social.soy 59 IN RRSIG A ECDSAP256SHA256 2 60 20200521000000 20200430000000 13872 social.soy ngYhVayxzwTXcghMyGSVIFIyRzpdssYuydAsW6Q9f9tyHu3z9JID/RhNJlmkeFNnJnNbNkBkV/Kw0uoYjcBpRg==
--- end ---
--- start of /tmp/social.soy.IN.DNSKEY ---
social.soy 3282 IN DNSKEY(12779) 257 3 ECDSAP256SHA256 wwVFyXaXZdDcNVYhOpkfkWSwRZx95U3nU+d8y78fFUFYGXCmitxuur4z5bDJTc0tUl2LPLKz0Ta1eUWCjw+4sA==
social.soy 3282 IN DNSKEY(1070) 256 3 ECDSAP256SHA256 BjOv++x6MlDPtyn7WFNGGVFCsC1HPeQTgjAKbe0eiUltn35TT2q2Gqkr6tk9GT+QlDANYf9o2lnPTNyLaknPog==
--- end ---
Error: No DNSKEY with keytag 13872 in /tmp/social.soy.IN.DNSKEY
validating social.soy DNSKEY with social.soy DS
OK RRSIG (DNSKEY, ECDSAP256SHA256) with DNSKEY (1070, ECDSAP256SHA256)
WARNING: no DS for DNSKEY with keytag: 1070
OK RRSIG (DNSKEY, ECDSAP256SHA256) with DNSKEY (12779, ECDSAP256SHA256)
OK DNSKEY (12779, ECDSAP256SHA256) with DS (SHA-256)
validating social.soy DS with soy DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (15355, RSASHA256)
validating soy DNSKEY with soy DS
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (37831, RSASHA256)
OK DNSKEY (37831, RSASHA256) with DS (SHA-256)
validating soy DS with . DNSKEY
OK RRSIG (DS, RSASHA256) with DNSKEY (48903, RSASHA256)
validating . DNSKEY with . DS (trust anchor)
OK RRSIG (DNSKEY, RSASHA256) with DNSKEY (20326, RSASHA256)
OK DNSKEY (20326, RSASHA256) with DS (SHA-256)

there seems to be a problem. As you guess, Google Public DNS (or any other client looking for these records) may go to one of these providers randomly and the problem is the DNSKEY records.

DNSKEY records from Rage4:

$ digsec query @ns1.r4ns.com social.soy DNSKEY

--- Answer ---
social.soy 600 IN DNSKEY(13872) 256 3 ECDSAP256SHA256 8pnYdYLCK7IUR24kB9RltFqj0lf2ZAh85qVew/BadKL742cdEB8pxsvAzQENivOobhZKqW4cphcZKTJXwl/gtw==
social.soy 600 IN DNSKEY(27454) 256 3 ECDSAP256SHA256 sdMFuBxoAmo1j2vURzaF+d6nGl0gbioMacgWRvf/17dVxu3mVDCtv3gZrNQosk2Si+vOaYIKLU+xcT/yLYqFjQ==
social.soy 600 IN DNSKEY(58596) 257 3 ECDSAP256SHA256 TDY77KDvLrBKkbUqGAgiRJG4o7q8YJgbTSCbufesy0Ng98v1z97Y+MCL9aHf0ZWSkvfkHTng+4NgOVn0drs6mA==

and DNSKEY records from ClouDNS:

$ digsec query @gns1.cloudns.net social.soy DNSKEY

--- Answer ---
social.soy 3600 IN DNSKEY(12779) 257 3 ECDSAP256SHA256 wwVFyXaXZdDcNVYhOpkfkWSwRZx95U3nU+d8y78fFUFYGXCmitxuur4z5bDJTc0tUl2LPLKz0Ta1eUWCjw+4sA==
social.soy 3600 IN DNSKEY(1070) 256 3 ECDSAP256SHA256 BjOv++x6MlDPtyn7WFNGGVFCsC1HPeQTgjAKbe0eiUltn35TT2q2Gqkr6tk9GT+QlDANYf9o2lnPTNyLaknPog==

are different. So if you query A records from one, but DNSKEY records from other, A records (actually the RRSIG of A records) -not surprisingly- cannot be validated. Here is why:

$ digsec query @gns1.cloudns.net social.soy A +rd +do +udp_payload_size=2048 +save-answer
$ digsec query @ns1.r4ns.com social.soy DNSKEY +rd +do +udp_payload_size=2048 +save-answer
$ digsec validate social.soy.IN.A social.soy.IN.RRSIG.A social.soy.IN.DNSKEY
--- start of social.soy.IN.RRSIG.A ---
social.soy 60 IN RRSIG A ECDSAP256SHA256 2 60 20200610185050 20200511185050 1070 social.soy KhEnq3GReE1FUi5haeFFT4IMpd+V9r2WZwZaGSkl7I7lo0+sqdkqpKy7J5Ym8lwPBr4uIM2A+HerwrVDFTjUlw==
--- end ---
--- start of social.soy.IN.DNSKEY ---
social.soy 600 IN DNSKEY(13872) 256 3 ECDSAP256SHA256 8pnYdYLCK7IUR24kB9RltFqj0lf2ZAh85qVew/BadKL742cdEB8pxsvAzQENivOobhZKqW4cphcZKTJXwl/gtw==
social.soy 600 IN DNSKEY(27454) 256 3 ECDSAP256SHA256 sdMFuBxoAmo1j2vURzaF+d6nGl0gbioMacgWRvf/17dVxu3mVDCtv3gZrNQosk2Si+vOaYIKLU+xcT/yLYqFjQ==
social.soy 600 IN DNSKEY(58596) 257 3 ECDSAP256SHA256 TDY77KDvLrBKkbUqGAgiRJG4o7q8YJgbTSCbufesy0Ng98v1z97Y+MCL9aHf0ZWSkvfkHTng+4NgOVn0drs6mA==
--- end ---
Error: No DNSKEY with keytag 1070 in social.soy.IN.DNSKEY

That is why If I use Google Public DNS (or any other public NS), it may cause DNSSEC validation errors.

The solution is either to use only one provider with DNSSEC, or find a way to synchronize these records (and implicitly any relevant information). There is currently an IETF draft called Multi Signer DNSSEC models which aims to offer solutions to multiple DNS provider scenarios.

After Social.Soy tech team have contacted Rage4 and ClouDNS, they were able to fix the issue by using the same key pair provided by social.soy. Here is the current situation.

DNSKEY records at Rage4:

$ digsec query @ns1.r4ns.com social.soy DNSKEY

<<< NETWORK COMMUNICATION >>>
Server: ns1.r4ns.com:53

--- Answer ---
social.soy 600 IN DNSKEY(1672) 256 3 ECDSAP256SHA256 w/2p2DIj/rd5tjFhrvuzh0dT3LhLjdexl8YYSI3bDixrW3CYijJmA4nSyHjLZrT+5R6AfhqE13+Fdw+5mHqepQ==
social.soy 600 IN DNSKEY(27454) 256 3 ECDSAP256SHA256 sdMFuBxoAmo1j2vURzaF+d6nGl0gbioMacgWRvf/17dVxu3mVDCtv3gZrNQosk2Si+vOaYIKLU+xcT/yLYqFjQ==
social.soy 600 IN DNSKEY(3705) 257 3 ECDSAP256SHA256 8qdjetJplopWk1vzxHyi2wQuTI1HffWXNEHDN1HSVMsHMne6u5wDvB5gDY1+kdm9AvHpvqQ1v6cQDUC4FGqNTw==

DNSKEY records at ClouDNS:

$ digsec query @gns1.cloudns.net social.soy DNSKEY

<<< NETWORK COMMUNICATION >>>
Server: gns1.cloudns.net:53

--- Answer ---
social.soy 3600 IN DNSKEY(3705) 257 3 ECDSAP256SHA256 8qdjetJplopWk1vzxHyi2wQuTI1HffWXNEHDN1HSVMsHMne6u5wDvB5gDY1+kdm9AvHpvqQ1v6cQDUC4FGqNTw==
social.soy 3600 IN DNSKEY(1672) 256 3 ECDSAP256SHA256 w/2p2DIj/rd5tjFhrvuzh0dT3LhLjdexl8YYSI3bDixrW3CYijJmA4nSyHjLZrT+5R6AfhqE13+Fdw+5mHqepQ==

As you see, DNSKEY(3705) and DNSKEY(1672) is now in both providers (and you can see the public keys are the same), so both providers can independently sign the records with the same key and it can be validated by the corresponding DNSKEY retrieved from any of the providers. If we check the A records for example:

$ digsec query @ns1.r4ns.com social.soy A +do +udp_payload_size=2048

<<< NETWORK COMMUNICATION >>>
Server: ns1.r4ns.com:53

--- Answer ---
social.soy 60 IN A 104.22.0.12
social.soy 60 IN A 104.22.1.12
social.soy 60 IN RRSIG A ECDSAP256SHA256 2 60 20200521000000 20200430000000 1672 social.soy qts6/GJA0smF7ePbDeUs6gIhyBxWXMKHNw9IEmsHjQzcdBd9CbAdXOqrYTNhwzvOqcUJU03lXWLi5fmJdpt4dg==

RRSIG is signed by DNSKEY(1672) which is available in both providers.

I would like to thank Social.Soy tech team for allowing me to post this problem and the solution, and Social.Soy tech team also thanks “Rage4 and ClouDNS for their great DNS and DNSSEC support”.

Referrer