From: Libor Peltan Date: Mon, 8 Dec 2025 10:44:44 +0000 (+0100) Subject: zonemd: on signer, verify only non-dnssec-related records X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fmerge-requests%2F1834%2Fhead;p=thirdparty%2Fknot-dns.git zonemd: on signer, verify only non-dnssec-related records --- diff --git a/src/knot/events/handlers/load.c b/src/knot/events/handlers/load.c index 634b4c6730..6f6ab55910 100644 --- a/src/knot/events/handlers/load.c +++ b/src/knot/events/handlers/load.c @@ -420,7 +420,7 @@ load_end: /* Don't update ZONEMD if no change and ZONEMD is up-to-date. * If ZONEFILE_LOAD_DIFSE, the change is non-empty and ZONEMD * is directly updated without its verification. */ - if (!zone_update_no_change(&up) || !zone_contents_digest_exists(up.new_cont, digest_alg, false)) { + if (!zone_update_no_change(&up) || !zone_contents_digest_exists(up.new_cont, digest_alg, false, false)) { if (zone_update_to(&up) == NULL || middle_serial == zone->zonefile.serial) { ret = zone_update_increment_soa(&up, conf); } @@ -448,7 +448,7 @@ load_end: } // If the original ZONEMD is outdated, use the reverted changeset again. - if (update_zonemd && !zone_contents_digest_exists(up.new_cont, digest_alg, false)) { + if (update_zonemd && !zone_contents_digest_exists(up.new_cont, digest_alg, false, false)) { ret = zone_update_apply_changeset(&up, cpy); changeset_free(cpy); if (ret != KNOT_EOK) { diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c index 6aede8d73e..900a6973ae 100644 --- a/src/knot/updates/zone-update.c +++ b/src/knot/updates/zone-update.c @@ -987,8 +987,9 @@ int zone_update_verify_digest(conf_t *conf, zone_update_t *update) if (!conf_bool(&val)) { return KNOT_EOK; } + val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name); - int ret = zone_contents_digest_verify(update->new_cont); + int ret = zone_contents_digest_verify(update->new_cont, conf_bool(&val)); if (ret != KNOT_EOK) { log_zone_error(update->zone->name, "ZONEMD, verification failed (%s)", knot_strerror(ret)); diff --git a/src/knot/zone/digest.c b/src/knot/zone/digest.c index accf7955c5..2503ec101e 100644 --- a/src/knot/zone/digest.c +++ b/src/knot/zone/digest.c @@ -19,6 +19,7 @@ typedef struct { uint8_t *buf; struct dnssec_digest_ctx *digest_ctx; const zone_node_t *apex; + bool ignore_dnssec; } contents_digest_ctx_t; static int digest_rrset(knot_rrset_t *rrset, const zone_node_t *node, void *vctx) @@ -30,6 +31,11 @@ static int digest_rrset(knot_rrset_t *rrset, const zone_node_t *node, void *vctx return KNOT_EOK; } + // ignore DNSSEC if verifying on signer + if (ctx->ignore_dnssec && knot_rrtype_is_dnssec(rrset->type)) { + return KNOT_EOK; + } + // ignore RRSIGs of apex ZONEMD if (node == ctx->apex && rrset->type == KNOT_RRTYPE_RRSIG) { knot_rdataset_t cpy = rrset->rrs, zonemd_rrsig = { 0 }; @@ -87,6 +93,7 @@ static int digest_node(zone_node_t *node, void *ctx) } int zone_contents_digest(const zone_contents_t *contents, int algorithm, + bool ignore_dnssec, uint8_t **out_digest, size_t *out_size) { if (out_digest == NULL || out_size == NULL) { @@ -101,6 +108,7 @@ int zone_contents_digest(const zone_contents_t *contents, int algorithm, .buf_size = DIGEST_BUF_MIN, .buf = malloc(DIGEST_BUF_MIN), .apex = contents->apex, + .ignore_dnssec = ignore_dnssec, }; if (ctx.buf == NULL) { return KNOT_ENOMEM; @@ -141,12 +149,13 @@ int zone_contents_digest(const zone_contents_t *contents, int algorithm, return ret; } -static int verify_zonemd(const knot_rdata_t *zonemd, const zone_contents_t *contents) +static int verify_zonemd(const knot_rdata_t *zonemd, const zone_contents_t *contents, + bool ignore_dnssec) { uint8_t *computed = NULL; size_t comp_size = 0; int ret = zone_contents_digest(contents, knot_zonemd_algorithm(zonemd), - &computed, &comp_size); + ignore_dnssec, &computed, &comp_size); if (ret != KNOT_EOK) { return ret; } @@ -161,7 +170,8 @@ static int verify_zonemd(const knot_rdata_t *zonemd, const zone_contents_t *cont return ret; } -bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool no_verify) +bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool no_verify, + bool ignore_dnssec) { if (alg == 0) { return true; @@ -181,7 +191,7 @@ bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool return true; } - return verify_zonemd(zonemd->rdata, contents) == KNOT_EOK; + return verify_zonemd(zonemd->rdata, contents, ignore_dnssec) == KNOT_EOK; } static bool check_duplicate_schalg(const knot_rdataset_t *zonemd, int check_upto, @@ -199,7 +209,7 @@ static bool check_duplicate_schalg(const knot_rdataset_t *zonemd, int check_upto return true; } -int zone_contents_digest_verify(const zone_contents_t *contents) +int zone_contents_digest_verify(const zone_contents_t *contents, bool ignore_dnssec) { if (contents == NULL) { return KNOT_EEMPTYZONE; @@ -226,7 +236,7 @@ int zone_contents_digest_verify(const zone_contents_t *contents) rr = knot_rdataset_next(rr); } - return supported == NULL ? KNOT_ENOTSUP : verify_zonemd(supported, contents); + return supported == NULL ? KNOT_ENOTSUP : verify_zonemd(supported, contents, ignore_dnssec); } static ptrdiff_t zonemd_hash_offs(void) @@ -255,7 +265,7 @@ int zone_update_add_digest(struct zone_update *update, int algorithm, bool place return KNOT_EOK; } } else { - int ret = zone_contents_digest(update->new_cont, algorithm, &digest, &dsize); + int ret = zone_contents_digest(update->new_cont, algorithm, false, &digest, &dsize); if (ret != KNOT_EOK) { return ret; } diff --git a/src/knot/zone/digest.h b/src/knot/zone/digest.h index b08c1ed334..dfbd95a7e2 100644 --- a/src/knot/zone/digest.h +++ b/src/knot/zone/digest.h @@ -10,14 +10,16 @@ /*! * \brief Compute hash over whole zone by concatenating RRSets in wire format. * - * \param contents Zone contents to digest. - * \param algorithm Algorithm to use. - * \param out_digest Output: buffer with computed hash (to be freed). - * \param out_size Output: size of the resulting hash. + * \param contents Zone contents to digest. + * \param algorithm Algorithm to use. + * \param ignore_dnssec Skip DNSSEC-related records while computing the digest. + * \param out_digest Output: buffer with computed hash (to be freed). + * \param out_size Output: size of the resulting hash. * * \return KNOT_E* */ int zone_contents_digest(const zone_contents_t *contents, int algorithm, + bool ignore_dnssec, uint8_t **out_digest, size_t *out_size); /*! @@ -25,16 +27,19 @@ int zone_contents_digest(const zone_contents_t *contents, int algorithm, * * \note Special value 255 of algorithm means that ZONEMD shall not exist. * - * \param contents Zone contents to be verified. - * \param alg Required algorithm of the ZONEMD. - * \param no_verify Don't verify the validness of the digest in ZONEMD. + * \param contents Zone contents to be verified. + * \param alg Required algorithm of the ZONEMD. + * \param no_verify Don't verify the validness of the digest in ZONEMD. + * \param ignore_dnssec Skip DNSSEC-related records when eventually verifying the digest. */ -bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool no_verify); +bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool no_verify, + bool ignore_dnssec); /*! * \brief Verify zone dgest in ZONEMD record. * - * \param contents Zone contents ot be verified. + * \param contents Zone contents ot be verified. + * \param ignore_dnssec Skip DNSSEC-related records while computing the digest. * * \retval KNOT_EEMPTYZONE The zone is empty. * \retval KNOT_ENOENT There is no ZONEMD in contents' apex. @@ -44,7 +49,7 @@ bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool * \retval KNOT_EMALF The computed hash differs from ZONEMD. * \return KNOT_E* */ -int zone_contents_digest_verify(const zone_contents_t *contents); +int zone_contents_digest_verify(const zone_contents_t *contents, bool ignore_dnssec); struct zone_update; /*! diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c index 1e6b79e2e8..46140faba7 100644 --- a/src/knot/zone/zonedb-load.c +++ b/src/knot/zone/zonedb-load.c @@ -75,7 +75,7 @@ static void replan_events(conf_t *conf, zone_t *zone, zone_t *old_zone, bool con { if (!conf_updated) { conf_val_t digest = conf_zone_get(conf, C_ZONEMD_GENERATE, zone->name); - if (zone->contents != NULL && !zone_contents_digest_exists(zone->contents, conf_opt(&digest), true)) { + if (zone->contents != NULL && !zone_contents_digest_exists(zone->contents, conf_opt(&digest), true, false)) { conf_updated = true; } } diff --git a/src/utils/kzonecheck/zone_check.c b/src/utils/kzonecheck/zone_check.c index edba74f8cf..d06c39c65e 100644 --- a/src/utils/kzonecheck/zone_check.c +++ b/src/utils/kzonecheck/zone_check.c @@ -105,7 +105,7 @@ int zone_check(const char *zone_file, const knot_dname_t *zone_name, bool zonemd } if (zonemd) { - ret = zone_contents_digest_verify(contents); + ret = zone_contents_digest_verify(contents, false); if (ret != KNOT_EOK) { if (stats.error_count > 0 && !stats.handler.error) { (void)fprintf(stderr, "\n"); diff --git a/tests-extra/tests/zone/zonemd_chain/test.py b/tests-extra/tests/zone/zonemd_chain/test.py index 981a452003..b866227388 100644 --- a/tests-extra/tests/zone/zonemd_chain/test.py +++ b/tests-extra/tests/zone/zonemd_chain/test.py @@ -25,7 +25,7 @@ master.conf_zone(zones).zonemd_generate = "zonemd-sha384" signer.conf_zone(zones).zonemd_verify = True signer.dnssec(zones).enable = True -signer.conf_zone(zones).zonemd_generate = "zonemd-sha384" +signer.conf_zone(zones).zonemd_generate = random.choice(["zonemd-sha384", "zonemd-sha512"]) slave.conf_zone(zones).dnssec_validation = True slave.conf_zone(zones).zonemd_verify = True @@ -34,18 +34,12 @@ t.start() serials = slave.zones_wait(zones) master.random_ddns(zones, allow_empty=False) -t.sleep(4) -slave.zones_wait(zones, serials, equal=True, greater=False) -signer.ctl("zone-retransfer") serials = slave.zones_wait(zones, serials) -signer.conf_zone(zones).zonemd_verify = False -signer.gen_confile() -signer.reload() - +signer.ctl("zone-freeze", wait=True) +master.random_ddns(zones, allow_empty=False) master.random_ddns(zones, allow_empty=False) +signer.ctl("zone-thaw") serials = slave.zones_wait(zones, serials) -if signer.log_search_count("fallback to AXFR ") > 0: # NOTE without the trailing space the message can appear for outgoing IXFR as well, which it actually should - set_err("AXFR fallback") t.end() diff --git a/tests/knot/test_digest.c b/tests/knot/test_digest.c index 14e6b4c2b5..f25fb0c38b 100644 --- a/tests/knot/test_digest.c +++ b/tests/knot/test_digest.c @@ -58,7 +58,7 @@ static zone_contents_t *str2contents(const char *zone_str) static int check_contents(const char *zone_str) { zone_contents_t *cont = str2contents(zone_str); - int ret = zone_contents_digest_verify(cont); + int ret = zone_contents_digest_verify(cont, false); zone_contents_deep_free(cont); return ret; }