From 8db40614c4764f7c1188d7d1c39a0b51a75a33dd Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 27 Aug 2025 11:45:11 -0700 Subject: [PATCH] dnssec-signzone -Z none removes ZONEMD records "dnssec-signzone -Z none" removes pre-existing ZONEMD records from a signed zone. If followed by another -Z option, a new ZONEMD record will be added to replace the existing ones. This can be used, for instance, to switch from one scheme/digest to the other. --- bin/dnssec/dnssec-signzone.c | 60 ++++++++++++++++++++------- bin/dnssec/dnssec-signzone.rst | 18 ++++---- bin/tests/system/dnssectools/tests.sh | 36 ++++++++++++++++ 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index b582496dd9..7b42e1d55b 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -173,7 +173,9 @@ static bool set_maxttl = false; static dns_ttl_t maxttl = 0; static bool no_max_check = false; static const char *sync_records = "cdnskey,cds:sha-256"; +uint8_t zonemd_scheme[2] = { 0 }, zonemd_digest[2] = { 0 }; static bool add_zonemd = false; +static bool del_zonemd = false; #define INCSTAT(counter) \ if (printstats) { \ @@ -1963,7 +1965,7 @@ addnsec3param(const unsigned char *salt, size_t salt_len, if (result == DNS_R_UNCHANGED) { result = ISC_R_SUCCESS; } - check_result(result, "dddnsec3param: dns_db_deleterdataset()"); + check_result(result, "addnsec3param: dns_db_deleterdataset()"); result = dns_db_addrdataset(gdb, node, gversion, 0, &rdataset, DNS_DBADD_MERGE, NULL); @@ -2217,6 +2219,35 @@ addzonemd(uint8_t scheme, uint8_t digest_type) { dns_db_detachnode(&node); } +static void +setup_zonemd(void) { + if (del_zonemd) { + /* Delete any current ZONEMD records.*/ + isc_result_t result; + dns_dbnode_t *node = NULL; + + result = dns_db_getoriginnode(gdb, &node); + check_result(result, "dns_db_getoriginnode()"); + + result = dns_db_deleterdataset(gdb, node, gversion, + dns_rdatatype_zonemd, 0); + dns_db_detachnode(&node); + + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + check_result(result, "dns_db_deleterdataset()"); + } + + if (add_zonemd) { + for (size_t i = 0; i < ARRAY_SIZE(zonemd_scheme); i++) { + if (zonemd_scheme[i] != 0) { + addzonemd(zonemd_scheme[i], zonemd_digest[i]); + } + } + } +} + static void rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, dns_diff_t *add, dns_diff_t *del) { @@ -3389,7 +3420,6 @@ main(int argc, char *argv[]) { bool set_optout = false; bool set_iter = false; bool nonsecify = false; - uint8_t zonemd_scheme[2] = { 0 }, zonemd_digest[2] = { 0 }; atomic_init(&shuttingdown, false); atomic_init(&finished, false); @@ -3685,13 +3715,17 @@ main(int argc, char *argv[]) { break; case 'Z': - add_zonemd = true; - if (strcasecmp(isc_commandline_argument, - "simple-sha384") == 0 || - strcasecmp(isc_commandline_argument, "sha384") == - 0 || - strcmp(isc_commandline_argument, "-") == 0) + if (strcasecmp(isc_commandline_argument, "none") == 0) { + del_zonemd = true; + memset(zonemd_scheme, 0, sizeof(zonemd_scheme)); + memset(zonemd_digest, 0, sizeof(zonemd_digest)); + } else if (strcasecmp(isc_commandline_argument, + "simple-sha384") == 0 || + strcasecmp(isc_commandline_argument, + "sha384") == 0 || + strcmp(isc_commandline_argument, "-") == 0) { + add_zonemd = true; for (size_t i = 0; i < ARRAY_SIZE(zonemd_scheme); i++) { @@ -3713,6 +3747,7 @@ main(int argc, char *argv[]) { strcasecmp(isc_commandline_argument, "sha512") == 0) { + add_zonemd = true; for (size_t i = 0; i < ARRAY_SIZE(zonemd_scheme); i++) { @@ -4043,13 +4078,8 @@ main(int argc, char *argv[]) { /* Remove duplicates and cap TTLs at maxttl */ cleanup_zone(); - if (add_zonemd) { - for (size_t i = 0; i < ARRAY_SIZE(zonemd_scheme); i++) { - if (zonemd_scheme[i] != 0) { - addzonemd(zonemd_scheme[i], zonemd_digest[i]); - } - } - } + /* Set up ZONEMD records */ + setup_zonemd(); if (!nonsecify) { if (IS_NSEC3) { diff --git a/bin/dnssec/dnssec-signzone.rst b/bin/dnssec/dnssec-signzone.rst index a28d6ffa17..55448274a5 100644 --- a/bin/dnssec/dnssec-signzone.rst +++ b/bin/dnssec/dnssec-signzone.rst @@ -403,13 +403,17 @@ Options .. option:: -Z method - This option causes a ZONEMD record to be added to the signed zone, - if there wasn't a ZONEMD already present. The ``method`` parameter - indicates the scheme and digest type to use: valid options are - ``simple-sha384`` (or ``sha384``, or ``-``) for scheme SIMPLE - and digest type SHA384, and ``simple-sha512`` (or ``sha512``) - for scheme SIMPLE and digest type SHA512. Multiple ``-Z`` options - can be used simultaneously to add multiple ZONEMD records. + This option causes a ZONEMD record to be added to, or removed from, + the signed zone. The ``method`` parameter indicates the scheme and + digest type to use: valid options are ``simple-sha384`` (or + ``sha384``, or ``-``) for scheme SIMPLE and digest type SHA384, + and ``simple-sha512`` (or ``sha512``) for scheme SIMPLE and digest + type SHA512. ``-Z none`` removes all ZONEMD records. + + Multiple ``-Z`` options can be used simultaneously, and are processed + in order. This allows multiple ZONEMD records to be added with a single + command, or (by specifying ``none`` first) existing ZONEMD records to + be removed and replaced. .. option:: zonefile diff --git a/bin/tests/system/dnssectools/tests.sh b/bin/tests/system/dnssectools/tests.sh index 5689182a29..f6b2d5471f 100644 --- a/bin/tests/system/dnssectools/tests.sh +++ b/bin/tests/system/dnssectools/tests.sh @@ -279,6 +279,42 @@ n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) +echo_ic "check that dnssec-signzone -Z none removes ZONEMD records ($n)" +ret=0 +( + cd signer/general || exit 1 + rm -f signed.zone + $SIGNER -Z none -f signed.zone -o example.com. test14.zone >signer.out.$n + count=$(grep -c "ZONEMD.90000 1 1 " signed.zone) + test $count -eq 1 && exit 1 + count=$(grep -c "ZONEMD.90000 1 2 " signed.zone) + test $count -eq 1 && exit 1 + count=$(grep -c "RRSIG.ZONEMD" signed.zone) + test $count -eq 1 && exit 1 + test -f signed.zone +) || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_ic "check that dnssec-signzone -Z none -Z - keeps one ZONEMD record ($n)" +ret=0 +( + cd signer/general || exit 1 + rm -f signed.zone + $SIGNER -Z none -Z - -f signed.zone -o example.com. test14.zone >signer.out.$n + count=$(grep -c "ZONEMD.90000 1 1 " signed.zone) + test $count -eq 1 || exit 1 + count=$(grep -c "ZONEMD.90000 1 2 " signed.zone) + test $count -eq 1 && exit 1 + count=$(grep -c "RRSIG.ZONEMD" signed.zone) + test $count -eq 1 || exit 1 + test -f signed.zone +) || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + get_default_algorithm_key_ids_from_sigs() { zone=$1