DNS toolkit for Python supporting almost all record types with high-level and low-level DNS operations
85
Transaction Signature (TSIG) authentication functionality for securing DNS messages with cryptographic signatures. Provides key management, signature generation, and verification for authenticated DNS communication.
Sign DNS messages with TSIG authentication.
def sign(wire, keyring, keyname, fudge=300, original_id=None, tsig_error=0,
other_data=b'', algorithm='HMAC-MD5.SIG-ALG.REG.INT'):
"""
Sign a DNS message with TSIG.
Args:
wire (bytes): Wire format DNS message to sign
keyring (dict): TSIG keyring containing keys
keyname (str or dns.name.Name): Name of key to use
fudge (int): Time fudge factor in seconds (default 300)
original_id (int): Original message ID for error responses
tsig_error (int): TSIG error code
other_data (bytes): Additional data for error responses
algorithm (str): TSIG algorithm name
Returns:
bytes: Signed wire format message with TSIG record
"""
def validate(wire, keyring, request_mac, now=None, request_fudge=None):
"""
Validate TSIG signature on a DNS message.
Args:
wire (bytes): Wire format DNS message with TSIG
keyring (dict): TSIG keyring containing keys
request_mac (bytes): MAC from original request
now (float): Current time (default system time)
request_fudge (int): Fudge from original request
Returns:
tuple: (validated_wire, tsig_ctx) where validated_wire is the
message without TSIG and tsig_ctx is context for multi-message
Raises:
dns.tsig.BadSignature: If signature validation fails
dns.tsig.BadTime: If time is outside fudge window
dns.tsig.BadKey: If key is not found or invalid
"""Manage TSIG keys and keyrings for authentication.
def from_text(textring):
"""
Create a TSIG keyring from a dictionary of text key data.
Args:
textring (dict): Dictionary mapping key names to base64-encoded keys
{key_name: base64_key_data, ...}
Returns:
dict: TSIG keyring ready for use
Example:
keyring = dns.tsigkeyring.from_text({
'key1.example.com.': 'base64encodedkeydata',
'key2.example.com.': 'anotherkeyinbase64'
})
"""
def to_text(keyring):
"""
Convert a TSIG keyring to text format.
Args:
keyring (dict): TSIG keyring
Returns:
dict: Dictionary with base64-encoded key values
"""Supported TSIG algorithms and algorithm management.
# Standard TSIG algorithms
HMAC_MD5 = 'HMAC-MD5.SIG-ALG.REG.INT'
HMAC_SHA1 = 'hmac-sha1'
HMAC_SHA224 = 'hmac-sha224'
HMAC_SHA256 = 'hmac-sha256'
HMAC_SHA384 = 'hmac-sha384'
HMAC_SHA512 = 'hmac-sha512'
# Default algorithm
default_algorithm = HMAC_MD5
def algorithm_from_text(text):
"""
Convert algorithm name to canonical form.
Args:
text (str): Algorithm name
Returns:
dns.name.Name: Canonical algorithm name
"""
def algorithm_to_text(algorithm):
"""
Convert algorithm to text format.
Args:
algorithm (dns.name.Name): Algorithm name
Returns:
str: Text representation of algorithm
"""Multi-message TSIG context for zone transfers and other multi-message operations.
class HMACTSig:
"""
HMAC-based TSIG context for multi-message authentication.
Used for zone transfers and other operations that span multiple
DNS messages, maintaining authentication state across messages.
"""
def __init__(self, keyring, keyname, algorithm=default_algorithm):
"""
Initialize TSIG context.
Args:
keyring (dict): TSIG keyring
keyname (str or dns.name.Name): Key name to use
algorithm (str): TSIG algorithm
"""
def sign(self, wire, fudge=300):
"""
Sign a message using this TSIG context.
Args:
wire (bytes): Wire format message
fudge (int): Time fudge factor
Returns:
bytes: Signed message with TSIG
"""
def validate(self, wire):
"""
Validate a message using this TSIG context.
Args:
wire (bytes): Wire format message with TSIG
Returns:
bytes: Validated message without TSIG
"""TSIG resource record data structure and operations.
class TSIG(dns.rdata.Rdata):
"""
TSIG resource record data.
Attributes:
algorithm (dns.name.Name): TSIG algorithm name
time_signed (int): Time message was signed (seconds since epoch)
fudge (int): Time fudge factor in seconds
mac (bytes): Message authentication code
original_id (int): Original message ID
error (int): TSIG error code
other (bytes): Other data (for error responses)
"""
def __init__(self, rdclass, rdtype, algorithm, time_signed, fudge, mac,
original_id, error, other):
"""Initialize TSIG record data."""
def to_text(self, origin=None, relativize=True):
"""Convert TSIG to text format."""
def to_wire(self, file, compress=None, origin=None):
"""Convert TSIG to wire format."""import dns.message
import dns.query
import dns.tsigkeyring
import dns.name
# Create TSIG keyring
keyring = dns.tsigkeyring.from_text({
'mykey.example.com.': 'base64-encoded-shared-secret'
})
# Create query with TSIG
qname = dns.name.from_text('secure.example.com.')
query = dns.message.make_query(qname, dns.rdatatype.A)
query.use_tsig(keyring, 'mykey.example.com.')
# Send authenticated query
response = dns.query.udp(query, '192.0.2.1')
# Check TSIG authentication
if response.had_tsig():
if response.tsig_error() == 0:
print("TSIG authentication successful")
# Process authenticated response
for rrset in response.answer:
print(f"Authenticated answer: {rrset}")
else:
print(f"TSIG authentication failed: {response.tsig_error()}")
else:
print("No TSIG in response")import dns.tsigkeyring
import dns.tsig
import dns.message
# Create keyring with SHA-256 key
keyring = dns.tsigkeyring.from_text({
'sha256-key.example.com.': 'base64-sha256-key-data'
})
# Create message with SHA-256 TSIG
query = dns.message.make_query('test.example.com.', dns.rdatatype.A)
query.use_tsig(keyring, 'sha256-key.example.com.', algorithm=dns.tsig.HMAC_SHA256)
# Sign and send
response = dns.query.tcp(query, '192.0.2.1')
# Different algorithms for different security levels
algorithms = [
(dns.tsig.HMAC_SHA256, 'SHA-256 (recommended)'),
(dns.tsig.HMAC_SHA512, 'SHA-512 (high security)'),
(dns.tsig.HMAC_SHA1, 'SHA-1 (legacy compatibility)'),
(dns.tsig.HMAC_MD5, 'MD5 (legacy only)')
]
for alg, description in algorithms:
print(f"Algorithm: {alg} - {description}")import dns.query
import dns.tsig
import dns.tsigkeyring
import dns.name
# Setup TSIG for zone transfer
keyring = dns.tsigkeyring.from_text({
'xfer-key.example.com.': 'zone-transfer-key-data'
})
zone_name = dns.name.from_text('example.com.')
# Perform authenticated zone transfer
try:
# TSIG context is automatically managed by dns.query.xfr
messages = dns.query.xfr('192.0.2.1', zone_name,
keyring=keyring,
keyname='xfer-key.example.com.',
keyalgorithm=dns.tsig.HMAC_SHA256)
message_count = 0
for message in messages:
message_count += 1
# Verify each message has valid TSIG
if message.had_tsig():
if message.tsig_error() == 0:
print(f"Message {message_count}: TSIG valid")
else:
print(f"Message {message_count}: TSIG error {message.tsig_error()}")
break
else:
print(f"Message {message_count}: No TSIG (unexpected)")
break
# Process zone data
for rrset in message.answer:
if rrset.rdtype != dns.rdatatype.TSIG: # Skip TSIG records
print(f" {rrset.name} {rrset.rdtype}")
print(f"Zone transfer complete: {message_count} messages")
except dns.tsig.BadSignature:
print("TSIG signature validation failed")
except dns.tsig.BadTime:
print("TSIG time validation failed")
except dns.exception.DNSException as e:
print(f"Zone transfer failed: {e}")import dns.update
import dns.tsigkeyring
import dns.query
import dns.tsig
# Create authenticated update
keyring = dns.tsigkeyring.from_text({
'update-key.example.com.': 'dynamic-update-key-data'
})
update = dns.update.Update('example.com.',
keyring=keyring,
keyname='update-key.example.com.',
keyalgorithm=dns.tsig.HMAC_SHA256)
# Add authenticated updates
update.add('dynamic.example.com.', 300, 'A', '192.0.2.100')
update.add('dynamic.example.com.', 300, 'TXT', 'Updated with TSIG authentication')
# Send authenticated update
response = dns.query.tcp(update, '192.0.2.1')
if response.rcode() == dns.rcode.NOERROR:
print("Authenticated update successful")
# Verify response authentication
if response.had_tsig() and response.tsig_error() == 0:
print("Response TSIG verification successful")
else:
print(f"Update failed: {dns.rcode.to_text(response.rcode())}")import dns.tsig
import dns.tsigkeyring
import dns.message
import time
# Create keyring and message
keyring = dns.tsigkeyring.from_text({
'manual-key.example.com.': 'manual-signing-key-data'
})
query = dns.message.make_query('manual.example.com.', dns.rdatatype.A)
# Manual TSIG signing
wire = query.to_wire()
signed_wire = dns.tsig.sign(wire, keyring, 'manual-key.example.com.',
fudge=300, algorithm=dns.tsig.HMAC_SHA256)
print(f"Original message size: {len(wire)} bytes")
print(f"Signed message size: {len(signed_wire)} bytes")
# Manual TSIG validation
try:
validated_wire, tsig_ctx = dns.tsig.validate(signed_wire, keyring, b'')
print("Manual TSIG validation successful")
print(f"Validated message size: {len(validated_wire)} bytes")
# Parse validated message
validated_msg = dns.message.from_wire(validated_wire)
print(f"Validated message ID: {validated_msg.id}")
except dns.tsig.BadSignature:
print("TSIG signature validation failed")
except dns.tsig.BadTime:
print("TSIG time validation failed")import dns.tsigkeyring
import base64
import secrets
# Generate random key material
def generate_tsig_key(algorithm='sha256', key_size=32):
"""Generate random TSIG key material."""
key_bytes = secrets.token_bytes(key_size)
key_b64 = base64.b64encode(key_bytes).decode()
return key_b64
# Generate keys for different algorithms
keys = {
'md5-key.example.com.': generate_tsig_key('md5', 16), # 128-bit
'sha1-key.example.com.': generate_tsig_key('sha1', 20), # 160-bit
'sha256-key.example.com.': generate_tsig_key('sha256', 32), # 256-bit
'sha512-key.example.com.': generate_tsig_key('sha512', 64) # 512-bit
}
# Create keyring
keyring = dns.tsigkeyring.from_text(keys)
# Export keyring for storage
exported_keys = dns.tsigkeyring.to_text(keyring)
print("Generated TSIG keys:")
for name, key in exported_keys.items():
print(f" {name}: {key[:20]}...") # Show first 20 chars
# Key rotation example
old_keyring = dns.tsigkeyring.from_text({
'old-key.example.com.': 'old-key-material'
})
new_keyring = dns.tsigkeyring.from_text({
'new-key.example.com.': 'new-key-material'
})
# Combined keyring for transition period
combined_keyring = {**old_keyring, **new_keyring}import dns.tsig
import dns.message
import dns.query
import dns.tsigkeyring
def diagnose_tsig_failure(response):
"""Diagnose TSIG authentication failures."""
if not response.had_tsig():
return "No TSIG record in response"
error_code = response.tsig_error()
error_messages = {
0: "No error",
16: "Bad signature (BADSIG)",
17: "Bad key (BADKEY)",
18: "Bad time (BADTIME)",
19: "Bad mode (BADMODE)",
20: "Bad name (BADNAME)",
21: "Bad algorithm (BADALG)",
22: "Bad truncation (BADTRUNC)"
}
error_msg = error_messages.get(error_code, f"Unknown error code {error_code}")
return f"TSIG error: {error_msg}"
# Example usage with error handling
try:
keyring = dns.tsigkeyring.from_text({
'test-key.example.com.': 'test-key-data'
})
query = dns.message.make_query('test.example.com.', dns.rdatatype.A)
query.use_tsig(keyring, 'test-key.example.com.')
response = dns.query.udp(query, '192.0.2.1')
if response.had_tsig():
if response.tsig_error() == 0:
print("TSIG authentication successful")
else:
print(diagnose_tsig_failure(response))
else:
print("Server does not support TSIG")
except dns.tsig.BadSignature as e:
print(f"TSIG signature error: {e}")
except dns.tsig.BadTime as e:
print(f"TSIG time error: {e}")
except dns.tsig.BadKey as e:
print(f"TSIG key error: {e}")
except dns.exception.DNSException as e:
print(f"DNS error: {e}")# TSIG-specific error codes
NOERROR = 0 # No error
BADSIG = 16 # Bad signature
BADKEY = 17 # Bad key
BADTIME = 18 # Bad time
BADMODE = 19 # Bad mode
BADNAME = 20 # Bad name
BADALG = 21 # Bad algorithm
BADTRUNC = 22 # Bad truncationTSIG authentication integrates with:
dns.query functions for authenticated queriesdns.update for secure zone updatesdns.query.xfrdns.message for manual operationsclass TSIGError(DNSException):
"""Base class for TSIG errors."""
class BadSignature(TSIGError):
"""TSIG signature verification failed."""
class BadTime(TSIGError):
"""TSIG time is outside acceptable window."""
class BadKey(TSIGError):
"""TSIG key is unknown or invalid."""
class BadAlgorithm(TSIGError):
"""TSIG algorithm is not supported."""
class BadMode(TSIGError):
"""TSIG mode is invalid."""
class BadName(TSIGError):
"""TSIG key name is invalid."""
class BadTruncation(TSIGError):
"""TSIG truncation is invalid."""Install with Tessl CLI
npx tessl i tessl/pypi-dnspythondocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10