DNS
DNS¶
DNSSEC¶
Can Only Check if a DNS Record has been changed.
Algorithms¶
Number | Mnemonics | DNSSEC Signing | DNSSEC Validation |
---|---|---|---|
1 | RSAMD5 | MUST NOT | MUST NOT |
3 | DSA | MUST NOT | MUST NOT |
5 | RSASHA1 | NOT RECOMMENDED | MUST |
6 | DSA-NSEC3-SHA1 | MUST NOT | MUST NOT |
7 | RSASHA1-NSEC3-SHA1 | NOT RECOMMENDED | MUST |
8 | RSASHA256 | MUST | MUST |
10 | RSASHA512 | NOT RECOMMENDED | MUST |
12 | ECC-GOST | MUST NOT | MAY |
13 | ECDSAP256SHA256 | MUST | MUST |
14 | ECDSAP384SHA384 | MAY | RECOMMENDED |
15 | ED25519 | RECOMMENDED | RECOMMENDED |
16 | ED448 | MAY | RECOMMENDED |
Keys¶
Get Top Level DNS Key:
>>> dig @ganz.ns.cloudflare.com. generalzero.org DNSKEY +dnssec
; <<>> DiG 9.20.4 <<>> @ganz.ns.cloudflare.com. generalzero.org DNSKEY +dnssec
; (6 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57095
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;generalzero.org. IN DNSKEY
;; ANSWER SECTION:
generalzero.org. 3600 IN DNSKEY 257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+ KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==
generalzero.org. 3600 IN DNSKEY 256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIjCWzJaOau8XNEZeqCYKD5ar0IRd8 KqXXFJkqmVfRvMGPmM1x8fGAa2XhSA==
generalzero.org. 3600 IN RRSIG DNSKEY 13 2 3600 20250305013940 20250103013940 2371 generalzero.org. reY4OL9yVqdZQYpbG6+n+Kb7kD5wpYPy4nxznuIErhp9uqZ8IpM+8YbG OY8dkk89dZlPBnQjC8+uAqHmxK6pHA==
;; Query time: 3 msec
;; SERVER: 2606:4700:58::a29f:2c28#53(ganz.ns.cloudflare.com.) (UDP)
;; WHEN: Tue Jan 07 22:21:00 EST 2025
;; MSG SIZE rcvd: 315
The DNSKEY Response contains 2 DNSKEY Keys. These are differentiated by the flags.
Key Signing Key(Flag 257): The Key that is used to sign the Zone Signing Key with the signature information in the RRSIG info.
Zone Signing Key(Flag 256): The Key that is used to sign subdomains. RRSIG data
DNSSec Record:
>>> dig www.generalzero.org +dnssec
; <<>> DiG 9.20.4 <<>> www.generalzero.org +dnssec
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59755
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 65494
;; QUESTION SECTION:
;www.generalzero.org. IN A
;; ANSWER SECTION:
www.generalzero.org. 253 IN A 172.67.179.53
www.generalzero.org. 253 IN A 104.21.91.200
www.generalzero.org. 253 IN RRSIG A 13 3 300 20250107222441 20250105202441 34505 generalzero.org. bCLHO171apwxiIvCI0zZYgHWBX6CmtpdvsKDykystJM+2IEXmQPsocv7 SUYyErUAKLf7VwKaSufHy+fdgMkO1Q==
;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Mon Jan 06 16:25:27 EST 2025
;; MSG SIZE rcvd: 191
Implementation¶
DNSSec Manual Verify:
import dns.dnssec
import dns.resolver
import dns.query
import dns.message
import base64, sys
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.exceptions import InvalidSignature
from dns.dnssecalgs import ( # pylint: disable=C0412
get_algorithm_cls_from_dnskey,
)
def parse_dnskey_to_public_key(dnskey):
"""Convert a DNSKEY record to a public key object, supporting various algorithms."""
#print(f"DNSKey: {dnskey.flags} {dnskey.protocol} {dnskey.algorithm} {dnskey.key}")
#flags, protocol, algorithm, key = [dnskey.flags, dnskey.protocol, dnskey.algorithm, dnskey.key]
return get_algorithm_cls_from_dnskey(dnskey)
def validate_domain_key(domain, ns_address="1.1.1.1", timeout=20):
# Get DNSKey from Name Servers
request = dns.message.make_query(domain, dns.rdatatype.DNSKEY, want_dnssec=True)
try:
response = dns.query.udp(request, ns_address, timeout=timeout)
#Check If valid Response
if response.rcode() != 0:
print("ERROR: no DNSKEY record found or SERVEFAIL")
return
#Check If Key exists
answer = response.answer
if len(answer) != 2:
print("ERROR: could not find RRSET record (DNSKEY and RR DNSKEY) in zone")
return
# check if is the DNSKEY record signed, RRSET validation
name = dns.name.from_text(domain)
#Get DNSKeys
public_key = None
signing_key = None
for dnskey in answer[0]:
if dnskey.flags == 256:
#Zone Signing Key
public_key = parse_dnskey_to_public_key(dnskey).public_cls.from_dnskey(dnskey)
elif dnskey.flags == 257:
#Key Signing Key
signing_key = parse_dnskey_to_public_key(dnskey).public_cls.from_dnskey(dnskey)
#_validate_rrsig
for rrsig in answer[1]:
print(f"Signature: {rrsig.signature.hex()}")
#dns.dnssec._validate_rrsig(answer[0], rrsig, {name: answer[0]})
data = dns.dnssec._make_rrsig_signature_data(answer[0], rrsig, domain)
print(f"SignData: {data.hex()}")
print(f"Public Key: {signing_key.key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo)}")
try:
signing_key.verify(rrsig.signature, data)
print(f"Verify: True")
break
except InvalidSignature as e:
print("Verify: False")
except Exception as e:
print(e)
return public_key
def validate_dnssec_resp(domain, record_type, public_key, ns_address="1.1.1.1", timeout=20):
"""Validate DNSSEC for the given domain and record type."""
# Fetch the DNSKEY record from the nameserver
try:
request = dns.message.make_query(domain, record_type, want_dnssec=True)
response = dns.query.udp(request, ns_address, timeout=timeout)
if response.rcode() != 0:
print(f"ERROR: {record_type} request failed for {domain}")
return
if len(response.answer) < 2:
print(f"ERROR: Incomplete DNSSEC records for {domain}")
return
dnskeys = response.answer[0]
rrsigs = response.answer[1]
# Validate DNSKEY RRSIG
for rrsig in rrsigs:
data = dns.dnssec._make_rrsig_signature_data(dnskeys, rrsig, domain)
print(f"Signature: {rrsig.signature.hex()}")
print(f"SignData: {data.hex()}")
print(f"Public Key: {public_key.key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo)}")
try:
public_key.verify(rrsig.signature, data)
print(f"DNSKEY signature verified for {domain}")
except InvalidSignature:
print(f"DNSKEY signature verification failed for {domain}")
return
except Exception as e:
print(f"Error validating DNSSEC for {domain}: {e}")
# Update __main__ to include subdomain validation
if __name__ == "__main__":
domain = sys.argv[1]
record_type = sys.argv[2]
# Strip Subdomain
top_level_domain = '.'.join(domain.split('.')[-2:]) + "."
ns_server = None
#Insure Remote Resolver
resolver = dns.resolver.Resolver(configure=False)
resolver.timeout = 20
resolver.nameservers = ['1.1.1.1', '9.9.9.9', '8.8.8.8']
#Get NS Server
try:
response = resolver.resolve(top_level_domain, rdtype=dns.rdatatype.NS)
ns_server = response.rrset[0]
response = resolver.resolve(str(ns_server), rdtype=dns.rdatatype.A)
ns_address = response.rrset[0].to_text()
print(f"Subdomain NS Server: {ns_server}, Address: {ns_address}")
except Exception as e:
print(f"Error resolving nameserver for {domain}: {e}")
# Validate Top Domain Key
print(f"Validate {top_level_domain} Keys and signature\n")
public_key = validate_domain_key(top_level_domain)
# Validate Main Domain Query
print("\n")
print(f"Validate {domain} Signature")
validate_dnssec_resp(domain, record_type, public_key)
# >>> python dnssec.py mail.generalzero.org A
# Subdomain NS Server: ganz.ns.cloudflare.com., Address: 172.64.35.40
# Validate generalzero.org. Keys and signature
# Signature: ade63838bf7256a759418a5b1bafa7f8a6fb903e70a583f2e27c739ee204ae1a7dbaa67c22933ef186c6398f1d924f3d75994f0674230bcfae02a1e6c4aea91c
# SignData: 00300d0200000e1067c7ab5c67773fdc09430b67656e6572616c7a65726f036f7267000b67656e6572616c7a65726f036f7267000030000100000e1000440100030da09311112cf9138818cd2feae970ebbd4d6a30f6088c25b325a39abbc5cd1197aa098283e5aaf421177c2aa5d714992a9957d1bcc18f98cd71f1f1806b65e1480b67656e6572616c7a65726f036f7267000030000100000e1000440101030d99db2cc14cabdc33d6d77da63a2f15f71112584f234e8d1dc428e39e8a4a97e1aa271a555dc90701e17e2a4c4b6f120b7c32d44f4ac02bd894cf2d4be7778a19
# Public Key: b'-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmdsswUyr3DPW132mOi8V9xESWE8j\nTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==\n-----END PUBLIC KEY-----\n'
# Verify: True
# Validate mail.generalzero.org Signature
# Signature: be9810e6620f9c863d16b7dc026c44362bdcd893011086ade06d8240f30b85f1fe984e12d931651f3592e65c39f9d2a34d06a37bfefb760904ffc0117f4912ed
# SignData: 00010d030000012c677f4d57677c8e3786c90b67656e6572616c7a65726f036f726700046d61696c0b67656e6572616c7a65726f036f726700000100010000012c000464231f4e
# Public Key: b'-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoJMRESz5E4gYzS/q6XDrvU1qMPYI\njCWzJaOau8XNEZeqCYKD5ar0IRd8KqXXFJkqmVfRvMGPmM1x8fGAa2XhSA==\n-----END PUBLIC KEY-----\n'
# DNSKEY signature verified for mail.generalzero.org