From: Ondřej Surý Date: Tue, 19 May 2026 13:58:54 +0000 (+0200) Subject: Drop RFC 2535 special-casing of the KEY record type X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b9c1b90b500167d80c0ddc6b997ad595b4c5e7ed;p=thirdparty%2Fbind9.git Drop RFC 2535 special-casing of the KEY record type 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. --- 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 76d25e981b3..7c6a24d4866 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 ea81381eb23..495d68e4564 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 ed920749cf9..781324fed26 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 a551d7d026f..fabe279d504 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 5713f21bfbc..64e49ad3f0f 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 690cf753941..eb85d5092c2 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 ced8ce3cf75..dce80a32ff6 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 2d375188a23..0dfc0cf7e3f 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 c5358705049..5c59014eb5e 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 b065b3e555b..940b54e2071 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);