diff --git a/bin/tests/system/cds/setup.sh b/bin/tests/system/cds/setup.sh index 27474bcc0a..842c9c1402 100644 --- a/bin/tests/system/cds/setup.sh +++ b/bin/tests/system/cds/setup.sh @@ -87,8 +87,9 @@ sed 's/ add \(.*\) IN DS / add \1 3600 IN DS /' UP.swapttl sign() { cat >db.$1 + cat $keyz.key $key1.key $key2.key >>db.$1 $SIGNER >/dev/null \ - -S -O full -o $Z -f sig.$1 db.$1 + -O full -o $Z -f sig.$1 db.$1 $keyz $key1 $key2 } sign null <settime.out.$zone. $SETTIME -s -g $O -k $R $Tpub -z $R $Tpub "$ZSK1" >settime.out.$zone.2 2>&1 $SETTIME -s -g $O -k $R $Tpub -z $R $Tpub "$ZSK2" >settime.out.$zone.2 2>&1 +# CDS and CDNSKEY should be published. +zone="cds-cdnskey.kasp" +echo_i "setting up zone: $zone" +Tpub="now-7d" +Tact="now" +keytimes="-P ${Tpub} -A ${Tact}" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $keytimes $zone 2>keygen.out.$zone.2) +$SETTIME -s -g $O -k $R $Tpub -r $R $Tpub -d $H $Tpub "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $R $Tpub -z $R $Tpub "$ZSK" >settime.out.$zone.2 2>&1 + # # Set up zones that are already signed. # diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 13b711b283..c4ce8f738c 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -175,7 +175,15 @@ def fips_properties(alg, bits=None): def check_all( - server, zone, policy, ksks, zsks, manual_mode=False, zsk_missing=False, tsig=None + server, + zone, + policy, + ksks, + zsks, + manual_mode=False, + zsk_missing=False, + cdss=None, + tsig=None, ): isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy) isctest.kasp.check_apex( @@ -183,6 +191,7 @@ def check_all( zone, ksks, zsks, + cdss=cdss, manual_mode=manual_mode, zsk_missing=zsk_missing, tsig=tsig, @@ -1067,6 +1076,44 @@ def test_kasp_dynamic(ns3): assert f"zone_resigninc: zone {zone}/IN (unsigned): enter" not in "ns3/named.run" +def test_kasp_cds_cdnskey(ns3, default_algorithm): + # Zone: cds-cdnskey.kasp. + zone = "cds-cdnskey.kasp" + policy = "cds-cdnskey" + policy_keys = [ + f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured", + f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + + isctest.kasp.wait_keymgr_done(ns3, zone) + + expected = isctest.kasp.policy_to_properties(ttl=3600, keys=policy_keys) + keys = isctest.kasp.keydir_to_keylist(zone, "ns3") + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if k.is_zsk()] + isctest.kasp.check_dnssec_verify(ns3, zone) + isctest.kasp.check_keys(zone, keys, expected) + check_all(ns3, zone, policy, ksks, zsks) + + # Reconfig + shutil.copyfile("ns3/named-reconfig2.conf", "ns3/named-reconfig.conf") + with ns3.watch_log_from_here() as watcher: + ns3.rndc("reconfig") + watcher.wait_for_line(f"keymgr: {zone} done") + + policy = "cds-cdnskey-no" + check_all(ns3, zone, policy, ksks, zsks, cdss=[]) + + # Reconfig, put CDS, CDNSKEY back + shutil.copyfile("ns3/named-reconfig3.conf", "ns3/named-reconfig.conf") + with ns3.watch_log_from_here() as watcher: + ns3.rndc("reconfig") + watcher.wait_for_line(f"keymgr: {zone} done") + + policy = "cds-cdnskey-alt" + check_all(ns3, zone, policy, ksks, zsks, cdss=["CDNSKEY", "CDS (SHA-384)"]) + + def test_kasp_checkds(ns3, default_algorithm): def wait_for_metadata(): return isctest.util.file_contents_contain(ksk.statefile, metadata) diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index c36ea9d10f..aa664b9187 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -1849,9 +1849,22 @@ add_cds(dns_dnsseckey_t *key, dns_rdata_t *keyrdata, const char *keystr, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "CDS (%s) for key %s is now published", algbuf, keystr); - r = addrdata(&cdsrdata, diff, origin, ttl, mctx); + return addrdata(&cdsrdata, diff, origin, ttl, mctx); } - return r; + return DNS_R_UNCHANGED; +} + +static bool +contains_digest(dns_kasp_digestlist_t *digests, unsigned int digesttype) { + for (dns_kasp_digest_t *alg = ISC_LIST_HEAD(*digests); alg != NULL; + alg = ISC_LIST_NEXT(alg, link)) + { + if (digesttype == alg->digest) { + return true; + } + } + + return false; } static isc_result_t @@ -1877,9 +1890,9 @@ delete_cds(dns_dnsseckey_t *key, dns_rdata_t *keyrdata, const char *keystr, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "CDS (%s) for key %s is now deleted", algbuf, keystr); - r = delrdata(&cdsrdata, diff, origin, cds->ttl, mctx); + return delrdata(&cdsrdata, diff, origin, cds->ttl, mctx); } - return r; + return DNS_R_UNCHANGED; } isc_result_t @@ -1893,6 +1906,7 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, dns_dnsseckey_t *key; dns_ttl_t cdsttl = ttl; dns_ttl_t cdnskeyttl = ttl; + bool changed = false; REQUIRE(digests != NULL); REQUIRE(keys != NULL); @@ -1923,9 +1937,15 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, for (dns_kasp_digest_t *alg = ISC_LIST_HEAD(*digests); alg != NULL; alg = ISC_LIST_NEXT(alg, link)) { - CHECK(add_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - alg->digest, cdsttl, diff, mctx)); + result = add_cds(key, &cdnskeyrdata, + (const char *)keystr, cds, + alg->digest, cdsttl, diff, + mctx); + if (result == ISC_R_SUCCESS) { + changed = true; + } else if (result != DNS_R_UNCHANGED) { + goto cleanup; + } } if (gencdnskey && @@ -1939,41 +1959,64 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, keystr); RETERR(addrdata(&cdnskeyrdata, diff, origin, cdnskeyttl, mctx)); + changed = true; } } - if (syncdelete(key->key, now)) { + if (dns_rdataset_isassociated(cds)) { char keystr[DST_KEY_FORMATSIZE]; dst_key_format(key->key, keystr, sizeof(keystr)); - if (dns_rdataset_isassociated(cds)) { - /* Delete all possible CDS records */ - delete_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - DNS_DSDIGEST_SHA1, diff, mctx); - delete_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - DNS_DSDIGEST_SHA256, diff, mctx); - delete_cds(key, &cdnskeyrdata, - (const char *)keystr, cds, - DNS_DSDIGEST_SHA384, diff, mctx); - } - - if (dns_rdataset_isassociated(cdnskey)) { - if (exists(cdnskey, &cdnskeyrdata)) { - isc_log_write(dns_lctx, - DNS_LOGCATEGORY_GENERAL, - DNS_LOGMODULE_DNSSEC, - ISC_LOG_INFO, - "CDNSKEY for key %s is " - "now deleted", - keystr); - RETERR(delrdata(&cdnskeyrdata, diff, - origin, cdnskey->ttl, - mctx)); + /* Delete all possible CDS records */ + for (dns_dsdigest_t digest = DNS_DSDIGEST_SHA1; + digest < DNS_DSDIGEST_TOTAL; digest++) + { + if (syncdelete(key->key, now) || + !contains_digest(digests, digest)) + { + result = delete_cds( + key, &cdnskeyrdata, + (const char *)keystr, cds, + digest, diff, mctx); + switch (result) { + case ISC_R_SUCCESS: + changed = true; + break; + case DNS_R_UNCHANGED: + case ISC_R_NOTIMPLEMENTED: + /* + * Either the digest is not + * supported and we cannot + * construct the CDS for it, or + * the CDS with this digest is + * not present in the CDS RRset. + */ + break; + default: + goto cleanup; + } } } } + + if (dns_rdataset_isassociated(cdnskey) && + exists(cdnskey, &cdnskeyrdata)) + { + if (syncdelete(key->key, now) || !gencdnskey) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, + sizeof(keystr)); + + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDNSKEY for key %s is now deleted", + keystr); + delrdata(&cdnskeyrdata, diff, origin, + cdnskey->ttl, mctx); + changed = true; + } + } } if (!dns_rdataset_isassociated(cds) && @@ -1998,12 +2041,30 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, &cdnskeyrdata)); if (dns_rdataset_isassociated(cds)) { - delete_cds(key, &cdnskeyrdata, (const char *)keystr, - cds, DNS_DSDIGEST_SHA1, diff, mctx); - delete_cds(key, &cdnskeyrdata, (const char *)keystr, - cds, DNS_DSDIGEST_SHA256, diff, mctx); - delete_cds(key, &cdnskeyrdata, (const char *)keystr, - cds, DNS_DSDIGEST_SHA384, diff, mctx); + for (dns_dsdigest_t digest = DNS_DSDIGEST_SHA1; + digest < DNS_DSDIGEST_TOTAL; digest++) + { + result = delete_cds(key, &cdnskeyrdata, + (const char *)keystr, cds, + digest, diff, mctx); + switch (result) { + case ISC_R_SUCCESS: + changed = true; + break; + case DNS_R_UNCHANGED: + case ISC_R_NOTIMPLEMENTED: + /* + * Either the digest is not + * supported and we cannot + * construct the CDS for it, or + * the CDS with this digest is + * not present in the CDS RRset. + */ + break; + default: + goto cleanup; + } + } } if (dns_rdataset_isassociated(cdnskey)) { @@ -2015,11 +2076,15 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, keystr); RETERR(delrdata(&cdnskeyrdata, diff, origin, cdnskey->ttl, mctx)); + changed = true; } } } - result = ISC_R_SUCCESS; + if (changed) { + return ISC_R_SUCCESS; + } + return DNS_R_UNCHANGED; cleanup: return result; @@ -2036,6 +2101,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, dns_rdata_t cds_delete = DNS_RDATA_INIT; dns_rdata_t cdnskey_delete = DNS_RDATA_INIT; isc_region_t r; + bool changed = false; r.base = keybuf; r.length = sizeof(keybuf); @@ -2058,6 +2124,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, "published", namebuf); RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx)); + changed = true; } } else { if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete)) @@ -2069,6 +2136,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, namebuf); RETERR(delrdata(&cds_delete, diff, origin, cds->ttl, mctx)); + changed = true; } } @@ -2083,6 +2151,7 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, namebuf); RETERR(addrdata(&cdnskey_delete, diff, origin, ttl, mctx)); + changed = true; } } else { if (dns_rdataset_isassociated(cdnskey) && @@ -2095,10 +2164,14 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, namebuf); RETERR(delrdata(&cdnskey_delete, diff, origin, cdnskey->ttl, mctx)); + changed = true; } } - return ISC_R_SUCCESS; + if (changed) { + return ISC_R_SUCCESS; + } + return DNS_R_UNCHANGED; } /* diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h index 649ca80065..b63cfccdee 100644 --- a/lib/dns/include/dns/ds.h +++ b/lib/dns/include/dns/ds.h @@ -22,6 +22,7 @@ #define DNS_DSDIGEST_SHA256 (2) #define DNS_DSDIGEST_GOST (3) #define DNS_DSDIGEST_SHA384 (4) +#define DNS_DSDIGEST_TOTAL (5) #define DNS_DSDIGEST_MAX (255) diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 655da2999f..8137923382 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -22797,7 +22797,7 @@ zone_rekey(dns_zone_t *zone) { result = dns_dnssec_syncupdate(&dnskeys, &rmkeys, &cdsset, &cdnskeyset, now, &digests, cdnskeypub, ttl, &diff, mctx); - if (result != ISC_R_SUCCESS) { + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY: %s", isc_result_totext(result)); @@ -22835,7 +22835,7 @@ zone_rekey(dns_zone_t *zone) { result = dns_dnssec_syncdelete( &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl, &diff, mctx, cdsdel, cdnskeydel); - if (result != ISC_R_SUCCESS) { + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY " "DELETE records: %s",