Introduction

In the first part of our DNS exploration, we discussed what DNS was, how it worked and why it was so essential to the operation of many services we came to rely upon, such as the web or electronic mail. We also touched upon network debugging with tcpdump and Wireshark.

Then, during the labs, we configured NSD and Unbound, an authoritative DNS server daemon and a DNS recursor, respectively. Everybody is now the admin of their own $login.una domain.

In this second part, we’ll focus mainly on DNSSEC, a security layer built on top of DNS.

But first, some history and motivation.

History

The Domain Name System was designed in 1983 and originally described in RFC 882 and RFC 883, about 40 years ago. Like many other ancient protocols—such as FTP, HTTP or Telnet—it was designed for a different world. As we saw firsthand during the other lecture, by default, DNS uses unencrypted UDP or TCP transport. This has far-reaching consequences:

  • First of all, anybody who can capture packets anywhere on our network path can analyze our DNS traffic. If we can take a tcpdump and analyze it with Wireshark, anybody else can, too. We were able to (partly) mitigate this issue and achieve some DNS privacy.
  • Secondly, an attacker on our network path can modify DNS responses to our queries and serve us fake records. This is where DNSSEC (DNS Security Extensions) fits in.

DNS attacks

Some of the most common types of DNS attacks are listed below:

  • An attacker on the network path between the victim and the DNS server either modifies the DNS server’s response, or sends theirs faster than the legitimate DNS server. The victim uses a fake DNS record. This would be an instance of DNS spoofing.
  • An attacker changes the DNS settings of their victim, pointing the victim to their own DNS server (for example, by tinkering with DHCP), and serves the fake records directly. Or, an attacker exploits a vulnerability in a DNS server implementation and remotely modifies the records it’s serving. This would be an instance of DNS hijacking.
  • An attacker may successfully pull out a DNS cache poisoning attack, in which they flood a recursive DNS server (such as Unbound) with fake DNS responses, modifying the query ID each time. Eventually, they might score a hit, and the recursor will pair their fake answer with an in-flight DNS transaction. It will cache the fake response and serve it happily on behalf of the attacker.

The terms “DNS spoofing” and “DNS hijacking” are used somewhat interchangeably, so don’t get too attached to them. They are just designations for broad classes of attacks which often overlap.

Practical example: DNS-enabled phishing

Serving the user a fake DNS record is usually not an attack in itself; usually, it will be the first step of a more sophisticated endeavor, such as phishing. Let’s take a look at how an attacker may use the inherent insecurity of DNS to carry out a phishing attack.

Let’s assume that an attacker is able to modify or outright fake the A (or AAAA, or CNAME) record of d3s.mff.cuni.cz, to serve you a fake website of the Department of Distributed and Dependable Systems. (Or substitute your bank’s website.) When you type that domain name into your browser, the following will happen with most browsers in their default configuration:

The browser will first resolve the domain name to the attacker-chosen IPv4 or IPv6 address, and will initiate an HTTP (as opposed to HTTPS) connection to it. This is the default behavior of many browsers when you don’t type the https:// scheme as part of the URL yourself. (Note: Chrome tries HTTPS first since version 90. Firefox can also default to HTTPS in case you visited the site over HTTPS in the past, see HSTS. Firefox will also default to HTTPS First in private browsing.)

A legit d3s.mff.cuni.cz web server will respond with a 301 redirect to itself:

~% curl -i http://d3s.mff.cuni.cz
HTTP/1.1 302 Found
Date: Tue, 13 Dec 2022 11:13:52 GMT
Server: Apache/2.4.29 (Ubuntu)
Location: https://d3s.mff.cuni.cz/
Content-Length: 289
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="https://d3s.mff.cuni.cz/">here</a>.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at d3s.mff.cuni.cz Port 80</address>
</body></html>

(You can see it’s an Apache web server serving us the redirect. It is configured to redirect all HTTP URLs to their HTTPS counterparts, which is the best practice.)

However, since the DNS response was spoofed, you’re not talking to the legit web server, so the game is different. The attacker’s goal is to trick you into thinking you’re looking at d3s.mff.cuni.cz. They now have two options:

  • They don’t perform the HTTPS redirect. In that case, the browser will try very hard to make the address bar as ugly and scary as possible to warn you that you’re using an insecure protocol (image source).

insecure

  • Or, they do perform the HTTPS redirect, but to their fake site served over HTTPS. However, they won’t be able to obtain an HTTPS server certificate for the original domain name (d3s.mff.cnui.cz), since they are not able to prove the ownership of the domain to a certification authority such as Let’s Encrypt or ZeroSSL. (To be resilient against DNS attacks, Let’s Encrypt resolves the hostname for which it’s issuing the certificate against multiple DNS servers.)

Since most users are nowadays quite vigilant about the address bar, especially when accessing sensitive websites like their Internet banking, the attacker has to go the alternate route, and serve you their fake website over HTTPS, albeit with a different domain name in the address bar. You will certainly notice that! Or will you?

In this text, I have deliberately misspelled ⅾ3s.mff.cuni.cz once. Did you notice? And would you notice in the address bar? Typosquatting is a term describing the deliberate use of a misspelled name. It’s a common technique used outside of the realm of domain names, for example with Python packages (paper). Chances are you’ll not notice the typo in the address bar, and you’ll feel safe because of a slick green lock icon telling you everything is perfectly fine. The icon isn’t wrong: your connection to the attacker’s server is indeed secure.

But let’s assume for a while that you are extremely vigilant, and you check each and every URL for typos. OK. In the above text, one of the d3s.mff.cuni.cz is not what they seem! Can you find it?

Depending on your system font, d3s.mff.cuni.cz and ⅾ3s.mff.cuni.cz might look exactly the same (they do on my machine), but they are not the same:

~% cat <<EOF | hexdump -C
Depending on your system font, d3s.mff.cuni.cz and ⅾ3s.mff.cuni.cz might look
exactly the same (they do on my machine), but they are _not_ the same:
EOF
00000000  44 65 70 65 6e 64 69 6e  67 20 6f 6e 20 79 6f 75  |Depending on you|
00000010  72 20 73 79 73 74 65 6d  20 66 6f 6e 74 2c 20 64  |r system font, d|
00000020  33 73 2e 6d 66 66 2e 63  75 6e 69 2e 63 7a 20 61  |3s.mff.cuni.cz a|
00000030  6e 64 20 e2 85 be 33 73  2e 6d 66 66 2e 63 75 6e  |nd ...3s.mff.cun|
00000040  69 2e 63 7a 20 6d 69 67  68 74 20 6c 6f 6f 6b 0a  |i.cz might look.|
00000050  65 78 61 63 74 6c 79 20  74 68 65 20 73 61 6d 65  |exactly the same|
00000060  20 28 74 68 65 79 20 64  6f 20 6f 6e 20 6d 79 20  | (they do on my |
00000070  6d 61 63 68 69 6e 65 29  2c 20 62 75 74 20 74 68  |machine), but th|
00000080  65 79 20 61 72 65 20 5f  6e 6f 74 5f 20 74 68 65  |ey are _not_ the|
00000090  20 73 61 6d 65 3a 0a                              | same:.|

Take a look at the output carefully: d3s.mff.cuni.cz is written with a d (UTF-8/ASCII byte 0x64) the first time, and a ⅾ (UTF-8 bytes 0xe2 0x85 0xbe) the second time. In case you wonder what the other letter which looks like d is, it’s a “small roman numeral five hundred”, a completely different Unicode code-point. The reason it looks exactly the same (or almost the same) is simple: there are too many things which ought to look like a d, but a limited number of creative designs for a d-looking glyph. So they’ll either be the exact same design, or at least they’ll look very similar.

Distinct Unicode code-points which are same or similar in appearance are called Unicode confusables. Check that link, you’ll find many other creative ways how to write d3s.mff.cuni.cz-looking text.

Now for the really bad news: Internationalized Domain Names (IDN) make it possible to include non-ASCII (Unicode) code-points in your domain names. So, the attacker can HTTPS redirect you to d3s.mff.ⅽuni.cz (see what I did there?—long live Roman numerals!) and get a TLS web certificate for that domain instead (they can—it’s their domain). Now will you notice that?

You cannot reasonably expect to notice this, unless your font doesn’t have a glyph for the confusable at all (and renders an ugly box), or happens to render the confusable in a radically different way.

Note: Technically this isn’t currently possible, since cz.nic, the .cz TLD administrator, does not allow IDNs (yet). See háčkyčárky.cz—the page is also available in English, just switch the language in the header—for rationale.


To summarize:

  • DNS is inherently insecure, and the system can be coerced into serving fake responses in multiple ways.
  • Practical attacks usually require several techniques (DNS spoofing to capture your browser, weak HTTPS policy to redirect you to the attacker’s site, typosquatting or confusables to make the site appear legit) to pull off successfully.
  • Almost every technology you rely upon is insecure. Don’t be fooled into thinking otherwise. If it’s secure, it’s usually less convenient, or it relies upon you to do the right thing (think ssh MITM warnings).

Mitigations

All is not lost. In the rest of the lecture, we’ll take care to secure DNS. But first, let’s secure our browser, to protect ourselves against the scenario described above. At a minimum, you’ll want to enable the following (links are for Firefox, but there are equivalents for Chrome/Chromium, etc.):

  • Enable HTTPS-Only Mode. This makes it impossible for the attacker to perform HTTPS redirection to their own site with a slightly different hostname. Since your browser checks the validity of the TLS certificate for the original hostname (e.g., d3s.mff.cuni.cz), even if the attacker serves you a fake DNS record, the TLS certificate of the web server won’t match.
  • Enable Punycode. This way, non-ASCII domain names cannot fool you anymore.
  • Or best of all, just use Arkenfox. This will enable a lot of different privacy and security options available in Firefox. The default config is extremely strict, you’ll need to tweak it a bit to make your browser usable with certain sites.
  • It may also be a good idea to use a password manager for your most critical logins: the password manager fills your credentials into the login only when the hostname matches byte-for-byte the one where you entered them originally. It therefore cannot be tricked into providing credentials to a spoofed site.
  • Configure DNSSEC on your domain, so that your DNS records cannot be spoofed.
  • Use a validating recursor such as Unbound to protect yourself against DNS-related attacks (at least for the domains which support DNSSEC).

Confusables in source code

Side note: just as was the case with typosquatting, Unicode confusables are a problem for much more than just domain names. Take the following snippet of Ruby which decides wheter nuclear warheads should be launched:

#!/usr/bin/env ruby

ALLOWED_TARGETS = ["dresden", "paris", "vienna"]

def missile_launch_allowed(target, secret_key)
  allowed = true
  аllowed = false if secret_key != 1234
  allowed = false unless ALLOWED_TARGETS.include?(target)
  allowed
end

puts(missile_launch_allowed("dresden", 9999))

It’s quite instructive to launch (pun intended) this, then run for bomb shelter. Ruby was chosen since it doesn’t require you to declare variables before first use, which is needed for this particular script to work.

About DNSSEC

In a nutshell, DNSSEC makes it possible to verify that the response you got from a DNS server can be trusted. It relies on asymmetric cryptography and several additional DNS record types.

First of all, let’s try to use DNSSEC. We can use drill:

~% drill -DT mff.cuni.cz
;; Number of trusted keys: 2
;; Domain: .
[T] . 172800 IN DNSKEY 256 3 8 ;{id = 18733 (zsk), size = 2048b}
. 172800 IN DNSKEY 257 3 8 ;{id = 20326 (ksk), size = 2048b}
Checking if signing key is trusted:
New key: .      172800  IN      DNSKEY  256 3 8 AwEAAeB54o2xvW6vY4qQZ0krDsEZCe6MsRWCqsXd4+cNJZMePnlV/xwDrIbbeH1SJzv742rOHzgAKM1/3SQHHSkoEIPx8XQdHAZBxfhaXl3e8c5WrE3aGXS5AeTWAkt85ccqWgKyitxjFmJEOol0BqS2xueltaDwgWcC10nPUY+y5l/kTOYyptYQS4gg1uJNXIob/R1XIEJ10ZCurkYqZxgqyHc7tZv09N23o9rnGdjnYiArH7FjlXD8Rvjde8YWkmfdbCEWnchrnxDK8KV2/ZvBpG/WYnRKXYPUceGCw59OJdJ5M7utkm547RB3eEd8CVVhbXopZlsKq3GCrBwaIVe9ci0= ;{id = 18733 (zsk), size = 2048b}
        Trusted key: .  172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= ;{id = 20326 (ksk), size = 2048b}
        Trusted key: .  166750  IN      DNSKEY  257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0= ;{id = 19036 (ksk), size = 2048b}
        Trusted key: .  172800  IN      DNSKEY  256 3 8 AwEAAeB54o2xvW6vY4qQZ0krDsEZCe6MsRWCqsXd4+cNJZMePnlV/xwDrIbbeH1SJzv742rOHzgAKM1/3SQHHSkoEIPx8XQdHAZBxfhaXl3e8c5WrE3aGXS5AeTWAkt85ccqWgKyitxjFmJEOol0BqS2xueltaDwgWcC10nPUY+y5l/kTOYyptYQS4gg1uJNXIob/R1XIEJ10ZCurkYqZxgqyHc7tZv09N23o9rnGdjnYiArH7FjlXD8Rvjde8YWkmfdbCEWnchrnxDK8KV2/ZvBpG/WYnRKXYPUceGCw59OJdJ5M7utkm547RB3eEd8CVVhbXopZlsKq3GCrBwaIVe9ci0= ;{id = 18733 (zsk), size = 2048b}
Key is now trusted!
        Trusted key: .  172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= ;{id = 20326 (ksk), size = 2048b}
[T] cz. 86400 IN DS 20237 13 2 cff0f3ecdbc529c1f0031ba1840bfb835853b9209ed1e508fff48451d7b778e2
;; Domain: cz.
[T] cz. 3600 IN DNSKEY 256 3 13 ;{id = 40910 (zsk), size = 256b}
cz. 3600 IN DNSKEY 257 3 13 ;{id = 20237 (ksk), size = 256b}
Checking if signing key is trusted:
New key: cz.    3600    IN      DNSKEY  256 3 13 NjrzkWo29GW+xwgdRTD95W3X8lUatdIZsg7jKFBGpHKKTCVJkIElo8SLPNV+NQphRrWaZ69egSTz1BQMkaqXkw== ;{id = 40910 (zsk), size = 256b}
        Trusted key: .  172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= ;{id = 20326 (ksk), size = 2048b}
        Trusted key: .  166750  IN      DNSKEY  257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0= ;{id = 19036 (ksk), size = 2048b}
        Trusted key: .  172800  IN      DNSKEY  256 3 8 AwEAAeB54o2xvW6vY4qQZ0krDsEZCe6MsRWCqsXd4+cNJZMePnlV/xwDrIbbeH1SJzv742rOHzgAKM1/3SQHHSkoEIPx8XQdHAZBxfhaXl3e8c5WrE3aGXS5AeTWAkt85ccqWgKyitxjFmJEOol0BqS2xueltaDwgWcC10nPUY+y5l/kTOYyptYQS4gg1uJNXIob/R1XIEJ10ZCurkYqZxgqyHc7tZv09N23o9rnGdjnYiArH7FjlXD8Rvjde8YWkmfdbCEWnchrnxDK8KV2/ZvBpG/WYnRKXYPUceGCw59OJdJ5M7utkm547RB3eEd8CVVhbXopZlsKq3GCrBwaIVe9ci0= ;{id = 18733 (zsk), size = 2048b}
        Trusted key: .  172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= ;{id = 20326 (ksk), size = 2048b}
        Trusted key: cz.        3600    IN      DNSKEY  256 3 13 NjrzkWo29GW+xwgdRTD95W3X8lUatdIZsg7jKFBGpHKKTCVJkIElo8SLPNV+NQphRrWaZ69egSTz1BQMkaqXkw== ;{id = 40910 (zsk), size = 256b}
Key is now trusted!
        Trusted key: cz.        3600    IN      DNSKEY  257 3 13 nqzH7xP1QU5UOVy/VvxFSlrB/XgX9JDJzj51PzIj35TXjZTyalTlAT/f7PAfaSD5mEG1N8Vk9NmI2nxgQqhzDQ== ;{id = 20237 (ksk), size = 256b}
[T] cuni.cz. 3600 IN DS 32757 13 2 50cdf44ed31b1473987b4759db87e7fa866647ff41bf2d468eb2a4bf7b42760c
;; Domain: cuni.cz.
[T] cuni.cz. 86400 IN DNSKEY 257 3 13 ;{id = 32757 (ksk), size = 256b}
Checking if signing key is trusted:
New key: cuni.cz.       86400   IN      DNSKEY  257 3 13 lkhf2k/fibAkAhLMq6x0l7LyUqiY1Lr3Ri4hAGFQn1e/73pSvZLMIm6mc5KJItRdqBQcS7/VgbaiWpJNB4hOFw== ;{id = 32757 (ksk), size = 256b}
        Trusted key: .  172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= ;{id = 20326 (ksk), size = 2048b}
        Trusted key: .  166750  IN      DNSKEY  257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0= ;{id = 19036 (ksk), size = 2048b}
        Trusted key: .  172800  IN      DNSKEY  256 3 8 AwEAAeB54o2xvW6vY4qQZ0krDsEZCe6MsRWCqsXd4+cNJZMePnlV/xwDrIbbeH1SJzv742rOHzgAKM1/3SQHHSkoEIPx8XQdHAZBxfhaXl3e8c5WrE3aGXS5AeTWAkt85ccqWgKyitxjFmJEOol0BqS2xueltaDwgWcC10nPUY+y5l/kTOYyptYQS4gg1uJNXIob/R1XIEJ10ZCurkYqZxgqyHc7tZv09N23o9rnGdjnYiArH7FjlXD8Rvjde8YWkmfdbCEWnchrnxDK8KV2/ZvBpG/WYnRKXYPUceGCw59OJdJ5M7utkm547RB3eEd8CVVhbXopZlsKq3GCrBwaIVe9ci0= ;{id = 18733 (zsk), size = 2048b}
        Trusted key: .  172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= ;{id = 20326 (ksk), size = 2048b}
        Trusted key: cz.        3600    IN      DNSKEY  256 3 13 NjrzkWo29GW+xwgdRTD95W3X8lUatdIZsg7jKFBGpHKKTCVJkIElo8SLPNV+NQphRrWaZ69egSTz1BQMkaqXkw== ;{id = 40910 (zsk), size = 256b}
        Trusted key: cz.        3600    IN      DNSKEY  257 3 13 nqzH7xP1QU5UOVy/VvxFSlrB/XgX9JDJzj51PzIj35TXjZTyalTlAT/f7PAfaSD5mEG1N8Vk9NmI2nxgQqhzDQ== ;{id = 20237 (ksk), size = 256b}
        Trusted key: cuni.cz.   86400   IN      DNSKEY  257 3 13 lkhf2k/fibAkAhLMq6x0l7LyUqiY1Lr3Ri4hAGFQn1e/73pSvZLMIm6mc5KJItRdqBQcS7/VgbaiWpJNB4hOFw== ;{id = 32757 (ksk), size = 256b}
Key is now trusted!
[T] mff.cuni.cz. 86400 IN DS 47500 13 2 6e5316a92bf1ac95f4f9916f57195d305a9e08ef7d0fdda6274f47b03a6abc19
;; Domain: mff.cuni.cz.
[T] mff.cuni.cz. 28800 IN DNSKEY 257 3 13 ;{id = 47500 (ksk), size = 256b}
[T] mff.cuni.cz.        28800   IN      A       195.113.27.221
;;[S] self sig OK; [B] bogus; [T] trusted; [U] unsigned

In the command above, -D enables DNSSEC in the query, and -T runs a DNSSEC trace. You can see the trace output above. The lines starting [T] indicate that a key is trusted. The last line in the output (the comment starting with ;;) is a legend for the flags. If you run the example above, you’ll notice it takes a while. Also, there’s quite a lot of output.

RRSIGs

DNSSEC adds several new record types to attach signatures to all records in a zone. As an example, let’s drop the -T flag, and just run a DNSSEC (-D) query for mff.cuni.cz again:

~% drill mff.cuni.cz
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 3269
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; mff.cuni.cz. IN      A

;; ANSWER SECTION:
mff.cuni.cz.    21135   IN      A       195.113.27.221

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 12 msec
;; SERVER: 8.8.8.8
;; WHEN: Tue Dec 13 19:39:25 2022
;; MSG SIZE  rcvd: 45

Compare that to a DNSSEC query, and notice the additional record (RRSIG, or RRset signature):

~% drill -D mff.cuni.cz
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 11920
;; flags: qr rd ra ad ; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; mff.cuni.cz. IN      A

;; ANSWER SECTION:
mff.cuni.cz.    21141   IN      A       195.113.27.221
mff.cuni.cz.    21141   IN      RRSIG   A 13 3 28800 20230305204805 20221119191842 47500 mff.cuni.cz. EAwgM0+3xvrROUPDp4qRpWALK6qMuqYHMbHXFbX9Hk7ba8yYVXS0lVvf6wXFwANyFKlvvNTsLoLModKbMkI9kQ==

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 23 msec
;; EDNS: version 0; flags: do ; udp: 512
;; SERVER: 8.8.8.8
;; WHEN: Tue Dec 13 19:39:18 2022
;; MSG SIZE  rcvd: 163

The RRSIG record contains the following information:

  • The name of the signed RRset (that’s just the name of the record, or mff.cuni.cz)
  • The type of the signed RRset (A)
  • The inception and expiration timestamp; the signature is valid between the inception and expiration timestamp, and invalid otherwise. These fields are presented as YYYYMMDDHHmmSS, and thus human readable.
  • Several other important fields, such as the type of signature algorithm used. See RFC4034 for details.
  • The base64-encoded cryptographic signature of the RRset (EAwgM0...). The signature is computed from the RRset (the exact set of records signed, in this case the single A record for mff.cuni.cz) and parts of the RRSIG record. The domain’s private key is used to produce the signature, and the signature can then be validated using the domain’s public key.

In other words, whenever you receive a DNS response, you can verify each record’s authenticity (that it is indeed the record as published by the domain’s authoritative server) and integrity (that it wasn’t modified in transit).

As you can see above, DNSSEC does not encrypt the records, and all DNS traffic is still clear-text. You still need to use DoT/DoH for encryption.

Chain of trust

The signature in the RRSIG is computed using the domain’s private key, which is, of course, kept secret. The public key is then used to verify the signature. The public key is published as a DNSKEY record within the zone itself, so that it’s easy to obtain it for anybody who wishes to verify the RRSIGs (verifying resolvers, such as Unbound, do that). Let’s query the DNSKEY record:

~% drill -D DNSKEY mff.cuni.cz
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 9632
;; flags: qr rd ra ad ; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; mff.cuni.cz. IN      DNSKEY

;; ANSWER SECTION:
mff.cuni.cz.    21600   IN      DNSKEY  257 3 13 1PMTgkDSUJEO8PbtFEtJ6sqtBUwlqv5yWMAQpedPoJtvJ9Oxoen3OJoFxEnZCFBCouNsR58PYdzYDowWEQAJVw== ;{id = 47500 (ksk), size = 256b}
mff.cuni.cz.    21600   IN      RRSIG   DNSKEY 13 3 28800 20230319201841 20221119191842 47500 mff.cuni.cz. yZvUmiVeja4HBZaSDKlX1dzkFo3onJ293BD7i7VS50SCefWEKVZQKp/Yu7kaia/PLXSNQA/XWAX2fteB+buFDA==

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 33 msec
;; EDNS: version 0; flags: do ; udp: 512
;; SERVER: 8.8.8.8
;; WHEN: Tue Dec 13 20:25:44 2022
;; MSG SIZE  rcvd: 227

The DNSKEY record contains the following information:

  • 16 flags bits, presented as an unsigned integer (257). One of the information encoded in the flags is whether this is a key signing key or zone signing key (to be discussed later).
  • Key algorithm (13), the list of algorithm is specified in RFC4034, appendix A1
  • The public key itself, base64 encoded (yZvUmi...). The exact format depends on the algorithm.

The DNSKEY record contains the public key material needed to verify the record signature. But how do we know we can trust the key? We cannot blindly trust the DNSKEY from the zone: a man-in-the middle could forge the records, sign them with their private key, and then forge the DNSKEY record as well. The signature would match, but that would say nothing about the validity of the signed records.

To indicate that the key is trusted, the parent zone (cuni.cz) will publish a DS (Delegation Signer) record:

~% drill -D DS mff.cuni.cz
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 32133
;; flags: qr rd ra ad ; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; mff.cuni.cz. IN      DS

;; ANSWER SECTION:
mff.cuni.cz.    21600   IN      DS      47500 13 2 6e5316a92bf1ac95f4f9916f57195d305a9e08ef7d0fdda6274f47b03a6abc19
mff.cuni.cz.    21600   IN      RRSIG   DS 13 3 86400 20221223202239 20221123201842 32757 cuni.cz. BvWsWUcxKpGn3wEMfeyy+4wRftcDwWXoW+ENifAdIea78kLuD/Bosj+Dkg9Ge3kjvzX/UqMDoTUWoM96tfKhpg==

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 34 msec
;; EDNS: version 0; flags: do ; udp: 512
;; SERVER: 8.8.8.8
;; WHEN: Tue Dec 13 21:05:54 2022
;; MSG SIZE  rcvd: 191

Crucially, the DS record contains a hash of the DNSKEY record stored in the child zone. This conveys the following message: “if you trust the parent zone, you can trust this key in the child zone”.

All that remains is recursion: to trust the DNSKEY of cuni.cz, you can query cz for the DS record of cuni.cz; to trust the DNSKEY of cz, you can query the root zone (.). The root zone key is trusted, and its public keys are distributed out-of-band. For example on Arch Linux, you’ll likely find out that you have dnssec-anchors installed:

% pacman -Ql dnssec-anchors
...
dnssec-anchors /etc/trusted-key.key
...

To summarize:

  • The public key of the root zone is trusted and obtained out of band, usually your package manager takes care of that (and also keeps the key up to date).
  • The root zone uses DS records to indicate that it trusts the DNSKEY records of the various TLDs, such as cz. (Of course it also uses NS records to delegate the TLDs, which are subdomains of the root zone, to the administrators of the TLDs.) For example, cz.nic is responsible for keeping their DS and NS records configured correctly within the root zone, and there’s likely a very strict process around that.
  • The TLD (say, cz) then publishes NS and DS records for DNSSEC-enabled subdomains, such as cuni.cz. The technical contacts for the domain are allowed to modify the NS and DS records associated with their domain, usually through some web interface or API provided by the domain registrar.
  • Finally, the individual RRsets, such as A mff.cuni.cz records, are signed with an RRSIG record.
  • This way, a chain of trust is established from the root zone to any DNSSEC-signed record.

The above description is a simplification, but is sufficient for now. DNSSEC requires more than the described new record types, and it also requires several extensions to the protocol. If you’re interested, check out this nice summary.

Key signing keys and zone signing keys

The best practice is to have two keys associated with a domain: a key signing key and a zone signing key.

  • The zone signing key (zsk) is the key which is actually used to produce the RRSIGs, i.e., to sign the RRsets.
  • The key signing key (ksk), on the other hand, is the one which is used to sign the zone signing key.
  • The DS record in the parent zone points (contains the hash of) the key signing key, not the zone signing key.

There are some advantages to splitting the responsibilities this way:

  • When you change the zsk of your domain, the DS record doesn’t have to change: the ksk is still the same (and trusted via the DS record), and the ksk is used to sign the zsk. In other words, you can sign your zone with a brand new zsk without having to propagate any changes (modified DS record) into the parent zone.
  • You can keep the ksk offline (and thus secure), since it’s needed only to sign new zsk’s. On the other hand, the zsk are needed whenever the zone changes, and need to be readily available—thus more likely to be exposed.

However, this setup is a bit more complex. Having a separate ksk and zsk is optional, and DNSSEC works with a single key just fine.

Fun fact, did you know that a key signing ceremony is held whenever the ksk is used to sign a new zsk for the root zone?

Next steps

During the next labs, you’ll enable DNSSEC on $login.una.