DNS toolkit for Python supporting almost all record types with high-level and low-level DNS operations
85
DNS Security Extensions (DNSSEC) functionality for validating DNS signatures, handling cryptographic keys, and performing security operations. Provides complete DNSSEC validation and key management capabilities.
Validate DNSSEC signatures and verify resource record authenticity.
def validate(rrset, rrsigset, keys, origin=None, now=None):
"""
Validate an RRset against its RRSIG records using provided keys.
Args:
rrset (dns.rrset.RRset): Resource record set to validate
rrsigset (dns.rrset.RRset): RRSIG records for validation
keys (dict): Mapping from key names to DNSKEY records
origin (dns.name.Name): Origin for relative names
now (float): Current time (default current time)
Raises:
dns.dnssec.ValidationFailure: If validation fails
dns.dnssec.UnsupportedAlgorithm: If algorithm not supported
"""
def validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
"""
Validate an RRset against a single RRSIG record.
Args:
rrset (dns.rrset.RRset): Resource record set to validate
rrsig (dns.rdata.RRSIG): RRSIG record for validation
keys (dict): Mapping from key names to DNSKEY records
origin (dns.name.Name): Origin for relative names
now (float): Current time (default current time)
Raises:
dns.dnssec.ValidationFailure: If validation fails
dns.dnssec.UnsupportedAlgorithm: If algorithm not supported
"""
def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
"""
Internal validation function with detailed error information.
Returns validation status and error details for diagnostic purposes.
"""Handle DNSKEY records and perform key-related cryptographic operations.
def make_ds(name, key, algorithm, origin=None):
"""
Create a DS record from a DNSKEY record.
Args:
name (dns.name.Name): Key owner name
key (dns.rdata.DNSKEY): DNSKEY record
algorithm (int): Digest algorithm (1=SHA-1, 2=SHA-256, 4=SHA-384)
origin (dns.name.Name): Origin for relative names
Returns:
dns.rdata.DS: Generated DS record
"""
def key_id(key, origin=None):
"""
Calculate the key ID (key tag) for a DNSKEY record.
Args:
key (dns.rdata.DNSKEY): DNSKEY record
origin (dns.name.Name): Origin parameter (historical, not needed)
Returns:
int: Key tag (0-65535)
"""
def make_cds(name, key, algorithm, origin=None):
"""
Create a CDS record from a DNSKEY record.
Args:
name (dns.name.Name): Key owner name
key (dns.rdata.DNSKEY): DNSKEY record
algorithm (int): Digest algorithm
origin (dns.name.Name): Origin for relative names
Returns:
dns.rdata.CDS: Generated CDS record
"""
def make_cdnskey(key):
"""
Create a CDNSKEY record from a DNSKEY record.
Args:
key (dns.rdata.DNSKEY): DNSKEY record
Returns:
dns.rdata.CDNSKEY: Generated CDNSKEY record
"""DNSSEC algorithm constants and algorithm support functions.
# DNSSEC algorithms
RSAMD5 = 1 # RSA/MD5 (deprecated)
DH = 2 # Diffie-Hellman
DSA = 3 # DSA/SHA-1
ECC = 4 # Elliptic curve cryptography
RSASHA1 = 5 # RSA/SHA-1
DSANSEC3SHA1 = 6 # DSA-NSEC3-SHA1
RSASHA1NSEC3SHA1 = 7 # RSASHA1-NSEC3-SHA1
RSASHA256 = 8 # RSA/SHA-256
RSASHA512 = 10 # RSA/SHA-512
ECCGOST = 12 # GOST R 34.10-2001
ECDSAP256SHA256 = 13 # ECDSA Curve P-256 with SHA-256
ECDSAP384SHA384 = 14 # ECDSA Curve P-384 with SHA-384
ED25519 = 15 # Ed25519
ED448 = 16 # Ed448
PRIVATEDNS = 253 # Private use DNS
PRIVATEOID = 254 # Private use OID
# Digest algorithms
SHA1 = 1 # SHA-1
SHA256 = 2 # SHA-256
GOST = 3 # GOST R 34.11-94
SHA384 = 4 # SHA-384
def algorithm_from_text(text):
"""
Convert algorithm name to number.
Args:
text (str): Algorithm name
Returns:
int: Algorithm number
"""
def algorithm_to_text(algorithm):
"""
Convert algorithm number to name.
Args:
algorithm (int): Algorithm number
Returns:
str: Algorithm name
"""
def _is_rsa(algorithm):
"""Check if algorithm is RSA-based."""
def _is_dsa(algorithm):
"""Check if algorithm is DSA-based."""
def _is_ecdsa(algorithm):
"""Check if algorithm is ECDSA-based."""
def _is_eddsa(algorithm):
"""Check if algorithm is EdDSA-based."""Functions for working with NSEC and NSEC3 records for authenticated denial of existence.
def nsec_matches(name, nsec, origin=None):
"""
Check if a name matches an NSEC record's coverage.
Args:
name (dns.name.Name): Name to check
nsec (dns.rdata.NSEC): NSEC record
origin (dns.name.Name): Origin for relative names
Returns:
bool: True if name is covered by NSEC
"""
def nsec3_hash(domain, salt, iterations, algorithm):
"""
Calculate NSEC3 hash for a domain name.
Args:
domain (dns.name.Name): Domain name to hash
salt (bytes): Salt value
iterations (int): Number of iterations
algorithm (int): Hash algorithm (1=SHA-1)
Returns:
bytes: NSEC3 hash value
"""
def nsec3_matches(hash_value, nsec3, origin=None):
"""
Check if a hash value matches an NSEC3 record's coverage.
Args:
hash_value (bytes): Hash value to check
nsec3 (dns.rdata.NSEC3): NSEC3 record
origin (dns.name.Name): Origin for relative names
Returns:
bool: True if hash is covered by NSEC3
"""Create DNSSEC signatures (requires private keys and cryptographic libraries).
def sign(rrset, key, signer, inception=None, expiration=None, lifetime=None):
"""
Sign an RRset with a private key.
Args:
rrset (dns.rrset.RRset): RRset to sign
key (private key object): Private key for signing
signer (dns.name.Name): Signer name
inception (int): Signature inception time
expiration (int): Signature expiration time
lifetime (int): Signature lifetime (alternative to expiration)
Returns:
dns.rdata.RRSIG: Generated RRSIG record
Note:
Requires cryptographic library (cryptography package)
"""import dns.resolver
import dns.dnssec
import dns.rdatatype
# Query for a DNSSEC-signed domain
resolver = dns.resolver.Resolver()
resolver.use_edns(0, dns.flags.DO, 4096) # Enable DNSSEC
# Get the DNSKEY records
dnskey_answer = resolver.query('example.com', dns.rdatatype.DNSKEY)
dnskey_rrset = dnskey_answer.rrset
# Get A records and their signatures
a_answer = resolver.query('www.example.com', dns.rdatatype.A)
a_rrset = a_answer.rrset
# Try to get RRSIG records for the A records
try:
rrsig_answer = resolver.query('www.example.com', dns.rdatatype.RRSIG)
rrsig_rrset = rrsig_answer.rrset
# Create key dictionary
keys = {}
for key in dnskey_rrset:
key_id = dns.dnssec.key_id(key)
keys[(dnskey_rrset.name, key_id)] = key
# Validate the signature
try:
dns.dnssec.validate(a_rrset, rrsig_rrset, keys)
print("DNSSEC validation successful!")
except dns.dnssec.ValidationFailure as e:
print(f"DNSSEC validation failed: {e}")
except dns.resolver.NoAnswer:
print("No RRSIG records found")import dns.dnssec
import dns.name
import dns.rdata
import dns.rdatatype
import dns.rdataclass
# Create a DNSKEY record (example)
dnskey_text = '256 3 8 AwEAAcX...base64-encoded-key...'
dnskey = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY, dnskey_text)
# Create DS record with SHA-256
name = dns.name.from_text('example.com.')
ds_sha256 = dns.dnssec.make_ds(name, dnskey, dns.dnssec.SHA256)
print(f"DS record (SHA-256):")
print(f" Key tag: {ds_sha256.key_tag}")
print(f" Algorithm: {ds_sha256.algorithm}")
print(f" Digest type: {ds_sha256.digest_type}")
print(f" Digest: {ds_sha256.digest.hex().upper()}")
# Create DS record with SHA-1 (less secure, for compatibility)
ds_sha1 = dns.dnssec.make_ds(name, dnskey, dns.dnssec.SHA1)
print(f"\nDS record (SHA-1):")
print(f" Key tag: {ds_sha1.key_tag}")
print(f" Digest: {ds_sha1.digest.hex().upper()}")
# Calculate key ID
key_id = dns.dnssec.key_id(dnskey)
print(f"\nDNSKEY key tag: {key_id}")import dns.dnssec
# Work with algorithm names and numbers
algorithms = [
dns.dnssec.RSASHA256,
dns.dnssec.RSASHA512,
dns.dnssec.ECDSAP256SHA256,
dns.dnssec.ECDSAP384SHA384,
dns.dnssec.ED25519
]
print("Supported DNSSEC algorithms:")
for alg in algorithms:
name = dns.dnssec.algorithm_to_text(alg)
print(f" {alg}: {name}")
# Convert algorithm names back to numbers
alg_name = 'RSASHA256'
alg_num = dns.dnssec.algorithm_from_text(alg_name)
print(f"\n{alg_name} = {alg_num}")
# Check algorithm types
print(f"\nRSASHA256 is RSA: {dns.dnssec._is_rsa(dns.dnssec.RSASHA256)}")
print(f"ECDSAP256SHA256 is ECDSA: {dns.dnssec._is_ecdsa(dns.dnssec.ECDSAP256SHA256)}")
print(f"ED25519 is EdDSA: {dns.dnssec._is_eddsa(dns.dnssec.ED25519)}")import dns.dnssec
import dns.name
# Calculate NSEC3 hash
domain = dns.name.from_text('www.example.com.')
salt = b'\x12\x34\x56\x78'
iterations = 10
algorithm = 1 # SHA-1
hash_value = dns.dnssec.nsec3_hash(domain, salt, iterations, algorithm)
print(f"NSEC3 hash for {domain}: {hash_value.hex().upper()}")
# Base32 encode the hash (typical NSEC3 format)
import base64
b32_hash = base64.b32encode(hash_value).decode().rstrip('=').lower()
print(f"Base32 encoded: {b32_hash}")import dns.message
import dns.query
import dns.dnssec
import dns.rdatatype
import dns.name
def validate_dnssec_chain(domain, nameserver):
"""Validate DNSSEC chain for a domain."""
# Create query with DNSSEC enabled
query = dns.message.make_query(domain, dns.rdatatype.A, want_dnssec=True)
# Send query
response = dns.query.udp(query, nameserver)
if not response.answer:
print("No answer section in response")
return False
# Extract RRsets
answer_rrset = None
rrsig_rrsets = []
for rrset in response.answer:
if rrset.rdtype == dns.rdatatype.A:
answer_rrset = rrset
elif rrset.rdtype == dns.rdatatype.RRSIG:
rrsig_rrsets.append(rrset)
if not answer_rrset or not rrsig_rrsets:
print("Missing required records for validation")
return False
# Get DNSKEY records for validation
dnskey_query = dns.message.make_query(domain, dns.rdatatype.DNSKEY, want_dnssec=True)
dnskey_response = dns.query.udp(dnskey_query, nameserver)
# Build key dictionary
keys = {}
for rrset in dnskey_response.answer:
if rrset.rdtype == dns.rdatatype.DNSKEY:
for key in rrset:
key_id = dns.dnssec.key_id(key)
keys[(rrset.name, key_id)] = key
# Validate signatures
for rrsig_rrset in rrsig_rrsets:
try:
dns.dnssec.validate(answer_rrset, rrsig_rrset, keys)
print(f"Successfully validated {domain}")
return True
except dns.dnssec.ValidationFailure as e:
print(f"Validation failed: {e}")
continue
return False
# Example usage
domain = dns.name.from_text('example.com.')
nameserver = '8.8.8.8'
validate_dnssec_chain(domain, nameserver)# Key flags
SEP = 0x0001 # Secure Entry Point
REVOKE = 0x0080 # Revoke flag
ZONE = 0x0100 # Zone key flag
# NSEC3 flags
OPTOUT = 0x01 # Opt-out flag
# Default values
default_algorithm = RSASHA256class DNSSECError(DNSException):
"""Base class for DNSSEC errors."""
class ValidationFailure(DNSSECError):
"""DNSSEC validation failed."""
class UnsupportedAlgorithm(DNSSECError):
"""Unsupported DNSSEC algorithm."""
class InvalidSignature(ValidationFailure):
"""RRSIG signature is invalid."""
class ExpiredSignature(ValidationFailure):
"""RRSIG signature has expired."""
class NotYetValidSignature(ValidationFailure):
"""RRSIG signature is not yet valid."""
class NoMatchingKey(ValidationFailure):
"""No matching DNSKEY found for RRSIG."""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