Skip to content
 

DNS security: TSIG

RFC 2845: Secret Key Transaction Authentication for DNS (TSIG) defines a method to authenticate DNS messages that are exchanged between two parties, provided they share a secret in advance.

This is relevant for certain critical DNS messages like zone transfers or dynamic updates, that have the potential of changing the information in the DNS and have thus a strong need to be protected. Of course TSIG does not prevent an attacker from injecting bogus or malicious information in such messages, but it makes it possible to detect the tampering or forgery at the receiver.
Another place where TSIG could be used (but it's almost never used) is to protect communication between a stub resolver and its recursive DNS server. Furthermore, the Bind DNS server uses TSIG (or something very similar, given that it's configured in the same way) to authenticate the communications with the rndc control program (usually on TCP port 953).

An important point is that TSIG protects communication between two parties, which makes it not very scalable when the number of communicating servers grows, as a key for each possible pair of communicating servers would be needed (that means, with N servers, a number of keys in the order of N2 on each host). However, when only two or a small number of parties are involved, TSIG is a good solution.

Essentially, the two communicating parties must share a secret (that should have been put in place in advance, of course in some secure way). When the shared secret is configured at both ends, it can be used to calculate an HMAC digest of the messages. The sender calculates the HMAC and adds it to the message; the receiver recalculates the HMAC independently and then compares it with the one it received included in the message. If the two values match, it can safely be assumed that the message is coming from the intended sender. (Well, strictly speaking all that can be assumed is that the message comes from some entity that knows the same secret key; if the key is secret as it should be, that means that it must be from the intended partner).

Implementation

In practice, TSIG is implemented as a "meta-RR", meaning that it appears in messages exchanged on the wire, but it does not appear in any zone file (which makes sense, given that it is something that must be calculated on the fly for each message sent). The RR includes, among other things, the name of the key used to calculate the HMAC, the algorithm used (currently only hmac-md5 seems to be supported/used), a timestamp to protect against replay attacks, and the HMAC itself. Note that the presence of a timestamp implies a good synchronization between the parties' clocks. The key name can be anything, as long as it matches the name of a key configured at both ends. The RFC suggests using a name that can be easily associated to the pair of servers using the key, like server1-server2.example.com; in the examples here we will be a bit more liberal.

Some rules to follow are:

  • If a TSIG RR is present, then it must be the last RR in the additional section;
  • If a request contains a TSIG, the response (if one is to be sent) must be signed, and must be signed with the same key;
  • If a request contained a TSIG, that must be included in the data covered by the response HMAC. For the client to be able to verify the response, it must thus store the original request HMAC until the answer is received;
  • Forwarding-only servers should pass the TSIG unchanged to their upstream servers. This makes it possible to have end-to-end TSIGs when forwarding servers are present in the path.

Examples

Resolver queries

Here is an example with dig communicating with a recursive resolver. On the recursive resolver (Bind here), a TSIG key is configured:

// this is in named.conf
key "secret-key" {
  algorithm hmac-md5;
  secret "Zm9vYmFy";
};

The value of the secret key must always be written base64-encoded. In this case, the secret key is "foobar", as anybody can check by feeding the strings to "base64 -d". Obviously, it's surely better to use a stronger password. One way is to use dnssec-keygen as described here.

The dig resolver allows to specify the key either in a file or directly on the command line, which is what's shown here for simplicity:

$ dig +multiline -y secret-key:Zm9vYmFy @10.10.0.10 test.foo.bar. A

; <<>> DiG 9.5.0-P2 <<>> +multiline -y secret-key @10.10.0.10 test.foo.bar. A
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28909
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3

;; QUESTION SECTION:
;test.foo.bar.          IN A

;; ANSWER SECTION:
test.foo.bar.           60 IN A 192.168.0.172
...

;; TSIG PSEUDOSECTION:
secret-key.             0 ANY TSIG hmac-md5.sig-alg.reg.int. 1262219517 300 16 (
                                2gNMCNh1maDstOzeJTMemg== ) 28909 NOERROR 0

;; Query time: 18 msec
;; SERVER: 10.10.0.10#53(10.10.0.10)
;; WHEN: Fri Jan 22 10:26:11 2010
;; MSG SIZE  rcvd: 194

In this case, the TSIG signatures were correct. Here's what happens if we supply the wrong key and the verification fails:

$ dig +multiline -y secret-key:ZnV1YmFy @10.10.0.10 test.foo.bar. A
;; Couldn't verify signature: tsig indicates error

; <<>> DiG 9.5.0-P2 <<>> +multiline -y secret-key @10.10.0.10 test.foo.bar. A
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOTAUTH, id: 26989
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;test.foo.bar.          IN A

;; TSIG PSEUDOSECTION:
secret-key.             0 ANY TSIG hmac-md5.sig-alg.reg.int. 1262219519 300 0 (
                                 ) 26989 BADSIG 0

;; Query time: 9 msec
;; SERVER: 10.10.0.10#53(10.10.0.10)
;; WHEN: Fri Jan 22 10:31:59 2010
;; MSG SIZE  rcvd: 94
;; WARNING -- Some TSIG could not be validated

And in the server's log:

client 192.168.0.1#55962: request has invalid signature: TSIG secret-key: tsig verify failure (BADSIG)
Zone transfer

For zone transfers, a configuration like the following is used (assuming 10.10.0.10 and 10.10.0.20 are the mater and the slave respectively):

// on the master
key "secret-key" {
  algorithm hmac-md5;
  secret "Zm9vYmFy";
};
...
server 10.10.0.20 { keys secret-key; };   // use the key when communicating with the slave
...
zone "example.com" {
  type master;
  allow-transfer { key secret-key; };
  file "/path/to/db.example.com";
};
// on the slave
key "secret-key" {
  algorithm hmac-md5;
  secret "Zm9vYmFy";
};
...
server 10.10.0.10 { keys secret-key; };   // use the key when communicating with the master
...
zone "example.com" {
  type slave;
  allow-notify { key secret-key; };
  ...
};

Each server is configured to sign messages to the peer, and the two allow- directives do what the name suggests, ie allow the action only if the message is signed with the specified key. So the slave accepts NOTIFY messages only if signed with the key (thus only from the master), and the master allows zone transfer requests only if signed with the key (thus only from the slave).

Dynamic DNS updates

A similar situation occurs when the TSIG key is used to protect dynamic DNS updates, such as when the dhcp server issues a new lease; the dhcp daemon must be configured with the secret key so its updates can be authenticated by the DNS server. Here is an example with ISC dhcpd:

# dhcpd.conf
...
ddns-updates on;
ddns-update-style interim;

key "secret-key" {
  algorithm hmac-md5;
  secret "Zm9vYmFy";
};
...
zone foo.bar. {
  primary 10.10.0.10;    # where to send dynamic updates for this zone
  key "secret-key";
}

# you can (and really should) also add a zone for reverse DNS
...
host baz {
  hardware ethernet 00:11:22:33:44:55;
  option host-name "baz";
  ddns-hostname "baz";
}

# other hosts here...

subnet 192.168.0.0 netmask 255.255.255.0 {
   range 192.168.0.1 192.168.0.100;
   option routers 192.168.0.254;
   ...
   option domain-name "foo.bar";
   ddns-domainname "foo.bar";
}

And on the server (Bind):

// named.conf
key "secret-key" {
  algorithm hmac-md5;
  secret "Zm9vYmFy";
};
...
zone "foo.bar" {
  type master;
  ...
  allow-update { key secret-key; };
};

When client baz gets its configuration via DHCP, the DNS records are created automatically:

$ dig @10.10.0.10 baz.foo.bar. A

; <<>> DiG 9.5.0-P2 <<>> @10.10.0.10 baz.foo.bar. A
; (1 server found)
;; global options:  printcmd
;; Got answer:
...

;; QUESTION SECTION:
;baz.foo.bar.                   IN      A

;; ANSWER SECTION:
baz.foo.bar.            300     IN      A       192.168.0.1
...
RNDC

In the case of Bind-rndc communication, a special command is provided, rndc-confgen, that generates the suitable configuration fragments to be included in both named.conf and rndc.conf (essentially it's a key statement block like those described above, plus other pieces of configuration to enable the control channel and allow only communications authenticated with the key on it).