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:

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:

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.