How to implement a DNSCurve forwarder

Are you writing a DNSCurve forwarder,
or merging the same functionality into an existing server,
adding link-level public-key protection to DNS packets?
This page explains what you have to do.

Packet processing

You have to be ready for three different types of network packets:

Expanded query packets in streamlined format.
In this case your response must also be in streamlined format.

Expanded query packets in TXT format.
In this case your response must also be in TXT format.

Unprotected (non-DNSCurve) packets.
In this case your response must also be an unprotected packet.

You are required to set aside a 4096-byte buffer for receiving a query packet.

You have to be ready for all three types of queries through UDP.
If you send any truncated response packets
(i.e., if you have DNS response packets larger than 512 bytes)
then you also have to be ready for all three types of queries through TCP.

You need your own DNSCurve secret key and a corresponding DNSCurve public key.
You can generate these upon installation
and then reuse the keys for talking to any number of clients.
The administrator will distribute your public key as part of a DNS server name.

An expanded query packet contains

a client's DNSCurve public key,

this packet's nonce, and

a cryptographic box that contains the original packet.

Open the box using the client's DNSCurve public key,
your DNSCurve secret key,
and this packet's nonce.
If the opening fails, handle the packet as an unprotected packet.
If the opening succeeds, build a response packet
in exactly the same way that you normally would,
and then respond with an expanded packet containing

a 96-bit nonce extension and

a cryptographic box that contains the original response packet.

Putting the original response packet into the box
uses your DNSCurve secret key,
the client's DNSCurve public key,
and a packet nonce built as the 96-bit query nonce
followed by the 96-bit nonce extension.

Expanded DNS query format

The DNSCurve cache implementation page
describes a restrictive format for query packets sent by a DNSCurve cache.
This section describes a less restrictive format
for query packets received by a DNSCurve server,
accommodating changes by intermediate firewalls and "transparent" proxies.

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.

A byte equal to \000
(meaning: query, opcode 0, not authoritative, not truncated, recursion not desired)
or \001
(meaning: query, opcode 0, not authoritative, not truncated, recursion desired).
The server is free to discard the packet without a response if any other byte appears.
The server must discard the packet without a response
if this byte has top bit set (i.e., is between \200 and \377);
this is how DNS prevents loops.

An irrelevant byte.

Two bytes equal to \000\001 (meaning exactly one question).
The server is free to discard the packet without a response if these two bytes are not \000\001.

Six irrelevant bytes.

A query name, in the usual RFC 1035 domain-name format.
The server is required to accept query names that exceed the 255-byte RFC 1035 limit.
Names that exceed the 255-byte limit
will occur if the original query is more than about 100 bytes;
the exact limit depends on the length of the zone name.

Bytes \000\020 (meaning query type TXT).
If other bytes appear, the packet is not a DNSCurve packet,
and the server treats the packet as an unprotected DNS packet.

Bytes \000\001 (meaning Internet query class).
If other bytes appear, the packet is not a DNSCurve packet,
and the server treats the packet as an unprotected DNS packet.

Zero or more irrelevant bytes.

The query name has several labels:

First, one or more labels, each label 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 on the
DNSCurve cache implementation page,
except that the magic string is x1a instead of uz5.

Finally, zero or more additional labels.
Current DNSCurve clients will always use the name of the server's zone here,
but the server must accept arbitrary labels to allow future extensions.

If the query name does not have this format,
the server treats the packet as an unprotected DNS packet.
If the cryptographic box is unopenable,
the server treats the packet as an unprotected DNS packet.

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: the client's nonce for this packet.

A cryptographic box containing the original query packet.

If the cryptographic box is unopenable,
the server discards the packet without a response.

Expanded DNS response format

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)
if the client's third byte was \000; or \205 if the client's third byte was \001.

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.

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

8 bytes: the string R6fnvWJ8.

12 bytes: the client's nonce.

12 bytes: the server's nonce extension.

A cryptographic box containing the original 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.

Symmetric servers

Top-level sites such as .com have many different server names.
The list of .com server names and IP addresses
currently takes 509 bytes in a 512-byte UDP DNS packet.
Adding twelve different DNSCurve public keys would expand the list to a kilobyte.
It is better to condense the list of twelve server names
to (e.g.) three server names,
each with one DNSCurve public key and four IP addresses.
This means that one key is shared by four physical servers
(and in fact more servers with "anycasting").

If you are writing a forwarder to use at these sites
then you have to support nonce separation.
This means that several servers share a key but use separate nonces:
for example,
the first of four servers is configured to use top bits 100,
the second is configured to use top bits 101,
the third is configured to use top bits 110,
and the fourth is configured to use top bits 111.