1 #!/usr/bin/python3 2 3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 4 # 5 # SPDX-License-Identifier: MPL-2.0 6 7 from collections.abc import AsyncGenerator 8 9 import dns.flags 10 import dns.message 11 import dns.rcode 12 import dns.rdatatype 13 14 from dnssec_wildcard.ans1.common import ( 15 Key, 16 add_dnskey, 17 add_signed, 18 name, 19 rrset, 20 rrset_from_rdata, 21 soa_rrset, 22 wildcard_rrsig, 23 ) 24 from isctest.asyncserver import DnsResponseSend, DomainHandler, QueryContext 25 26 TTL = 300 27 FORGED_A = "198.51.100.45" 28 LEGIT_A = "192.0.2.113" 29 30 31 def add_ds(response: dns.message.Message, zone: str, child: Key, parent: Key) -> None: 32 add_signed(response.answer, rrset_from_rdata(zone, child.ds), parent) 33 34 35 def add_nodata(response: dns.message.Message, zone: str, key: Key) -> None: 36 add_signed(response.authority, soa_rrset(zone), key) 37 38 39 # A signed zone P, chained to a configured trust anchor, that 40 # (a) publishes a wildcard *.P A or AAAA record and 41 # (b) delegates at least one separately-signed child C.P (own DS in P) 42 # operated by a distinct principal. 43 F045_PARENT = "f045.test." 44 F045_CHILD = f"child.{F045_PARENT}" 45 F045_QUERY = f"q.{F045_PARENT}" 46 F045_SERVICE = f"svc.{F045_CHILD}" 47 48 49 def add_parent_mx_with_forged_additional( 50 response: dns.message.Message, parent: Key 51 ) -> None: 52 add_signed( 53 response.answer, 54 rrset(F045_QUERY, dns.rdatatype.MX, f"10 {F045_SERVICE}"), 55 parent, 56 ) 57 response.additional.append(rrset(F045_SERVICE, dns.rdatatype.A, FORGED_A)) 58 response.additional.append(wildcard_rrsig(F045_SERVICE, FORGED_A, parent)) 59 60 61 def add_child_a(response: dns.message.Message, child: Key) -> None: 62 add_signed(response.answer, rrset(F045_SERVICE, dns.rdatatype.A, LEGIT_A), child) 63 64 65 class F045Handler(DomainHandler): 66 domains = [F045_PARENT, F045_CHILD] 67 68 def __init__(self, keys: dict[str, Key]) -> None: 69 super().__init__() 70 self.keys = keys 71 72 if F045_PARENT not in keys or F045_CHILD not in keys: 73 return 74 75 self.parent = name(F045_PARENT) 76 self.child = name(F045_CHILD) 77 self.query = name(F045_QUERY) 78 self.service = name(F045_SERVICE) 79 80 async def get_responses( 81 self, qctx: QueryContext 82 ) -> AsyncGenerator[DnsResponseSend, None]: 83 qctx.prepare_new_response(with_zone_data=False) 84 qctx.response.flags |= dns.flags.AA 85 qctx.response.set_rcode(dns.rcode.NOERROR) 86 87 parent_key = self.keys[F045_PARENT] 88 child_key = self.keys[F045_CHILD] 89 90 if qctx.qname == self.parent and qctx.qtype == dns.rdatatype.DNSKEY: 91 # Priming, DNSKEY RRset 92 add_dnskey(qctx.response, F045_PARENT, parent_key) 93 elif qctx.qname == self.parent and qctx.qtype == dns.rdatatype.SOA: 94 # Priming, SOA RRset 95 add_signed(qctx.response.answer, soa_rrset(F045_PARENT), parent_key) 96 elif qctx.qname == self.query and qctx.qtype == dns.rdatatype.MX: 97 # Trigger query. 98 add_parent_mx_with_forged_additional(qctx.response, parent_key) 99 elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.DS: 100 # Chain of trust, DS of child. 101 add_ds(qctx.response, F045_CHILD, child_key, parent_key) 102 elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.DNSKEY: 103 # Chain of trust, DNSKEY of child. 104 add_dnskey(qctx.response, F045_CHILD, child_key) 105 elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.SOA: 106 # SOA of child. 107 add_signed(qctx.response.answer, soa_rrset(F045_CHILD), child_key) 108 elif qctx.qname == self.service and qctx.qtype == dns.rdatatype.A: 109 # Zone data at child. 110 add_child_a(qctx.response, child_key) 111 elif qctx.qname.is_subdomain(self.child): 112 # No data at child. 113 add_nodata(qctx.response, F045_CHILD, child_key) 114 elif qctx.qname.is_subdomain(self.parent): 115 # No data at parent. 116 add_nodata(qctx.response, F045_PARENT, parent_key) 117 else: 118 qctx.response.set_rcode(dns.rcode.NXDOMAIN) 119 120 yield DnsResponseSend(qctx.response, authoritative=True) 121