]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
zonemd: on signer, verify only non-dnssec-related records 1834/head
authorLibor Peltan <libor.peltan@nic.cz>
Mon, 8 Dec 2025 10:44:44 +0000 (11:44 +0100)
committerDaniel Salzman <daniel.salzman@nic.cz>
Tue, 9 Dec 2025 09:32:06 +0000 (10:32 +0100)
src/knot/events/handlers/load.c
src/knot/updates/zone-update.c
src/knot/zone/digest.c
src/knot/zone/digest.h
src/knot/zone/zonedb-load.c
src/utils/kzonecheck/zone_check.c
tests-extra/tests/zone/zonemd_chain/test.py
tests/knot/test_digest.c

index 634b4c6730efc548422a47278411a90780ff7195..6f6ab559103c7e19cb89a7d9adc74c8465a5698a 100644 (file)
@@ -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) {
index 6aede8d73efbaaf1ba9d94bc91f2a2d22c989641..900a6973ae4e2a75a9e11c2fe8fc4caba7257967 100644 (file)
@@ -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));
index accf7955c54006233ee084bbfa7403a88c70e6af..2503ec101e4053bffbd926d4a424ec36779e5c22 100644 (file)
@@ -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;
                }
index b08c1ed334faab45b9bff6cccaeb703e9834828b..dfbd95a7e20a546a2527e99dedc38bc32dd34b36 100644 (file)
 /*!
  * \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;
 /*!
index 1e6b79e2e8df309ba99e9ca77c9e299aaab62e1f..46140faba7cd29881d300035e87fd14934ca7b26 100644 (file)
@@ -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;
                }
        }
index edba74f8cf219c38d54d522964e4f82628296176..d06c39c65e8d6c00f5027d0bdf9880de7444be2a 100644 (file)
@@ -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");
index 981a452003ba80d3605012a3fefe9967f0c66c94..b866227388f106714196809d5d90e61fb775cf54 100644 (file)
@@ -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()
index 14e6b4c2b50c26e4ae90f7ab9c7e4d1d2d998e18..f25fb0c38ba08ec6328e453b27dec5b213be200a 100644 (file)
@@ -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;
 }