diff --git a/bin/tests/system/nsupdate/ns3/named.conf.in b/bin/tests/system/nsupdate/ns3/named.conf.in index 180ce700bf..a87973b48b 100644 --- a/bin/tests/system/nsupdate/ns3/named.conf.in +++ b/bin/tests/system/nsupdate/ns3/named.conf.in @@ -105,3 +105,15 @@ zone "multisigner.test" { dnssec-policy "default-dynamic"; file "multisigner.test.db"; }; + +zone "zonemd.test" { + type primary; + allow-update { any; }; + file "zonemd.test.db.signed"; +}; + +zone "zonemd-nsec3.test" { + type primary; + allow-update { any; }; + file "zonemd-nsec3.test.db.signed"; +}; diff --git a/bin/tests/system/nsupdate/ns3/sign.sh b/bin/tests/system/nsupdate/ns3/sign.sh index 501f9fb379..542b3f409e 100644 --- a/bin/tests/system/nsupdate/ns3/sign.sh +++ b/bin/tests/system/nsupdate/ns3/sign.sh @@ -48,3 +48,28 @@ $SIGNER -A -3 - -P -o $zone -k $keyname1 $zonefile $keyname2 >/dev/null # Just copy multisigner.db.in because it is signed with dnssec-policy. cp multisigner.test.db.in multisigner.test.db + +# test a NSEC signed zone with ZONEMD being added. + +zone=zonemd.test. +infile=zonemd.test.db.in +zonefile=zonemd.test.db + +keyname1=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b 1024 -n zone -f KSK $zone) +keyname2=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b 1024 -n zone $zone) + +cat $infile $keyname1.key $keyname2.key >$zonefile + +$SIGNER -z -o $zone -k $keyname1 $zonefile $keyname2 >/dev/null + +# test a NSEC3 signed zone with ZONEMD being added. +zone=zonemd-nsec3.test. +infile=zonemd-nsec3.test.db.in +zonefile=zonemd-nsec3.test.db + +keyname1=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b 1024 -n zone -f KSK $zone) +keyname2=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b 1024 -n zone $zone) + +cat $infile $keyname1.key $keyname2.key >$zonefile + +$SIGNER -3 - -z -o $zone -k $keyname1 $zonefile $keyname2 >/dev/null diff --git a/bin/tests/system/nsupdate/ns3/zonemd-nsec3.test.db.in b/bin/tests/system/nsupdate/ns3/zonemd-nsec3.test.db.in new file mode 100644 index 0000000000..3781e3b59f --- /dev/null +++ b/bin/tests/system/nsupdate/ns3/zonemd-nsec3.test.db.in @@ -0,0 +1,17 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 10 +zonemd-nsec3.test. IN SOA ns3.zonemd-nsec3.test. hostmaster.zonemd-nsec3.test. 1 3600 900 2419200 3600 +zonemd-nsec3.test. IN NS ns3.zonemd-nsec3.test. +zonemd-nsec3.test. IN NS ns5.zonemd-nsec3.test. +ns3.zonemd-nsec3.test. IN A 10.53.0.3 +ns5.zonemd-nsec3.test. IN A 10.53.0.5 diff --git a/bin/tests/system/nsupdate/ns3/zonemd.test.db.in b/bin/tests/system/nsupdate/ns3/zonemd.test.db.in new file mode 100644 index 0000000000..b856ededb0 --- /dev/null +++ b/bin/tests/system/nsupdate/ns3/zonemd.test.db.in @@ -0,0 +1,15 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 10 +zonemd.test. IN SOA zonemd.test. hostmaster.zonemd.test. 1 3600 900 2419200 3600 +zonemd.test. IN NS zonemd.test. +zonemd.test. IN A 10.53.0.3 diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh index c6752ed75c..03412e6cbb 100755 --- a/bin/tests/system/nsupdate/tests.sh +++ b/bin/tests/system/nsupdate/tests.sh @@ -2443,6 +2443,44 @@ wait_for_log 10 "too many DNS UPDATEs queued" ns1/named.run || ret=1 status=1 } +n=$(expr $n + 1) +ret=0 +echo_i "check that adding a dummy ZONEMD record result updated ZONEMD record (nsec) ($n)" +$NSUPDATE -D -C resolv.conf -p ${PORT} <nsupdate.out-$n 2>&1 || ret=1 +server 10.53.0.3 ${PORT} +zone zonemd.test +update add zonemd.test 0 zonemd 0 1 1 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +send +EOF +$DIG $DIGOPTS +tcp +norec zonemd.test ZONEMD +dnssec @10.53.0.3 >dig.out.test$n || ret=1 +grep "status: NOERROR," dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +grep "^zonemd\.test\..*IN.ZONEMD.2 1 1" dig.out.test$n >/dev/null || ret=1 +grep "^zonemd\.test\..*IN.RRSIG.ZONEMD" dig.out.test$n >/dev/null || ret=1 +[ $ret = 0 ] || { + echo_i "failed" + status=1 +} + +n=$(expr $n + 1) +ret=0 +echo_i "check that adding a dummy ZONEMD record result updated ZONEMD record (nsec3) ($n)" +$NSUPDATE -D -C resolv.conf -p ${PORT} <nsupdate.out-$n 2>&1 || ret=1 +server 10.53.0.3 ${PORT} +zone zonemd-nsec3.test +update add zonemd-nsec3.test 0 zonemd 0 1 1 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +send +EOF +$DIG $DIGOPTS +tcp +norec zonemd-nsec3.test ZONEMD +dnssec @10.53.0.3 >dig.out.test$n || ret=1 +grep "status: NOERROR," dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +grep "^zonemd-nsec3\.test\..*IN.ZONEMD.2 1 1" dig.out.test$n >/dev/null || ret=1 +grep "^zonemd-nsec3\.test\..*IN.RRSIG.ZONEMD" dig.out.test$n >/dev/null || ret=1 +[ $ret = 0 ] || { + echo_i "failed" + status=1 +} + if ! $FEATURETEST --gssapi; then echo_i "SKIPPED: GSSAPI tests" else diff --git a/bin/tests/system/nsupdate/tests_sh_nsupdate.py b/bin/tests/system/nsupdate/tests_sh_nsupdate.py index 735b6a78e3..51bf39d874 100644 --- a/bin/tests/system/nsupdate/tests_sh_nsupdate.py +++ b/bin/tests/system/nsupdate/tests_sh_nsupdate.py @@ -59,6 +59,8 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/nsec3param.test.db", "ns3/relaxed.db", "ns3/too-big.test.db", + "ns3/zonemd-nsec3.test.db", + "ns3/zonemd.test.db", "ns5/local.db", "ns6/2.0.0.2.ip6.addr.db", "ns6/in-addr.db", diff --git a/lib/dns/include/dns/update.h b/lib/dns/include/dns/update.h index ec406dca0c..c88268d57c 100644 --- a/lib/dns/include/dns/update.h +++ b/lib/dns/include/dns/update.h @@ -54,6 +54,13 @@ dns_update_soaserial(uint32_t serial, dns_updatemethod_t method, * Sets *used to the method that was used. */ +isc_result_t +dns_update_zonemd(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff); +/*%< + * Update the apex ZONEMD RRset's simple records to reflect changes in + * zone content. + */ + isc_result_t dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *oldver, dns_dbversion_t *newver, diff --git a/lib/dns/update.c b/lib/dns/update.c index e38d0aec46..0ebb89fddc 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -53,6 +53,7 @@ #include #include #include +#include #include /**************************************************************************/ @@ -1484,7 +1485,8 @@ struct dns_update_state { sign_nsec, update_nsec3, process_nsec3, - sign_nsec3 + sign_nsec3, + update_zonemd } state; }; @@ -1515,6 +1517,73 @@ dns__jitter_expire(dns_zone_t *zone) { return sigvalidity; } +isc_result_t +dns_update_zonemd(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff) { + bool found[256] = { 0 }; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_zonemd, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + goto failure; + } + CHECK(result); + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_zonemd_t zonemd; + unsigned char buf[DNS_ZONEMD_BUFFERSIZE]; + + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &zonemd, NULL)); + if (zonemd.scheme == DNS_ZONEMD_SCHEME_SIMPLE) { + /* + * Delete all ZONEMD simple records. We will + * regenerate those with digest types we support. + */ + CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_DEL, + dns_db_origin(db), rdataset.ttl, + &rdata)); + /* + * Only add a single record for a given digest type + * regardless of how many records of that type exist. + */ + if (dns_zonemd_supported(&rdata) && + !found[zonemd.digest_type]) + { + dns_rdata_reset(&rdata); + CHECK(dns_zonemd_buildrdata( + &rdata, db, version, zonemd.scheme, + zonemd.digest_type, diff->mctx, buf, + sizeof(buf))); + CHECK(update_one_rr(db, version, diff, + DNS_DIFFOP_ADD, + dns_db_origin(db), + rdataset.ttl, &rdata)); + found[zonemd.digest_type] = true; + } + } + result = dns_rdataset_next(&rdataset); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return result; +} + isc_result_t dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *oldver, dns_dbversion_t *newver, @@ -1536,6 +1605,8 @@ dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, unsigned int sigs = 0; unsigned int maxsigs = dns_zone_getsignatures(zone); + dns_rdataset_init(&rdataset); + if (statep == NULL || *statep == NULL) { if (statep == NULL) { state = &mystate; @@ -1582,7 +1653,6 @@ dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, * MINIMUM field. */ CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); - dns_rdataset_init(&rdataset); CHECK(dns_db_findrdataset(db, node, newver, dns_rdatatype_soa, 0, (isc_stdtime_t)0, &rdataset, NULL)); @@ -1590,6 +1660,7 @@ dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &soa, NULL)); state->nsecttl = ISC_MIN(rdataset.ttl, soa.minimum); + dns_rdata_reset(&rdata); dns_rdataset_disassociate(&rdataset); dns_db_detachnode(db, &node); @@ -1992,7 +2063,8 @@ next_state: if (!state->build_nsec3) { update_log(log, zone, ISC_LOG_DEBUG(3), "no NSEC3 chains to rebuild"); - goto failure; + state->state = update_zonemd; + goto next_state; } update_log(log, zone, ISC_LOG_DEBUG(3), @@ -2156,6 +2228,41 @@ next_state: dns_diff_appendminimal(diff, &t); } + /* FALLTHROUGH */ + case update_zonemd: + state->state = update_zonemd; + /* + * Update apex ZONEMD RRset if present. + */ + update_log(log, zone, ISC_LOG_DEBUG(3), "Update apex ZONEMD?"); + + CHECK(dns_update_zonemd(db, newver, &state->work)); + if (ISC_LIST_EMPTY(state->work.tuples)) { + update_log(log, zone, ISC_LOG_DEBUG(3), + "No apex ZONEMD changes"); + goto failure; + } + + update_log(log, zone, ISC_LOG_DEBUG(3), + "Update apex ZONEMD RRset signatures"); + + CHECK(delete_if(true_p, db, newver, dns_db_origin(db), + dns_rdatatype_rrsig, dns_rdatatype_zonemd, NULL, + &state->sig_diff)); + CHECK(add_sigs(log, zone, db, newver, dns_db_origin(db), + dns_rdatatype_zonemd, &state->sig_diff, + state->zone_keys, state->nkeys, state->now, + state->inception, state->expire)); + + /* Record our changes for the journal. */ + while ((t = ISC_LIST_HEAD(state->work.tuples)) != NULL) { + ISC_LIST_UNLINK(state->work.tuples, t, link); + dns_diff_appendminimal(diff, &t); + } + while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) { + ISC_LIST_UNLINK(state->sig_diff.tuples, t, link); + dns_diff_appendminimal(diff, &t); + } INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples)); INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples)); INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples)); @@ -2165,6 +2272,9 @@ next_state: } failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } if (node != NULL) { dns_db_detachnode(db, &node); } diff --git a/lib/ns/update.c b/lib/ns/update.c index 1b73d5ba7c..77755cf8d0 100644 --- a/lib/ns/update.c +++ b/lib/ns/update.c @@ -3338,6 +3338,7 @@ update_action(void *arg) { */ CHECK(dns_nsec3param_deletechains(db, ver, zone, true, &diff)); + CHECK(dns_update_zonemd(db, ver, &diff)); } else if (has_dnskey && isdnssec(db, ver, privatetype)) { dns_update_log_t log; uint32_t interval = @@ -3354,6 +3355,8 @@ update_action(void *arg) { isc_result_totext(result)); goto failure; } + } else { + CHECK(dns_update_zonemd(db, ver, &diff)); } maxrecords = dns_zone_getmaxrecords(zone);