0
# TSIG Authentication
1
2
Transaction Signature (TSIG) authentication functionality for securing DNS messages with cryptographic signatures. Provides key management, signature generation, and verification for authenticated DNS communication.
3
4
## Capabilities
5
6
### TSIG Signing
7
8
Sign DNS messages with TSIG authentication.
9
10
```python { .api }
11
def sign(wire, keyring, keyname, fudge=300, original_id=None, tsig_error=0,
12
other_data=b'', algorithm='HMAC-MD5.SIG-ALG.REG.INT'):
13
"""
14
Sign a DNS message with TSIG.
15
16
Args:
17
wire (bytes): Wire format DNS message to sign
18
keyring (dict): TSIG keyring containing keys
19
keyname (str or dns.name.Name): Name of key to use
20
fudge (int): Time fudge factor in seconds (default 300)
21
original_id (int): Original message ID for error responses
22
tsig_error (int): TSIG error code
23
other_data (bytes): Additional data for error responses
24
algorithm (str): TSIG algorithm name
25
26
Returns:
27
bytes: Signed wire format message with TSIG record
28
"""
29
30
def validate(wire, keyring, request_mac, now=None, request_fudge=None):
31
"""
32
Validate TSIG signature on a DNS message.
33
34
Args:
35
wire (bytes): Wire format DNS message with TSIG
36
keyring (dict): TSIG keyring containing keys
37
request_mac (bytes): MAC from original request
38
now (float): Current time (default system time)
39
request_fudge (int): Fudge from original request
40
41
Returns:
42
tuple: (validated_wire, tsig_ctx) where validated_wire is the
43
message without TSIG and tsig_ctx is context for multi-message
44
45
Raises:
46
dns.tsig.BadSignature: If signature validation fails
47
dns.tsig.BadTime: If time is outside fudge window
48
dns.tsig.BadKey: If key is not found or invalid
49
"""
50
```
51
52
### Key Management
53
54
Manage TSIG keys and keyrings for authentication.
55
56
```python { .api }
57
def from_text(textring):
58
"""
59
Create a TSIG keyring from a dictionary of text key data.
60
61
Args:
62
textring (dict): Dictionary mapping key names to base64-encoded keys
63
{key_name: base64_key_data, ...}
64
65
Returns:
66
dict: TSIG keyring ready for use
67
68
Example:
69
keyring = dns.tsigkeyring.from_text({
70
'key1.example.com.': 'base64encodedkeydata',
71
'key2.example.com.': 'anotherkeyinbase64'
72
})
73
"""
74
75
def to_text(keyring):
76
"""
77
Convert a TSIG keyring to text format.
78
79
Args:
80
keyring (dict): TSIG keyring
81
82
Returns:
83
dict: Dictionary with base64-encoded key values
84
"""
85
```
86
87
### TSIG Algorithms
88
89
Supported TSIG algorithms and algorithm management.
90
91
```python { .api }
92
# Standard TSIG algorithms
93
HMAC_MD5 = 'HMAC-MD5.SIG-ALG.REG.INT'
94
HMAC_SHA1 = 'hmac-sha1'
95
HMAC_SHA224 = 'hmac-sha224'
96
HMAC_SHA256 = 'hmac-sha256'
97
HMAC_SHA384 = 'hmac-sha384'
98
HMAC_SHA512 = 'hmac-sha512'
99
100
# Default algorithm
101
default_algorithm = HMAC_MD5
102
103
def algorithm_from_text(text):
104
"""
105
Convert algorithm name to canonical form.
106
107
Args:
108
text (str): Algorithm name
109
110
Returns:
111
dns.name.Name: Canonical algorithm name
112
"""
113
114
def algorithm_to_text(algorithm):
115
"""
116
Convert algorithm to text format.
117
118
Args:
119
algorithm (dns.name.Name): Algorithm name
120
121
Returns:
122
str: Text representation of algorithm
123
"""
124
```
125
126
### TSIG Context
127
128
Multi-message TSIG context for zone transfers and other multi-message operations.
129
130
```python { .api }
131
class HMACTSig:
132
"""
133
HMAC-based TSIG context for multi-message authentication.
134
135
Used for zone transfers and other operations that span multiple
136
DNS messages, maintaining authentication state across messages.
137
"""
138
139
def __init__(self, keyring, keyname, algorithm=default_algorithm):
140
"""
141
Initialize TSIG context.
142
143
Args:
144
keyring (dict): TSIG keyring
145
keyname (str or dns.name.Name): Key name to use
146
algorithm (str): TSIG algorithm
147
"""
148
149
def sign(self, wire, fudge=300):
150
"""
151
Sign a message using this TSIG context.
152
153
Args:
154
wire (bytes): Wire format message
155
fudge (int): Time fudge factor
156
157
Returns:
158
bytes: Signed message with TSIG
159
"""
160
161
def validate(self, wire):
162
"""
163
Validate a message using this TSIG context.
164
165
Args:
166
wire (bytes): Wire format message with TSIG
167
168
Returns:
169
bytes: Validated message without TSIG
170
"""
171
```
172
173
### TSIG Record Data
174
175
TSIG resource record data structure and operations.
176
177
```python { .api }
178
class TSIG(dns.rdata.Rdata):
179
"""
180
TSIG resource record data.
181
182
Attributes:
183
algorithm (dns.name.Name): TSIG algorithm name
184
time_signed (int): Time message was signed (seconds since epoch)
185
fudge (int): Time fudge factor in seconds
186
mac (bytes): Message authentication code
187
original_id (int): Original message ID
188
error (int): TSIG error code
189
other (bytes): Other data (for error responses)
190
"""
191
192
def __init__(self, rdclass, rdtype, algorithm, time_signed, fudge, mac,
193
original_id, error, other):
194
"""Initialize TSIG record data."""
195
196
def to_text(self, origin=None, relativize=True):
197
"""Convert TSIG to text format."""
198
199
def to_wire(self, file, compress=None, origin=None):
200
"""Convert TSIG to wire format."""
201
```
202
203
## Usage Examples
204
205
### Basic TSIG Authentication
206
207
```python
208
import dns.message
209
import dns.query
210
import dns.tsigkeyring
211
import dns.name
212
213
# Create TSIG keyring
214
keyring = dns.tsigkeyring.from_text({
215
'mykey.example.com.': 'base64-encoded-shared-secret'
216
})
217
218
# Create query with TSIG
219
qname = dns.name.from_text('secure.example.com.')
220
query = dns.message.make_query(qname, dns.rdatatype.A)
221
query.use_tsig(keyring, 'mykey.example.com.')
222
223
# Send authenticated query
224
response = dns.query.udp(query, '192.0.2.1')
225
226
# Check TSIG authentication
227
if response.had_tsig():
228
if response.tsig_error() == 0:
229
print("TSIG authentication successful")
230
231
# Process authenticated response
232
for rrset in response.answer:
233
print(f"Authenticated answer: {rrset}")
234
else:
235
print(f"TSIG authentication failed: {response.tsig_error()}")
236
else:
237
print("No TSIG in response")
238
```
239
240
### TSIG with Different Algorithms
241
242
```python
243
import dns.tsigkeyring
244
import dns.tsig
245
import dns.message
246
247
# Create keyring with SHA-256 key
248
keyring = dns.tsigkeyring.from_text({
249
'sha256-key.example.com.': 'base64-sha256-key-data'
250
})
251
252
# Create message with SHA-256 TSIG
253
query = dns.message.make_query('test.example.com.', dns.rdatatype.A)
254
query.use_tsig(keyring, 'sha256-key.example.com.', algorithm=dns.tsig.HMAC_SHA256)
255
256
# Sign and send
257
response = dns.query.tcp(query, '192.0.2.1')
258
259
# Different algorithms for different security levels
260
algorithms = [
261
(dns.tsig.HMAC_SHA256, 'SHA-256 (recommended)'),
262
(dns.tsig.HMAC_SHA512, 'SHA-512 (high security)'),
263
(dns.tsig.HMAC_SHA1, 'SHA-1 (legacy compatibility)'),
264
(dns.tsig.HMAC_MD5, 'MD5 (legacy only)')
265
]
266
267
for alg, description in algorithms:
268
print(f"Algorithm: {alg} - {description}")
269
```
270
271
### Multi-Message TSIG (Zone Transfers)
272
273
```python
274
import dns.query
275
import dns.tsig
276
import dns.tsigkeyring
277
import dns.name
278
279
# Setup TSIG for zone transfer
280
keyring = dns.tsigkeyring.from_text({
281
'xfer-key.example.com.': 'zone-transfer-key-data'
282
})
283
284
zone_name = dns.name.from_text('example.com.')
285
286
# Perform authenticated zone transfer
287
try:
288
# TSIG context is automatically managed by dns.query.xfr
289
messages = dns.query.xfr('192.0.2.1', zone_name,
290
keyring=keyring,
291
keyname='xfer-key.example.com.',
292
keyalgorithm=dns.tsig.HMAC_SHA256)
293
294
message_count = 0
295
for message in messages:
296
message_count += 1
297
298
# Verify each message has valid TSIG
299
if message.had_tsig():
300
if message.tsig_error() == 0:
301
print(f"Message {message_count}: TSIG valid")
302
else:
303
print(f"Message {message_count}: TSIG error {message.tsig_error()}")
304
break
305
else:
306
print(f"Message {message_count}: No TSIG (unexpected)")
307
break
308
309
# Process zone data
310
for rrset in message.answer:
311
if rrset.rdtype != dns.rdatatype.TSIG: # Skip TSIG records
312
print(f" {rrset.name} {rrset.rdtype}")
313
314
print(f"Zone transfer complete: {message_count} messages")
315
316
except dns.tsig.BadSignature:
317
print("TSIG signature validation failed")
318
except dns.tsig.BadTime:
319
print("TSIG time validation failed")
320
except dns.exception.DNSException as e:
321
print(f"Zone transfer failed: {e}")
322
```
323
324
### Dynamic Updates with TSIG
325
326
```python
327
import dns.update
328
import dns.tsigkeyring
329
import dns.query
330
import dns.tsig
331
332
# Create authenticated update
333
keyring = dns.tsigkeyring.from_text({
334
'update-key.example.com.': 'dynamic-update-key-data'
335
})
336
337
update = dns.update.Update('example.com.',
338
keyring=keyring,
339
keyname='update-key.example.com.',
340
keyalgorithm=dns.tsig.HMAC_SHA256)
341
342
# Add authenticated updates
343
update.add('dynamic.example.com.', 300, 'A', '192.0.2.100')
344
update.add('dynamic.example.com.', 300, 'TXT', 'Updated with TSIG authentication')
345
346
# Send authenticated update
347
response = dns.query.tcp(update, '192.0.2.1')
348
349
if response.rcode() == dns.rcode.NOERROR:
350
print("Authenticated update successful")
351
352
# Verify response authentication
353
if response.had_tsig() and response.tsig_error() == 0:
354
print("Response TSIG verification successful")
355
else:
356
print(f"Update failed: {dns.rcode.to_text(response.rcode())}")
357
```
358
359
### Manual TSIG Operations
360
361
```python
362
import dns.tsig
363
import dns.tsigkeyring
364
import dns.message
365
import time
366
367
# Create keyring and message
368
keyring = dns.tsigkeyring.from_text({
369
'manual-key.example.com.': 'manual-signing-key-data'
370
})
371
372
query = dns.message.make_query('manual.example.com.', dns.rdatatype.A)
373
374
# Manual TSIG signing
375
wire = query.to_wire()
376
signed_wire = dns.tsig.sign(wire, keyring, 'manual-key.example.com.',
377
fudge=300, algorithm=dns.tsig.HMAC_SHA256)
378
379
print(f"Original message size: {len(wire)} bytes")
380
print(f"Signed message size: {len(signed_wire)} bytes")
381
382
# Manual TSIG validation
383
try:
384
validated_wire, tsig_ctx = dns.tsig.validate(signed_wire, keyring, b'')
385
print("Manual TSIG validation successful")
386
print(f"Validated message size: {len(validated_wire)} bytes")
387
388
# Parse validated message
389
validated_msg = dns.message.from_wire(validated_wire)
390
print(f"Validated message ID: {validated_msg.id}")
391
392
except dns.tsig.BadSignature:
393
print("TSIG signature validation failed")
394
except dns.tsig.BadTime:
395
print("TSIG time validation failed")
396
```
397
398
### Key Generation and Management
399
400
```python
401
import dns.tsigkeyring
402
import base64
403
import secrets
404
405
# Generate random key material
406
def generate_tsig_key(algorithm='sha256', key_size=32):
407
"""Generate random TSIG key material."""
408
key_bytes = secrets.token_bytes(key_size)
409
key_b64 = base64.b64encode(key_bytes).decode()
410
return key_b64
411
412
# Generate keys for different algorithms
413
keys = {
414
'md5-key.example.com.': generate_tsig_key('md5', 16), # 128-bit
415
'sha1-key.example.com.': generate_tsig_key('sha1', 20), # 160-bit
416
'sha256-key.example.com.': generate_tsig_key('sha256', 32), # 256-bit
417
'sha512-key.example.com.': generate_tsig_key('sha512', 64) # 512-bit
418
}
419
420
# Create keyring
421
keyring = dns.tsigkeyring.from_text(keys)
422
423
# Export keyring for storage
424
exported_keys = dns.tsigkeyring.to_text(keyring)
425
print("Generated TSIG keys:")
426
for name, key in exported_keys.items():
427
print(f" {name}: {key[:20]}...") # Show first 20 chars
428
429
# Key rotation example
430
old_keyring = dns.tsigkeyring.from_text({
431
'old-key.example.com.': 'old-key-material'
432
})
433
434
new_keyring = dns.tsigkeyring.from_text({
435
'new-key.example.com.': 'new-key-material'
436
})
437
438
# Combined keyring for transition period
439
combined_keyring = {**old_keyring, **new_keyring}
440
```
441
442
### Error Handling and Diagnostics
443
444
```python
445
import dns.tsig
446
import dns.message
447
import dns.query
448
import dns.tsigkeyring
449
450
def diagnose_tsig_failure(response):
451
"""Diagnose TSIG authentication failures."""
452
453
if not response.had_tsig():
454
return "No TSIG record in response"
455
456
error_code = response.tsig_error()
457
error_messages = {
458
0: "No error",
459
16: "Bad signature (BADSIG)",
460
17: "Bad key (BADKEY)",
461
18: "Bad time (BADTIME)",
462
19: "Bad mode (BADMODE)",
463
20: "Bad name (BADNAME)",
464
21: "Bad algorithm (BADALG)",
465
22: "Bad truncation (BADTRUNC)"
466
}
467
468
error_msg = error_messages.get(error_code, f"Unknown error code {error_code}")
469
return f"TSIG error: {error_msg}"
470
471
# Example usage with error handling
472
try:
473
keyring = dns.tsigkeyring.from_text({
474
'test-key.example.com.': 'test-key-data'
475
})
476
477
query = dns.message.make_query('test.example.com.', dns.rdatatype.A)
478
query.use_tsig(keyring, 'test-key.example.com.')
479
480
response = dns.query.udp(query, '192.0.2.1')
481
482
if response.had_tsig():
483
if response.tsig_error() == 0:
484
print("TSIG authentication successful")
485
else:
486
print(diagnose_tsig_failure(response))
487
else:
488
print("Server does not support TSIG")
489
490
except dns.tsig.BadSignature as e:
491
print(f"TSIG signature error: {e}")
492
except dns.tsig.BadTime as e:
493
print(f"TSIG time error: {e}")
494
except dns.tsig.BadKey as e:
495
print(f"TSIG key error: {e}")
496
except dns.exception.DNSException as e:
497
print(f"DNS error: {e}")
498
```
499
500
## TSIG Error Codes
501
502
```python { .api }
503
# TSIG-specific error codes
504
NOERROR = 0 # No error
505
BADSIG = 16 # Bad signature
506
BADKEY = 17 # Bad key
507
BADTIME = 18 # Bad time
508
BADMODE = 19 # Bad mode
509
BADNAME = 20 # Bad name
510
BADALG = 21 # Bad algorithm
511
BADTRUNC = 22 # Bad truncation
512
```
513
514
## Integration Notes
515
516
TSIG authentication integrates with:
517
518
- **DNS Queries**: Use with `dns.query` functions for authenticated queries
519
- **Dynamic Updates**: Use with `dns.update` for secure zone updates
520
- **Zone Transfers**: Automatic TSIG handling in `dns.query.xfr`
521
- **Message Handling**: TSIG records in `dns.message` for manual operations
522
- **Key Management**: Secure key storage and rotation practices
523
524
## Exceptions
525
526
```python { .api }
527
class TSIGError(DNSException):
528
"""Base class for TSIG errors."""
529
530
class BadSignature(TSIGError):
531
"""TSIG signature verification failed."""
532
533
class BadTime(TSIGError):
534
"""TSIG time is outside acceptable window."""
535
536
class BadKey(TSIGError):
537
"""TSIG key is unknown or invalid."""
538
539
class BadAlgorithm(TSIGError):
540
"""TSIG algorithm is not supported."""
541
542
class BadMode(TSIGError):
543
"""TSIG mode is invalid."""
544
545
class BadName(TSIGError):
546
"""TSIG key name is invalid."""
547
548
class BadTruncation(TSIGError):
549
"""TSIG truncation is invalid."""
550
```