TSIG and cookies
editSign with a TSIG key
A TSIG key (RFC 8945) is a secret shared with the server. Set TsigKey to sign every query of the client, and to reject any response that is not signed with the same key. Servers often require it for zone transfers:
// The name, algorithm and secret of the key configured on the server
var key = new DnsTsigKey("transfer-key", DnsTsigAlgorithm.HmacSha256, Convert.FromBase64String("xnRdh6uLqXIbHHeRPrsytA3ZCoYbpSUjYWBRftgDE/s="));
using var client = new DnsClient(serverAddress, DnsClientProtocol.Tcp, new DnsClientOptions
{
TsigKey = key,
});
// Every query is signed, and every response is verified: an unsigned or forged response throws
var recordCount = 0;
await foreach (var message in client.TransferZoneAsync("example.com"))
{
Assert.Equal(DnsResponseCode.NoError, message.Header.ResponseCode);
recordCount += message.Answers.Count;
}
Assert.Equal(5, recordCount);- A DnsTsigKey has the name of the key on the server, an algorithm and the secret. Key files store the secret in base64: decode it with
Convert.FromBase64String. - The algorithms are HmacSha256 (the one every server supports), HmacSha384, HmacSha512, and, for old servers, HmacSha1 and HmacMd5.
- The signatures carry the time. A response signed further from the clock of TimeProvider than Fudge (5 minutes by default) is rejected, and the server applies the same check to the queries: keep the clocks in sync.
Verification
Every query is signed: QueryAsync, SendAsync, ReverseLookupAsync, TransferZoneAsync, and the queries of local DNSSEC validation. The response is returned only when its signature is valid:
- A response that is not signed, signed with another key or algorithm, whose signature does not match, or signed too far from the current time, throws DnsProtocolException.
- In a zone transfer, the server may leave up to 99 messages in a row unsigned: the next signature covers them. The first and the last messages must be signed. TransferZoneAsync returns the unsigned messages only once that signature is verified.
- When the server cannot verify the query, the response code is the TSIG error: BadKey (the server does not know the key), BadSignature (the secret differs) or BadTime (the clocks differ by more than the fudge). A BadKey or BadSignature response cannot be authenticated, so it must carry no records; a BadTime response must be signed. Any other error in the TSIG record throws DnsProtocolException. BadSignature has the value 16 of BadVersion: compare the values, not the names.
- The signature of a response is the DnsTsigRecord, the last of its AdditionalRecords.
Use DNS cookies
DNS cookies (RFC 7873) make it harder for an attacker who does not see the traffic to forge responses, and let the server recognize the client. Set EnableCookies:
using var client = new DnsClient(serverAddress, DnsClientProtocol.Tcp, new DnsClientOptions
{
EnableCookies = true,
});
// The first query sends a client cookie; the next ones also return the cookie the server sent back
var response = await client.QueryAsync("www.example.com", DnsQueryType.A);
Assert.Equal(DnsResponseCode.NoError, response.Header.ResponseCode);
response = await client.QueryAsync("www.example.com", DnsQueryType.A);
Assert.Equal(DnsResponseCode.NoError, response.Header.ResponseCode);- Each DnsClient has its own random client cookie. The server cookie of the last response is sent with the next queries.
- A response with a cookie that is not the client cookie throws DnsProtocolException. A server without cookies answers as usual.
- A query (or a zone transfer) answered BadCookie is sent again once, with the server cookie of the answer. If the server answers BadCookie again, that response is returned.
- Cookies need EDNS: creating a client with
EnableEdns = falseandEnableCookies = truethrowsArgumentException. - Cookies do not authenticate the answer: use TSIG, an encrypted transport or DNSSEC when it must be trusted.