DNSCurve: Usable security for DNS


Introduction
DNS users:
Why DNSCurve?
Installing DNSCurve
DNS data managers:
Why DNSCurve?
Installing DNSCurve
DNS implementors:
Caches
Forwarders
Protocol designers:
Cryptography
DNS integration
Attackers:
Forgery
Negative forgery
Replays
Query espionage
Database espionage
+ nsec3walker
CPU flooding
Amplification
+ dnssecamp

How to implement a DNSCurve cache

Are you the author of a DNS cache: software to "resolve" DNS names, talking to DNS servers around the Internet? This page explains how to add support for DNSCurve to your software, adding link-level public-key protection to DNS packets.

Overview of the DNSCurve protocol

When you're about to contact a DNS server, check the DNS server name to see whether it contains a correctly formatted DNSCurve public key. If it does, you can and should encrypt and authenticate the packets that you send to the server.

For example, when you're about to contact the DNS server ns1t.nytimes.com for the zone nytimes.com, go ahead and send packets the way you normally do: the name ns1t.nytimes.com does not contain a DNSCurve public key. However, when you're about to contact the DNS server uz5xgm1kx1zj8xsh51zp315k0rw7dcsgyxqh2sl7g8tjg25ltcvhyw.nytimes.com for the zone nytimes.com, encrypt and authenticate your packets; this name does contain a DNSCurve public key.

You need your own DNSCurve secret key and a corresponding DNSCurve public key. You can generate these when your program starts and then reuse the keys for talking to any number of servers. To prevent tracking of keys you may wish to regenerate keys frequently, for example once per minute or once per change of IP address, but if you are not concerned with tracking then there is no danger in using a single key for a long time (even years!).

You also need, for each outgoing DNSCurve packet, a 96-bit nonce. Nonce means "number used once." Once you have used a particular nonce to encrypt a packet, you must never use the same nonce to encrypt another packet. This requirement is essential for cryptographic security, and lasts as long as you are continuing to use the same secret key.

You can, for example, choose the nonce as a simple counter: 1 for the first packet, 2 for the second packet, etc. However, it is better to choose the nonce as a 64-bit counter followed by a 32-bit cryptographically generated random number; then blind attackers will have more trouble consuming your CPU time. It is even better to increase the counter to, e.g., the number of nanoseconds that have passed since the standard C epoch (the beginning of 1970 GMT) in your local clock, so that the current value of the counter does not leak your traffic rate. Do not decrease the counter if the clock jumps backwards! Another way to hide the counter, without using a clock, is to encrypt the counter under a secret key.

What you send to the server, instead of the original packet, is an expanded packet containing

  • your DNSCurve public key,
  • this packet's nonce, and
  • a cryptographic box that contains the original packet.
Putting the original packet into the box cryptographically protects the packet using your DNSCurve secret key, the server's DNSCurve public key, and this packet's nonce (extended to 24 bytes by appending 12 null bytes). DNSCurve has been co-developed with a public-domain Networking and Cryptography library containing a crypto_box function (specifically crypto_box_curve25519xsalsa20poly1305) that you can use. The cryptographic details are specified in the paper Cryptography in NaCl. You can gain speed by splitting crypto_box into crypto_box_beforenm and crypto_box_afternm, and caching the output of crypto_box_beforenm.

The server will open the box using your DNSCurve public key, the server's DNSCurve secret key, and this packet's nonce. What you receive back from the server will also be an expanded packet containing

  • a nonzero server-selected 96-bit nonce extension and
  • a cryptographic box.
Use your DNSCurve secret key, the server's DNSCurve public key, and the response packet's nonce to open the box. The response packet's nonce is, by definition, the client's 96-bit query nonce followed by the server's 96-bit nonce extension.

If the opening fails, the packet is forged; discard the packet and continue waiting for packets from the legitimate server. If the opening succeeds, inside the box you will find the original response packet; handle the packet exactly the same way that you would handle a normal DNS response packet.

Anchors

Your cache begins with the list of root servers as an anchor. You are encouraged to allow users to configure this anchor with DNSCurve keys. DNSCurve server names for the root servers protect your packets to and from those servers, so you securely learn the DNSCurve server names for .com and other top-level domains, so your packets to and from the .com servers are protected, so you securely learn the DNSCurve server names for nytimes.com, etc.

Perhaps your cache also allows users to configure anchors for various DNS subtrees: e.g., an anchor that specifies DNS servers for chase.com, immunizing the chase.com and *.chase.com lookups against outages of .com and of the DNS roots. Presumably these users are interested in immunizing the chase.com and *.chase.com lookups against all behavior of .com and of the DNS roots, not just outages, so you are encouraged to allow every anchor to be configured with DNSCurve keys.

Base-32 encoding

Sometimes DNSCurve communicates byte strings inside DNS query names. A byte string is interpreted as a number in little-endian form. Each 5-bit sequence of this number, from least significant to most significant, is encoded as one of the standard "digits" 0123456789bcdfghjklmnpqrstuvwxyz. A final sequence of fewer than 5 bits is zero-extended before encoding. Decoders must accept BCDFGHJKLMNPQRSTUVWXYZ as synonyms for bcdfghjklmnpqrstuvwxyz.

For example, the two-byte string with bytes 0x64,0x88 (i.e., 100,136 decimal) is interpreted as the integer 0x8864 (i.e., 34916). The bits 1000100001100100 of this integer are divided into 5-bit parts 00100, 00011, 00010, 00001, which in turn are encoded as 4, 3, 2, 1. The original string is therefore encoded as the string 4321.

DNSCurve public keys

DNSCurve public keys are 32-byte strings. These strings represent 255-bit numbers in little-endian form; the last byte is always between 0 and 127.

When a DNSCurve public key is communicated as part of a DNS server name it is first encoded as a 54-byte alphanumeric string. The first 3 bytes are the magic string uz5. The remaining 51 bytes are the base-32 encoding of the 255-bit public key (i.e., the 52-byte base-32 encoding of the 32-byte public key, with the final 0 removed).

To recognize a DNSCurve public key inside a DNS name, check each label of the name (after conversion from uppercase to lowercase) to see whether the label is

  • exactly 54 bytes,
  • the first 3 bytes being exactly the magic string uz5, and
  • each of the remaining 51 bytes being one of 0123456789bcdfghjklmnpqrstuvwxyz.
You must check every label of the name, not just the first label. If a name contains several DNSCurve public keys, use the first one (the one farthest from the root).

DNSCurve implementation options

You have a choice of two different ways to send expanded DNS query packets:
  • You can send a "streamlined" expanded DNS query packet directly to the server.
  • You can send a "TXT" expanded DNS query packet directly to the server.
The streamlined format is simpler, is smaller, and leaks less information. The TXT format has the advantage of passing smoothly through existing firewalls that enforce format constraints on outgoing DNS packets.

Every DNSCurve server is required to accept streamlined query packets. You can write a DNSCurve cache that uses only streamlined packets and that does not know anything about TXT-format packets; this cache will be suitable for users with direct Internet connections.

Every DNSCurve server is also required to accept TXT-format query packets. You can write a DNSCurve cache that uses only TXT-format packets and that does not know anything about streamlined packets.

You can also write a DNSCurve cache that uses TXT format by default but that gives users the option of switching to streamlined format to leak less information.

Implementation option 1: Streamlined DNSCurve

An expanded query packet in streamlined format has the following bytes:
  • 8 bytes: the string Q6fnvWj8.
  • 32 bytes: the client's DNSCurve public key.
  • 12 bytes: a client-selected nonce for this packet.
  • A cryptographic box containing the original DNS query packet.

An expanded response packet in streamlined format has the following bytes:

  • 8 bytes: the string R6fnvWJ8.
  • 12 bytes: the client's nonce.
  • 12 bytes: a server-selected nonce extension.
  • A cryptographic box containing the original DNS response packet.
Note that this streamlined response format does not repeat the client's query name, and in particular does not repeat the client's public key. However, it does repeat the client's nonce.

If you send an expanded DNS query packet in streamlined format then you are free to assume that the expanded response packet also uses streamlined format. If you send a non-DNSCurve DNS query packet then you are free to assume that the response is also a non-DNSCurve packet. The server is responsible for following these rules.

Implementation option 2: TXT DNSCurve

An expanded query packet in TXT format is, from the perspective of the original DNS protocol, a DNS TXT query packet. It has the following bytes:
  • A two-byte query ID selected by the client.
  • Byte \000 (meaning: query, opcode 0, not authoritative, not truncated, recursion not desired).
  • Byte \000 (meaning: recursion not available, no Z bits, RCODE 0).
  • Bytes \000\001 (meaning exactly one question).
  • Bytes \000\000\000\000\000\000 (meaning no answer records, no authority records, and no additional records).
  • A query name, in the usual RFC 1035 domain-name format.
  • Bytes \000\020 (meaning query type TXT).
  • Bytes \000\001 (meaning Internet query class).
The query name has several labels:
  • First, one or more labels, each label before the last being exactly 50 bytes, the last label being at most 50 bytes. The concatenation of these labels is the base-32 encoding of a 12-byte client-selected nonce for this packet followed by a cryptographic box containing the original DNS query packet.
  • Next, one 54-byte label: the client's DNSCurve public key, encoded as discussed above, except that the magic string is x1a instead of uz5.
  • Finally, zero or more additional labels specifying the name of the zone served by this server.

An expanded response packet in TXT format is, from the perspective of the original DNS protocol, a DNS TXT response packet. It has the following bytes:

  • A two-byte query ID matching the ID selected by the client.
  • Byte \204 (meaning: response, opcode 0, authoritative, not truncated, recursion not desired).
  • Byte \000 (meaning: recursion not available, no Z bits, RCODE 0).
  • Bytes \000\001 (meaning exactly one question).
  • Bytes \000\001 (meaning exactly one answer).
  • Bytes \000\000\000\000 (meaning no authority records and no additional records).
  • The query name sent by the client.
  • Bytes \000\020 (meaning query type TXT).
  • Bytes \000\001 (meaning Internet query class).
  • Bytes \300\014 (meaning the same query name sent by the client).
  • Bytes \000\020 (meaning response type TXT).
  • Bytes \000\001 (meaning Internet query class).
  • Bytes \000\000\000\000 (meaning TTL 0).
  • Two bytes stating, in big-endian form, the number of bytes of "RDATA".
  • The "RDATA": one or more strings of at most 255 bytes, each string preceded by a byte stating its length.
The concatenation of the strings inside the "RDATA" (without the length bytes) is a 12-byte server-selected nonce extension followed by a cryptographic box containing the original DNS response packet.

If you send an expanded DNS query packet in TXT format then you are free to assume that the expanded response packet also uses TXT format. The server is responsible for following this rule.

UDP and TCP

If a normal DNS response packet is larger than 512 bytes then the server replaces it by an explicitly truncated packet. The client then tries again through TCP. Servers are not required to support TCP if no responses are above 512 bytes; clients are permitted to try TCP only if the server has explicitly indicated truncation.

DNSCurve does not require TCP support from servers that were not already supporting TCP. If the original DNS response packet is at most 512 bytes then the server is permitted to send the expanded response packet as a UDP packet. DNSCurve clients are required to set aside a 4096-byte buffer for receiving a UDP response packet.

If the original DNS response packet is above 512 bytes then it is replaced by an explicitly truncated packet and the truncated packet is protected by DNSCurve. In this case the client tries again by TCP, sending its DNSCurve query packet through TCP and receiving the DNSCurve response through TCP.

TCP is considerably more expensive for clients and servers than UDP is, and TCP has no protection against denial of service, so server administrators are advised to stay below 512 bytes if possible. DNSCurve adds some denial-of-service protection for UDP but cannot do anything to help TCP.

Version

This is version 2017.01.22 of the in-implement.html web page.