From 2de202a6b7a1faea8f7dc232e9f8c24faf7d2bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Tue, 19 May 2026 15:38:28 +0200 Subject: [PATCH 1/2] Stop treating SIG and NXT records specially RFC 3755 retired SIG and NXT in favour of RRSIG and NSEC. BIND still warned about them at zone load, refused them in dynamic updates, parsed SIG with a non-zero "type covered" field as a signature on an RRset, and tracked them via dns_rdatatype_issig(). Those carve-outs were the sole path that made the GL#5818 crash class reachable. Treat both types as ordinary unknown rdata: they load, transfer, sign and answer like any other record, and dynamic updates carry them through the generic path. SIG(0) is unaffected; its message-parsing carve-out is preserved. --- bin/tests/system/nsupdate/ans11/ans.py | 34 +++--- bin/tests/system/nsupdate/tests_update_sig.py | 101 +++++++----------- lib/dns/include/dns/rdata.h | 9 +- lib/dns/master.c | 11 -- lib/dns/message.c | 7 +- lib/dns/nsec.c | 4 +- lib/dns/nsec3.c | 1 - lib/dns/qpzone.c | 1 - lib/dns/resolver.c | 12 +-- lib/ns/update.c | 6 -- 10 files changed, 67 insertions(+), 119 deletions(-) diff --git a/bin/tests/system/nsupdate/ans11/ans.py b/bin/tests/system/nsupdate/ans11/ans.py index 177a889e3c..918ff26a3b 100644 --- a/bin/tests/system/nsupdate/ans11/ans.py +++ b/bin/tests/system/nsupdate/ans11/ans.py @@ -10,14 +10,13 @@ # information regarding copyright ownership. """ -GL#5818 Finding 1 regression support — AsyncDnsServer primary. +AsyncDnsServer primary serving "sigaxfr.nil.". -Serves a minimal zone "sigaxfr.nil." whose AXFR carries two SIG records -at the same owner with different covered types (A and MX) and different -TTLs (600 and 1200). A buggy secondary running dns_diff_load() with -rdata_covers() that only recognises RRSIG will file both rdatas under -typepair (SIG, 0) with the first tuple's TTL; a fixed secondary keeps -them under (SIG, A) and (SIG, MX) with their distinct TTLs. +The AXFR carries two SIG (type 24) rdatas at the same owner with +different "covered type" body fields (A and MX) so the secondary can +verify it stores both under a single opaque rdataset. Per RFC 3755 +SIG has no covered-type semantics on BIND any more; the two rdatas +share the rdataset TTL. """ from collections.abc import AsyncGenerator @@ -75,7 +74,7 @@ class SigAxfrServer(DomainHandler): return # AXFR: opening SOA, NS, NS's A, two SIG RRs at the same owner - # with distinct covered types and TTLs, closing SOA. + # with distinct "covered type" body fields, closing SOA. resp = qctx.response resp.answer.append(soa_rrset) @@ -89,17 +88,16 @@ class SigAxfrServer(DomainHandler): ) resp.answer.append(a_rrset) - sig_a = _make_sig_rdata("A 6 2 600 20260331170000 20260318160000 21831 . 0000") - sig_a_rrset = dns.rrset.RRset(HOST, dns.rdataclass.IN, dns.rdatatype.SIG) - sig_a_rrset.add(sig_a, ttl=600) - resp.answer.append(sig_a_rrset) - - sig_mx = _make_sig_rdata( - "MX 6 2 1200 20260331170000 20260318160000 21831 . 0000" + sig_rrset = dns.rrset.RRset(HOST, dns.rdataclass.IN, dns.rdatatype.SIG) + sig_rrset.add( + _make_sig_rdata("A 6 2 600 20260331170000 20260318160000 21831 . 0000"), + ttl=600, ) - sig_mx_rrset = dns.rrset.RRset(HOST, dns.rdataclass.IN, dns.rdatatype.SIG) - sig_mx_rrset.add(sig_mx, ttl=1200) - resp.answer.append(sig_mx_rrset) + sig_rrset.add( + _make_sig_rdata("MX 6 2 600 20260331170000 20260318160000 21831 . 0000"), + ttl=600, + ) + resp.answer.append(sig_rrset) # Closing SOA terminates the AXFR. resp.answer.append(soa_rrset) diff --git a/bin/tests/system/nsupdate/tests_update_sig.py b/bin/tests/system/nsupdate/tests_update_sig.py index 4f33ceb90f..ac3e0ea19f 100644 --- a/bin/tests/system/nsupdate/tests_update_sig.py +++ b/bin/tests/system/nsupdate/tests_update_sig.py @@ -9,8 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -""" -Regression tests for GL#5818: legacy DNSSEC types on the dynamic-update path. +"""Regression tests for GL#5818: legacy DNSSEC types on the dynamic-update path. SIG (24) and NXT (30) are obsolete DNSSEC record types, superseded by RRSIG and NSEC in RFC 3755. Allowing a client to inject them via dynamic update @@ -24,11 +23,9 @@ exposed two bugs in sequence: different TTL then tripped DNS_DBADD_EXACTTTL in qpzone and came back as SERVFAIL. -The adopted defence is to outright refuse SIG and NXT updates at the front -door (ns/update.c), keeping KEY updates permitted for SIG(0) transaction -signatures. These tests verify the refusal. The reachability of the -diff.c:rdata_covers() bug via inbound zone transfer is covered separately -by the AXFR-based regression test in this file. +The adopted defence is to treat the legacy SIG and NXT records as normal RR +records without any special processing. + """ from re import compile as Re @@ -92,18 +89,17 @@ def _make_nxt_rdata(): def test_tcp_self_sig_record(ns6): - """SIG (type 24) updates must be refused at the front door. + """SIG (type 24) updates are accepted and stored as opaque rdata. - Prior to the fix in dns__db_findrdataset(), a SIG update here - crashed named. Prior to the fix in diff.c rdata_covers(), the - record was silently misfiled under typepair (SIG, 0). The - adopted policy outright refuses SIG (obsolete; use RRSIG) so the - buggy dynamic-update paths are no longer reachable. A PTR add - first ensures the node exists, which is the original - crash-reproducing precondition. + Per RFC 3755 SIG is obsolete (superseded by RRSIG). BIND treats + incoming SIG records as a generic unknown type with no covered-type + semantics: dynamic updates carrying SIG are accepted and the record + becomes queryable. A PTR add first ensures the node exists. """ + owner = "1.0.53.10.in-addr.arpa." + ptr_update = dns.update.UpdateMessage("in-addr.arpa.") - ptr_update.add("1.0.53.10.in-addr.arpa.", 600, "PTR", "localhost.") + ptr_update.add(owner, 600, "PTR", "localhost.") response = isctest.query.tcp( ptr_update, ns6.ip, port=ns6.ports.dns, source="10.53.0.1" ) @@ -114,34 +110,27 @@ def test_tcp_self_sig_record(ns6): rds.update_ttl(600) rds.add(sig) sig_update = dns.update.UpdateMessage("in-addr.arpa.") - sig_update.add("1.0.0.127.in-addr.arpa.", rds) + sig_update.add(owner, rds) - with ns6.watch_log_from_here() as watcher: - response = isctest.query.tcp( - sig_update, ns6.ip, port=ns6.ports.dns, source="10.53.0.1" - ) - assert response.rcode() == dns.rcode.REFUSED + response = isctest.query.tcp( + sig_update, ns6.ip, port=ns6.ports.dns, source="10.53.0.1" + ) + assert response.rcode() == dns.rcode.NOERROR - watcher.wait_for_line( - "updating zone 'in-addr.arpa/IN': update failed: SIG updates are not allowed (REFUSED)" - ) - - # Confirm nothing of type SIG was stored. - msg = isctest.query.create("1.0.53.10.in-addr.arpa.", "SIG") + # Confirm the SIG record was stored. + msg = isctest.query.create(owner, "SIG") res = isctest.query.tcp(msg, ns6.ip, port=ns6.ports.dns) stored = any(rrset.rdtype == dns.rdatatype.SIG for rrset in res.answer) - assert not stored, "SIG record was stored despite REFUSED response" + assert stored, "SIG record was not stored despite NOERROR response" def test_tcp_self_nxt_record(ns6): - """NXT (type 30) updates must be refused at the front door. + """NXT (type 30) updates are accepted and stored as opaque rdata. NXT is the legacy DNSSEC denial-of-existence type, obsolete since - RFC 3755 replaced it with NSEC. Accepting it via dynamic update - would let an authorised updater inject records that the signing - and cut-point logic has no provision for. + RFC 3755 replaced it with NSEC. BIND treats it as a generic + unknown rdata type. """ - # A second owner under a source that also matches tcp-self. source = "10.53.0.2" owner = "2.0.53.10.in-addr.arpa." @@ -157,28 +146,23 @@ def test_tcp_self_nxt_record(ns6): nxt_update = dns.update.UpdateMessage("in-addr.arpa.") nxt_update.add(owner, rds) - with ns6.watch_log_from_here() as watcher: - response = isctest.query.tcp( - nxt_update, ns6.ip, port=ns6.ports.dns, source="10.53.0.1" - ) - assert response.rcode() == dns.rcode.REFUSED + response = isctest.query.tcp(nxt_update, ns6.ip, port=ns6.ports.dns, source=source) + assert response.rcode() == dns.rcode.NOERROR - watcher.wait_for_line( - "updating zone 'in-addr.arpa/IN': update failed: NXT updates are not allowed (REFUSED)" - ) + # Confirm the NXT record was stored. + msg = isctest.query.create(owner, "NXT") + res = isctest.query.tcp(msg, ns6.ip, port=ns6.ports.dns) + stored = any(rrset.rdtype == dns.rdatatype.NXT for rrset in res.answer) + assert stored, "NXT record was not stored despite NOERROR response" -def test_sig_covers_preserved_via_axfr(ns6): - """Regression test for GL#5818 Finding 1, reached via AXFR. +def test_sig_axfr_stored_opaque(ns6): + """SIG records received via AXFR are stored as opaque rdata. ans11 serves an AXFR for sigaxfr.nil. containing two SIG rdatas at - the same owner with different covered types (A, MX) and different - TTLs (600, 1200). ns6 pulls the zone via dns_diff_load(), which - calls diff.c rdata_covers(); before the fix that helper returned 0 - for SIG, so both tuples were grouped and filed under typepair - (SIG, 0) with the first TTL (600) — the MX-covering record's TTL - (1200) was silently dropped. With the fix the records land in - distinct typepairs and both TTLs survive. + the same owner with different "covered type" body fields (A, MX). + Per RFC 3755 SIG has no covered-type semantics; both rdatas land in + a single opaque rdataset and both survive in the zone DB. rndc dumpdb is used to inspect the secondary's stored state directly; the wire-level response can merge same-(owner,type,class) @@ -211,12 +195,11 @@ def test_sig_covers_preserved_via_axfr(ns6): else: raise AssertionError(f"{dump_path} never contained {deadline_marker!r}") - # Collect every SIG line for the owner from the dump. Format is: - # . IN SIG ... + # Collect every SIG line for the owner from the dump. sig_lines = [] for line in text.splitlines(): fields = line.split() - if len(fields) < 6: + if len(fields) < 4: continue if not fields[0].lower().startswith("host.sigaxfr.nil"): continue @@ -226,14 +209,10 @@ def test_sig_covers_preserved_via_axfr(ns6): assert ( len(sig_lines) == 2 - ), f"expected 2 SIG records at {owner}, got {len(sig_lines)}: {sig_lines}" + ), f"expected 2 SIG rdatas at {owner}, got {len(sig_lines)}: {sig_lines}" - ttl_by_covers = {fields[4]: int(fields[1]) for fields in sig_lines} - assert ttl_by_covers == {"A": 600, "MX": 1200}, ( - f"SIG records lost their covers/TTL binding: {ttl_by_covers}. With " - "the Finding 1 bug both records are filed under typepair (SIG, 0) " - "and share the first-seen TTL (600)." - ) + ttls = {int(fields[1]) for fields in sig_lines} + assert ttls == {600}, f"SIG rdataset should share a single TTL, got {ttls}" def parse_named_conf_keys(conf_text): diff --git a/lib/dns/include/dns/rdata.h b/lib/dns/include/dns/rdata.h index f31dce0fe2..c7bdf5eaff 100644 --- a/lib/dns/include/dns/rdata.h +++ b/lib/dns/include/dns/rdata.h @@ -720,20 +720,19 @@ dns_rdatatype_isknown(dns_rdatatype_t type) { /*% * Return true iff a query for the rdata type can have multiple - * unrelated answers in a response: ANY, RRSIG, or SIG. + * unrelated answers in a response: ANY, or RRSIG. */ static inline bool dns_rdatatype_ismulti(dns_rdatatype_t type) { - return type == dns_rdatatype_any || type == dns_rdatatype_rrsig || - type == dns_rdatatype_sig; + return type == dns_rdatatype_any || type == dns_rdatatype_rrsig; } /*% - * Return true iff the rdata type is a signature: either RRSIG or SIG. + * Return true iff the rdata type is RRSIG. */ static inline bool dns_rdatatype_issig(dns_rdatatype_t type) { - return type == dns_rdatatype_rrsig || type == dns_rdatatype_sig; + return type == dns_rdatatype_rrsig; } /*% diff --git a/lib/dns/master.c b/lib/dns/master.c index 59841abf67..741b18f981 100644 --- a/lib/dns/master.c +++ b/lib/dns/master.c @@ -494,7 +494,6 @@ loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, unsigned int options, .ttl_known = ((options & DNS_MASTER_NOTTL) != 0), .default_ttl_known = ((options & DNS_MASTER_NOTTL) != 0), .warn_1035 = true, - .warn_tcr = true, .warn_sigexpired = true, .options = options, .zclass = zclass, @@ -1947,16 +1946,6 @@ load_text(dns_loadctx_t *lctx) { } } - if ((type == dns_rdatatype_sig || type == dns_rdatatype_nxt) && - lctx->warn_tcr && dns_master_isprimary(lctx)) - { - (*callbacks->warn)(callbacks, - "%s:%lu: old style DNSSEC " - " zone detected", - source, line); - lctx->warn_tcr = false; - } - if ((lctx->options & DNS_MASTER_AGETTL) != 0) { /* * Adjust the TTL for $DATE. If the RR has diff --git a/lib/dns/message.c b/lib/dns/message.c index cfc489f98d..9b5360ea3f 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -1285,12 +1285,7 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx, issigzero = true; } } else { - if (msg->rdclass != dns_rdataclass_any && - msg->rdclass != rdclass) - { - /* XXX test coverage */ - DO_ERROR(DNS_R_FORMERR); - } + covers = dns_rdatatype_none; } } else { covers = dns_rdatatype_none; diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c index d444374e8c..a551d7d026 100644 --- a/lib/dns/nsec.c +++ b/lib/dns/nsec.c @@ -380,8 +380,8 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring child nsec"); return ISC_R_IGNORE; } - if (type == dns_rdatatype_cname || type == dns_rdatatype_nxt || - type == dns_rdatatype_nsec || type == dns_rdatatype_key || + if (type == dns_rdatatype_cname || type == dns_rdatatype_nsec || + type == dns_rdatatype_key || !dns_nsec_typepresent(&rdata, dns_rdatatype_cname)) { *exists = true; diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c index c4954c311f..5713f21bfb 100644 --- a/lib/dns/nsec3.c +++ b/lib/dns/nsec3.c @@ -1940,7 +1940,6 @@ dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, return ISC_R_IGNORE; } if (type == dns_rdatatype_cname || - type == dns_rdatatype_nxt || type == dns_rdatatype_nsec || type == dns_rdatatype_key || !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname)) diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c index 543d56688d..ced8ce3cf7 100644 --- a/lib/dns/qpzone.c +++ b/lib/dns/qpzone.c @@ -1829,7 +1829,6 @@ cname_and_other(qpznode_t *node, uint32_t serial) { cname = true; } } else if (rdtype != dns_rdatatype_key && - rdtype != dns_rdatatype_sig && rdtype != dns_rdatatype_nsec && rdtype != dns_rdatatype_rrsig) { diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index db8c663f11..c535870504 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -936,7 +936,7 @@ typedef struct respctx { * fctx_query() when resending */ dns_rdatatype_t type; /* type being sought (set to - * ANY if qtype was SIG or RRSIG) */ + * ANY if qtype was RRSIG) */ bool aa; /* authoritative answer? */ dns_trust_t trust; /* answer trust level */ bool chaining; /* CNAME/DNAME processing? */ @@ -4574,7 +4574,6 @@ resume_qmin(void *arg) { fctx->type != dns_rdatatype_key && fctx->type != dns_rdatatype_nsec && fctx->type != dns_rdatatype_any && - fctx->type != dns_rdatatype_sig && fctx->type != dns_rdatatype_rrsig) { pull_from_resp(resp, fctx); @@ -5577,12 +5576,10 @@ evict_cname_other(fetchctx_t *fctx, dns_name_t *name) { dns_typepair_t typepair = DNS_TYPEPAIR_VALUE(rdataset.type, rdataset.covers); switch (typepair) { - /* KEY, NSEC and NXT records are allowed */ + /* KEY and NSEC records are allowed */ case DNS_TYPEPAIR(dns_rdatatype_nsec): - case DNS_TYPEPAIR(dns_rdatatype_nxt): case DNS_TYPEPAIR(dns_rdatatype_key): case DNS_SIGTYPEPAIR(dns_rdatatype_nsec): - case DNS_SIGTYPEPAIR(dns_rdatatype_nxt): case DNS_SIGTYPEPAIR(dns_rdatatype_key): /* Keep the CNAME and its signature */ case DNS_TYPEPAIR(dns_rdatatype_cname): @@ -5664,8 +5661,7 @@ cache_rrset(fetchctx_t *fctx, isc_stdtime_t now, dns_name_t *name, */ if (!dns_rdataset_matchestype(rdataset, dns_rdatatype_cname) && !dns_rdataset_matchestype(rdataset, dns_rdatatype_key) && - !dns_rdataset_matchestype(rdataset, dns_rdatatype_nsec) && - !dns_rdataset_matchestype(rdataset, dns_rdatatype_nxt)) + !dns_rdataset_matchestype(rdataset, dns_rdatatype_nsec)) { delete_rrset(fctx, name, dns_rdatatype_cname); } @@ -8132,7 +8128,7 @@ rctx_answer_init(respctx_t *rctx) { } /* - * There can be multiple RRSIG and SIG records at a name so + * There can be multiple RRSIG records at a name so * we treat these types as a subset of ANY. */ rctx->type = fctx->type; diff --git a/lib/ns/update.c b/lib/ns/update.c index b5ea582879..735d81de9b 100644 --- a/lib/ns/update.c +++ b/lib/ns/update.c @@ -1687,12 +1687,6 @@ send_update(ns_client_t *client, dns_zone_t *zone) { } else if (rdata.type == dns_rdatatype_nsec) { FAILC(DNS_R_REFUSED, "explicit NSEC updates are not " "allowed in secure zones"); - } else if (rdata.type == dns_rdatatype_sig) { - FAILC(DNS_R_REFUSED, "SIG updates are not " - "allowed"); - } else if (rdata.type == dns_rdatatype_nxt) { - FAILC(DNS_R_REFUSED, "NXT updates are not " - "allowed"); } else if (rdata.type == dns_rdatatype_rrsig && !dns_name_equal(name, zonename)) { From b9c1b90b500167d80c0ddc6b997ad595b4c5e7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Tue, 19 May 2026 15:58:54 +0200 Subject: [PATCH 2/2] Drop RFC 2535 special-casing of the KEY record type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After SIG and NXT lost their special handling, KEY remained the only RFC 2535-era type still receiving coexistence allowances: KEY alongside CNAME at the same owner, KEY answered from the parent side of a zone cut, KEY kept across CNAME eviction in the cache. RFC 3755 retains type 25 only for SIG(0) and TKEY transaction signatures, and neither relies on those allowances in practice. The in-tree comment that flagged the RFC 3007 parent-side carve-out as "unclear" predicted this cleanup. Zones that publish CNAME and KEY at the same owner — already invalid under RFC 2181 — now fail to load. System test fixtures are updated accordingly, and a new test asserts that SIG, NXT, and KEY records pick up covering RRSIGs when their zone is signed. --- ...-cname-and-key.db => bad-cname-and-key.db} | 0 .../system/dnssec/ns3/secure.example.db.in | 6 ++- bin/tests/system/dnssec/ns3/sign.sh | 3 +- bin/tests/system/dnssec/tests_validation.py | 40 +++++++++++-------- lib/dns/nsec.c | 1 - lib/dns/nsec3.c | 1 - lib/dns/qpcache.c | 6 +-- lib/dns/qpzone.c | 29 ++++---------- lib/dns/rdata/generic/key_25.c | 3 +- lib/dns/resolver.c | 8 +--- tests/dns/rdata_test.c | 2 - 11 files changed, 42 insertions(+), 57 deletions(-) rename bin/tests/system/checkzone/zones/{good-cname-and-key.db => bad-cname-and-key.db} (100%) diff --git a/bin/tests/system/checkzone/zones/good-cname-and-key.db b/bin/tests/system/checkzone/zones/bad-cname-and-key.db similarity index 100% rename from bin/tests/system/checkzone/zones/good-cname-and-key.db rename to bin/tests/system/checkzone/zones/bad-cname-and-key.db diff --git a/bin/tests/system/dnssec/ns3/secure.example.db.in b/bin/tests/system/dnssec/ns3/secure.example.db.in index 76d25e981b..7c6a24d486 100644 --- a/bin/tests/system/dnssec/ns3/secure.example.db.in +++ b/bin/tests/system/dnssec/ns3/secure.example.db.in @@ -39,9 +39,13 @@ normalthenrrsig A 10.0.0.28 rrsigonly A 10.0.0.29 cnameandkey CNAME @ -cnamenokey CNAME @ dnameandkey DNAME @ +; Legacy DNSSEC types (RFC 3755) carried as opaque zone data and signed. +sigrr SIG A 6 2 86400 20260331170000 20260318160000 21831 . AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +nxtrr NXT next.secure.example. A +keyrr KEY 0 3 13 zJxo/L9JqctLZuL8CqocSmgHUhmQCrQQmRHjwhzXfjDgPoPjRo1nofU7yXHeli8myiulQLZk3h1CTayP8dOvkQ== + mixedcase A 10.0.0.30 mixedCASE TXT "mixed case" MIXEDcase AAAA 2002:: diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh index ea81381eb2..495d68e456 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -115,11 +115,10 @@ zone=secure.example. infile=secure.example.db.in zonefile=secure.example.db -cnameandkey=$("$KEYGEN" -T KEY -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "cnameandkey.$zone") dnameandkey=$("$KEYGEN" -T KEY -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "dnameandkey.$zone") keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") -cat "$infile" dsset-badalg.secure.example. "$cnameandkey.key" "$dnameandkey.key" "$keyname.key" >"$zonefile" +cat "$infile" dsset-badalg.secure.example. "$dnameandkey.key" "$keyname.key" >"$zonefile" "$SIGNER" -z -D -o "$zone" "$zonefile" >/dev/null cat "$zonefile" "$zonefile".signed >"$zonefile".tmp diff --git a/bin/tests/system/dnssec/tests_validation.py b/bin/tests/system/dnssec/tests_validation.py index ed920749cf..781324fed2 100644 --- a/bin/tests/system/dnssec/tests_validation.py +++ b/bin/tests/system/dnssec/tests_validation.py @@ -290,22 +290,6 @@ def test_chain_validation(): isctest.check.adflag(res2) assert answer_has(res2, rdatatype.CNAME) - # check KEY lookup via CNAME - msg = isctest.query.create("cnameandkey.secure.example", "KEY") - res1 = isctest.query.tcp(msg, "10.53.0.3") - res2 = isctest.query.tcp(msg, "10.53.0.4") - isctest.check.same_answer(res1, res2) - isctest.check.adflag(res2) - assert not answer_has(res2, rdatatype.CNAME) - - # check KEY lookup via CNAME (not present) - msg = isctest.query.create("cnamenokey.secure.example", "KEY") - res1 = isctest.query.tcp(msg, "10.53.0.3") - res2 = isctest.query.tcp(msg, "10.53.0.4") - isctest.check.same_answer(res1, res2) - isctest.check.adflag(res2) - assert not answer_has(res2, rdatatype.CNAME) - # check DNSKEY lookup via DNAME msg = isctest.query.create("a.dnameandkey.secure.example", "DNSKEY") res1 = isctest.query.tcp(msg, "10.53.0.3") @@ -1351,6 +1335,30 @@ def test_unknown_algorithms(): isctest.check.noadflag(res2) +def test_legacy_dnssec_types_are_signed(): + """SIG (24), NXT (30) and KEY (25) records carry a covering RRSIG. + + Per RFC 3755 SIG and NXT are obsolete and treated as opaque zone + data; KEY remains valid for SIG(0)/TKEY use. All three are + ordinary zone data and must be signed like any other RRset. + """ + for owner, rrtype in [ + ("sigrr.secure.example", "SIG"), + ("nxtrr.secure.example", "NXT"), + ("keyrr.secure.example", "KEY"), + ]: + expected = rdatatype.from_text(rrtype) + msg = isctest.query.create(owner, rrtype, cd=True) + res = isctest.query.tcp(msg, "10.53.0.3") + isctest.check.noerror(res) + assert any( + rr.rdtype == expected for rr in res.answer + ), f"{rrtype} record missing in answer for {owner}: {res.answer}" + assert any( + rr.rdtype == rdatatype.RRSIG and rr.covers == expected for rr in res.answer + ), f"RRSIG({rrtype}) missing in answer for {owner}: {res.answer}" + + def test_rrsigs_for_glue(): msg = isctest.query.create("ns3.secure.example", "A", cd=True) res = isctest.query.tcp(msg, "10.53.0.4") diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c index a551d7d026..fabe279d50 100644 --- a/lib/dns/nsec.c +++ b/lib/dns/nsec.c @@ -381,7 +381,6 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, return ISC_R_IGNORE; } if (type == dns_rdatatype_cname || type == dns_rdatatype_nsec || - type == dns_rdatatype_key || !dns_nsec_typepresent(&rdata, dns_rdatatype_cname)) { *exists = true; diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c index 5713f21bfb..64e49ad3f0 100644 --- a/lib/dns/nsec3.c +++ b/lib/dns/nsec3.c @@ -1941,7 +1941,6 @@ dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, } if (type == dns_rdatatype_cname || type == dns_rdatatype_nsec || - type == dns_rdatatype_key || !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname)) { *exists = true; diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c index 690cf75394..eb85d5092c 100644 --- a/lib/dns/qpcache.c +++ b/lib/dns/qpcache.c @@ -1578,11 +1578,9 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, /* * Certain DNSSEC types are not subject to CNAME matching - * (RFC4035, section 2.5 and RFC3007). + * (RFC4035, section 2.5). */ - if (type == dns_rdatatype_key || type == dns_rdatatype_nsec || - type == dns_rdatatype_rrsig) - { + if (type == dns_rdatatype_nsec || type == dns_rdatatype_rrsig) { cname_ok = false; } diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c index ced8ce3cf7..dce80a32ff 100644 --- a/lib/dns/qpzone.c +++ b/lib/dns/qpzone.c @@ -1828,8 +1828,7 @@ cname_and_other(qpznode_t *node, uint32_t serial) { if (first_existing_header(top, serial) != NULL) { cname = true; } - } else if (rdtype != dns_rdatatype_key && - rdtype != dns_rdatatype_nsec && + } else if (rdtype != dns_rdatatype_nsec && rdtype != dns_rdatatype_rrsig) { if (first_existing_header(top, serial) != NULL) { @@ -3638,12 +3637,12 @@ found: /* * Certain DNSSEC types are not subject to CNAME matching - * (RFC4035, section 2.5 and RFC3007). + * (RFC4035, section 2.5). * * We don't check for RRSIG, because we don't store RRSIG records * directly. */ - if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) { + if (type == dns_rdatatype_nsec) { cname_ok = false; } @@ -3684,15 +3683,9 @@ found: search.need_cleanup = true; maybe_zonecut = false; at_zonecut = true; - /* - * It is not clear if KEY should still be - * allowed at the parent side of the zone - * cut or not. It is needed for RFC3007 - * validated updates. - */ + if ((search.options & DNS_DBFIND_GLUEOK) == 0 && - type != dns_rdatatype_nsec && - type != dns_rdatatype_key) + type != dns_rdatatype_nsec) { /* * Glue is not OK, but any answer we @@ -3900,18 +3893,10 @@ found: /* * If we're beneath a zone cut, we must indicate that the * result is glue, unless we're actually at the zone cut - * and the type is NSEC or KEY. + * and the type is NSEC. */ if (search.zonecut == node) { - /* - * It is not clear if KEY should still be - * allowed at the parent side of the zone - * cut or not. It is needed for RFC3007 - * validated updates. - */ - if (dns_rdatatype_isnsec(type) || - type == dns_rdatatype_key) - { + if (dns_rdatatype_isnsec(type)) { result = ISC_R_SUCCESS; } else if (type == dns_rdatatype_any) { result = DNS_R_ZONECUT; diff --git a/lib/dns/rdata/generic/key_25.c b/lib/dns/rdata/generic/key_25.c index 2d375188a2..0dfc0cf7e3 100644 --- a/lib/dns/rdata/generic/key_25.c +++ b/lib/dns/rdata/generic/key_25.c @@ -18,8 +18,7 @@ #include -#define RRTYPE_KEY_ATTRIBUTES \ - (DNS_RDATATYPEATTR_ATCNAME | DNS_RDATATYPEATTR_ZONECUTAUTH) +#define RRTYPE_KEY_ATTRIBUTES (0) static isc_result_t generic_fromtext_key(ARGS_FROMTEXT) { diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index c535870504..5c59014eb5 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -4571,7 +4571,6 @@ resume_qmin(void *arg) { */ if ((result == DNS_R_CNAME || result == DNS_R_DNAME) && fctx->qmin_labels == dns_name_countlabels(fctx->name) && - fctx->type != dns_rdatatype_key && fctx->type != dns_rdatatype_nsec && fctx->type != dns_rdatatype_any && fctx->type != dns_rdatatype_rrsig) @@ -5576,11 +5575,9 @@ evict_cname_other(fetchctx_t *fctx, dns_name_t *name) { dns_typepair_t typepair = DNS_TYPEPAIR_VALUE(rdataset.type, rdataset.covers); switch (typepair) { - /* KEY and NSEC records are allowed */ + /* NSEC records are allowed */ case DNS_TYPEPAIR(dns_rdatatype_nsec): - case DNS_TYPEPAIR(dns_rdatatype_key): case DNS_SIGTYPEPAIR(dns_rdatatype_nsec): - case DNS_SIGTYPEPAIR(dns_rdatatype_key): /* Keep the CNAME and its signature */ case DNS_TYPEPAIR(dns_rdatatype_cname): case DNS_SIGTYPEPAIR(dns_rdatatype_cname): @@ -5660,7 +5657,6 @@ cache_rrset(fetchctx_t *fctx, isc_stdtime_t now, dns_name_t *name, * along with the covered RRset in 'delete_rrset()'. */ if (!dns_rdataset_matchestype(rdataset, dns_rdatatype_cname) && - !dns_rdataset_matchestype(rdataset, dns_rdatatype_key) && !dns_rdataset_matchestype(rdataset, dns_rdatatype_nsec)) { delete_rrset(fctx, name, dns_rdatatype_cname); @@ -8867,7 +8863,7 @@ rctx_answer_cname(respctx_t *rctx) { } if (rctx->type == dns_rdatatype_rrsig || - rctx->type == dns_rdatatype_key || rctx->type == dns_rdatatype_nsec) + rctx->type == dns_rdatatype_nsec) { char buf[DNS_RDATATYPE_FORMATSIZE]; dns_rdatatype_format(rctx->type, buf, sizeof(buf)); diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c index b065b3e555..940b54e207 100644 --- a/tests/dns/rdata_test.c +++ b/tests/dns/rdata_test.c @@ -3365,7 +3365,6 @@ ISC_RUN_TEST_IMPL(atcname) { bool tf = dns_rdatatype_atcname((dns_rdatatype_t)i); switch (i) { case dns_rdatatype_nsec: - case dns_rdatatype_key: case dns_rdatatype_rrsig: if (!tf) { print_message(UNR, i); @@ -3416,7 +3415,6 @@ ISC_RUN_TEST_IMPL(iszonecutauth) { case dns_rdatatype_ns: case dns_rdatatype_ds: case dns_rdatatype_nsec: - case dns_rdatatype_key: case dns_rdatatype_rrsig: if (!tf) { print_message(UNR, i);