1 #!/usr/bin/python3 2 3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 4 # 5 # SPDX-License-Identifier: MPL-2.0 6 7 from dataclasses import dataclass 8 from pathlib import Path 9 10 import json 11 12 from cryptography.hazmat.primitives import serialization 13 14 import dns.dnssec 15 import dns.message 16 import dns.name 17 import dns.rdata 18 import dns.rdataclass 19 import dns.rdatatype 20 import dns.rrset 21 22 TTL = 300 23 24 25 @dataclass(frozen=True) 26 class Key: 27 zone: dns.name.Name 28 private_key: object 29 dnskey: dns.rdata.Rdata 30 ds: dns.rdata.Rdata 31 32 33 def name(text: str) -> dns.name.Name: 34 return dns.name.from_text(text) 35 36 37 def load_keys() -> dict[str, Key]: 38 path = Path(".") / "keys.json" 39 with path.open(encoding="utf-8") as keys_file: 40 raw_keys = json.load(keys_file) 41 42 keys = {} 43 for zone, raw_key in raw_keys.items(): 44 private_key = serialization.load_pem_private_key( 45 raw_key["private_pem"].encode("ascii"), 46 password=None, 47 ) 48 dnskey = dns.rdata.from_text( 49 dns.rdataclass.IN, dns.rdatatype.DNSKEY, raw_key["dnskey"] 50 ) 51 ds = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS, raw_key["ds"]) 52 keys[zone] = Key(name(zone), private_key, dnskey, ds) 53 54 return keys 55 56 57 def rrset(owner: str, rdtype: dns.rdatatype.RdataType, *rdatas: str) -> dns.rrset.RRset: 58 return dns.rrset.from_text(owner, TTL, dns.rdataclass.IN, rdtype, *rdatas) 59 60 61 def rrset_from_rdata(owner: str, rdata: dns.rdata.Rdata) -> dns.rrset.RRset: 62 return dns.rrset.from_rdata(owner, TTL, rdata) 63 64 65 def add_signed( 66 section: list[dns.rrset.RRset], covered: dns.rrset.RRset, signer: Key 67 ) -> None: 68 rrsig = dns.dnssec.sign( 69 covered, 70 signer.private_key, 71 signer.zone, 72 signer.dnskey, 73 lifetime=86400, 74 verify=True, 75 ) 76 section.append(covered) 77 section.append(dns.rrset.from_rdata(covered.name, covered.ttl, rrsig)) 78 79 80 def soa_rrset(zone) -> dns.rrset.RRset: 81 return rrset( 82 zone, 83 dns.rdatatype.SOA, 84 f"ns.{zone} hostmaster.{zone} 1 3600 600 86400 300", 85 ) 86 87 88 def add_dnskey(response: dns.message.Message, zone: str, key: Key) -> None: 89 add_signed(response.answer, rrset_from_rdata(zone, key.dnskey), key) 90 91 92 def wildcard_rrsig(owner: str, a: str, key: Key) -> dns.rrset.RRset: 93 wildcard = rrset(key.zone, dns.rdatatype.A, a) 94 rrsig = dns.dnssec.sign( 95 wildcard, 96 key.private_key, 97 key.zone, 98 key.dnskey, 99 lifetime=86400, 100 verify=True, 101 ) 102 return dns.rrset.from_rdata(name(owner), wildcard.ttl, rrsig) 103