CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-dnspython

DNS toolkit for Python supporting almost all record types with high-level and low-level DNS operations

85

1.37x
Overview
Eval results
Files

tsig.mddocs/

TSIG Authentication

Transaction Signature (TSIG) authentication functionality for securing DNS messages with cryptographic signatures. Provides key management, signature generation, and verification for authenticated DNS communication.

Capabilities

TSIG Signing

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
    """

Key Management

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
    """

TSIG Algorithms

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
    """

TSIG Context

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 Record Data

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."""

Usage Examples

Basic TSIG Authentication

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")

TSIG with Different Algorithms

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}")

Multi-Message TSIG (Zone Transfers)

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}")

Dynamic Updates with TSIG

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())}")

Manual TSIG Operations

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")

Key Generation and Management

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}

Error Handling and Diagnostics

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 Error Codes

# 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 truncation

Integration Notes

TSIG authentication integrates with:

  • DNS Queries: Use with dns.query functions for authenticated queries
  • Dynamic Updates: Use with dns.update for secure zone updates
  • Zone Transfers: Automatic TSIG handling in dns.query.xfr
  • Message Handling: TSIG records in dns.message for manual operations
  • Key Management: Secure key storage and rotation practices

Exceptions

class 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-dnspython

docs

dns-constants.md

dns-exceptions.md

dns-messages.md

dns-names.md

dns-queries.md

dns-records.md

dns-resolution.md

dns-updates.md

dns-utilities.md

dns-zones.md

dnssec.md

index.md

tsig.md

tile.json