A Minimum Complete Tutorial of DNSSEC

February 21, 2019

Introduction

This post assumes you know enough about the basics of DNS and using dig DNS lookup utility. If you need a refresh, check my post: A Short Practical Tutorial of dig, DNS and DNSSEC.

I am very surprised to see that DNSSEC is not widely used even today. Why I say this ? Even the well-known international and regional sites are not using it, lets check some:

$ dig +dnssec medium.com A
... no DNSSEC records ...
$ dig +dnssec bbc.co.uk A
... no DNSSEC records ...
$ dig +dnssec galaxus.ch A
... no DNSSEC records ...
$ dig +dnssec hepsiburada.com A
... no DNSSEC records ...

I am not sure why this is the case. As you know, or should know, DNS replies are not signed, thus their authenticity cannot be verified (as an analogy, DNS is like HTTP, DNSSEC provides HTTPS). When you want to access this site, your computer basically looks up the following DNS record:

$ dig metebalci.com A
...

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

Update on 12/05/2020: I have updated the DNS records of metebalci.com, so you will see different output if you run this. However, what I explain in this post is of course valid for new records as well.

I know above records are correct, because I have configured these DNS records as above since I have access to the authoritative DNS records as the owner. However, how can you, as a user of this site, be sure the IP addresses above are correct/authentic ?

The purpose of this tutorial is to show how metebalci.com A IN records can be validated with DNSSEC. The process is same for any other domain and any other record type and class.

DNSSEC Usage Statistics

There are various sites showing some kind of DNSSEC deployment and validation statistics, a collection of such sites are listed here: DNSSEC Statistics | Internet Society. Below I give a short summary.

There are 137M domains under com, only 1M is signed. Less than 1%. (source: December 2018 Reports of https://www.statdns.com/)

There are even country TLDs (TLD: Top Level Domain), such as Cyprus, Slovakia, Turkey, Uzbekistan and some others, that have their TLD (.cy, .sk, .tr, .uz) unsigned and have no DS record in the root (source: http://stats.research.icann.org/dns/tld_report/). This means you cannot have DNSSEC for any domain under such TLDs. There are 1535 TLDs in total and 1400 of them are signed.

You can check this with digsec, just query the DS record:

$ dig @a.root-servers.net +dnssec com. DS
...
com.  86400	IN	DS	30909 8 2 E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CF C41A5766
...

$ dig @a.root-servers.net +dnssec ch. DS
...
ch.   86400	IN	DS	11896 13 2 24EE6537B1C452D3AEBF439DCF74024717054152DA7F206D5FCBA1A9 0F70711F
...

$ dig @a.root-servers.net +dnssec tr. DS
...
NO DS RECORD in the ANSWER
...

You can look at the DNSSEC Deployment Maps, and a snapshot for country-code TLDs (ccTLDs) is below:

ccTLD DNSSEC Status on 2018-03-12

ccTLD DNSSEC Status on 2018-03-12

Rick Lamb’s DNSSEC report page also gives a quick overview:

  • 90% of TLDs in root are signed.
  • Approx. 4% of second level domains (2LDs) are signed.
  • Approx. 12% of users are validating DNSSEC.

There is also an APNIC article from December 2017: Why DNSSEC deployment remains so low. I am using Hover for metebalci.com, and as mentioned in this article and in Hover’s Understanding and managing DNSSEC page, Hover also does not support DNSSEC in its hosted DNS service, but only if you keep the DNS on your own servers. That is what I do, I keep my DNS at Google Cloud Platform.

Also, DNSSEC validation, which means (cryptographically) validating the DNS records obtained by a DNS query (and returned by a DNS server), rates are also low worldwide, the world average is less than 15%, this can be seen per country here: (https://stats.labs.apnic.net/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 run this query on your own, you may see a different RRSIG record, because RRSIG records have expiry date (2018-12-24 15:13:30 above) and at that date, it is going to change.

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:

DNSSEC Authentication Chain (http://dnsviz.net/d/metebalci.com/dnssec/)

DNSSEC Authentication Chain (http://dnsviz.net/d/metebalci.com/dnssec/)

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, 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.

Note: I will not use and explain the NSEC or NSEC3 records in this post. These are used to explicitly say the name that has been queried does not exist, rather than returning no answer.

Using Google Public DNS

I think it is worth to clarify AD and CD bits with a practical information at this point. Google Public DNS FAQ explains this nicely:

“Google Public DNS is a validating, security-aware resolver. All responses from DNSSEC signed zones are validated unless clients explicitly set the CD flag in DNS requests to disable the validation.”

and it continues:

“If Google Public DNS cannot validate a response (due to misconfiguration, missing or incorrect RRSIG records, etc.), it will return an error response (SERVFAIL) instead. However, if the impact is significant (e.g. a very popular domain is failing validation), we may temporarily disable validation on the zone until the problem is fixed.”

Also mentioned in the FAQ is a way to test a bad RRSIG record of www.dnssec-failed.org:

$ dig +qr +dnssec @8.8.8.8 www.dnssec-failed.org A
; <<>> DiG 9.11.3-1ubuntu1-Ubuntu <<>> +qr +dnssec @8.8.8.8 www.dnssec-failed.org A
; (1 server found)
;; global options: +cmd
;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27967
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
; COOKIE: b67cf15913c79d34
;; QUESTION SECTION:
;www.dnssec-failed.org.  IN A
;; QUERY SIZE: 62
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 27967
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;www.dnssec-failed.org.  IN A
;; Query time: 286 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Jun 20 12:38:21 CEST 2018
;; MSG SIZE  rcvd: 50

As said in the FAQ, the name server returned SERVFAIL and did not return an answer (ANSWER=0). So using Google Public DNS actually protects you some of the security problems of DNS as a user. However, keep in mind, from the FAQ:

“Traditional DNS traffic is transported over UDP or TCP without encryption. We also provide DNS-over-HTTPS which encrypts the traffic between clients and Google Public DNS. You may try it at: https://dns.google.com.”

and

“DNS-over-HTTPS and DNSSEC are complementary. Google Public DNS uses DNSSEC to authenticate responses from name servers whenever possible. However, in order to securely authenticate a traditional UDP or TCP response from Google Public DNS, a client would need to repeat the DNSSEC validation itself, which very few client resolvers currently do. DNS-over-HTTPS encrypts the traffic between stub resolvers and Google Public DNS, and complements DNSSEC to provide end-to-end authenticated DNS lookups.”

So as a user, just by using Google Public DNS in a traditional way (with UDP or TCP), you cannot be still sure that the data you receive is authentic. You either have to use a DNSSEC aware client resolver on your computer or use DNS over HTTPS with a validating, DNSSEC aware resolver like Google Public DNS.

Responsible (Authoritative) Name Server

Every zone have one or more authoritative name server(s), meaning it is the owner or the master of the records, so its response is considered always valid, whereas the other name servers may have the cached, maybe stale, data. The zone has a NS record:

$ dig metebalci.com NS
...
metebalci.com.  21600 IN NS ns-cloud-b1.googledomains.com.
metebalci.com.  21600 IN NS ns-cloud-b2.googledomains.com.
metebalci.com.  21600 IN NS ns-cloud-b3.googledomains.com.
metebalci.com.  21600 IN NS ns-cloud-b4.googledomains.com.
...

But how do we know metebalci.com is the zone ? We can query the SOA record:

$ dig metebalci.com SOA
...
metebalci.com.  300 IN SOA ns-cloud-e1.googledomains.com. cloud-dns-hostmaster.google.com. 1 21600 3600 259200 300
...

it means metebalci.com is the start of (a zone) authority. If you query a subdomain, for example www.metebalci.com, it will also return the same SOA record, since it (www) is under this (metebalci.com) zone.

$ dig www.metebalci.com SOA
...
metebalci.com.		49	IN	SOA	ns-cloud-b1.googledomains.com. cloud-dns-hostmaster.google.com. 1 21600 3600 259200 300
...

Can we query the NS information for www.metebalci.com ? We can, but result will be empty since www.metebalci.com is not a zone.

In short, you can issue a SOA query to any domain name, and then issue a NS query for the zone to learn the authoritative name servers.

Since we learned the authoritative NS for this zone, we can query metebalci.com on this name server and we will see something different.

$ dig @ns-cloud-b1.googledomains.com metebalci.com A
...
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
...

Different than before, we see AA (Authoritative Answer) flag, indicating the responding name server (ns-cloud-b1.googledomains.com) is an authority for the domain name in question (metebalci.com). Also, flags do not contain RA (Recursion Available), so this name server do not support recursion. We can see that with:

$ dig @ns-cloud-b1.googledomains.com www.medium.com A
...
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 27757
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
...

It returns no answer and status REFUSED, since recursion is disabled and it is not authoritative name server for medium.com, it cannot return any result. If the recursion was allowed, it could find the authoritative name server of medium.com, fetch the result and return back to us. Normally public name servers (like Cloudfare 1.1.1.1 and Google 8.8.8.8) respond to recursive queries but other authoritative name servers do not respond as such.

Domain Name System Security Extensions (DNSSEC)

DNSSEC provides a way to cryptographically verify the authenticity of the resource records.

The purpose of this tutorial is to show how metebalci.com A IN records are validated. The process is same for any other domain and any other record type and class.

First, how to know if a zone (at least) supports DNSSEC ? DNSKEY record type holds the public key of the zone for DNSSEC signatures. So we can query that:

$ 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.
  • Key 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.

Update on 12/05/2020: As of v0.4, digsec allows to use another server. Google Public DNS is the default if you do not provide another server name.

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.

Please do not forget if you run these examples on your own, the process and the overall structure of outputs will be similar but the particular individual values can be different because DNSSEC records are changed when the keys are changed/rotated.

$ 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.

A real problem and the solution (updated on 13.05.2020)

This section uses new features introduced in v0.5 of digsec.

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”.