diff options
Diffstat (limited to 'src/knot/dnssec/zone-keys.c')
-rw-r--r-- | src/knot/dnssec/zone-keys.c | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/src/knot/dnssec/zone-keys.c b/src/knot/dnssec/zone-keys.c new file mode 100644 index 0000000..a76c4be --- /dev/null +++ b/src/knot/dnssec/zone-keys.c @@ -0,0 +1,767 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <limits.h> +#include <stdio.h> + +#include "libdnssec/error.h" +#include "knot/common/log.h" +#include "knot/dnssec/zone-keys.h" +#include "libknot/libknot.h" +#include "contrib/openbsd/strlcat.h" + +#define MAX_KEY_INFO 128 + +typedef struct { + char msg[MAX_KEY_INFO]; + knot_time_t key_time; +} key_info_t; + +knot_dynarray_define(keyptr, zone_key_t *, DYNARRAY_VISIBILITY_NORMAL) + +void normalize_generate_flags(kdnssec_generate_flags_t *flags) +{ + if (!(*flags & DNSKEY_GENERATE_KSK) && !(*flags & DNSKEY_GENERATE_ZSK)) { + *flags |= DNSKEY_GENERATE_ZSK; + } + if (!(*flags & DNSKEY_GENERATE_SEP_SPEC)) { + if ((*flags & DNSKEY_GENERATE_KSK)) { + *flags |= DNSKEY_GENERATE_SEP_ON; + } else { + *flags &= ~DNSKEY_GENERATE_SEP_ON; + } + } +} + +static int generate_dnssec_key(dnssec_keystore_t *keystore, + const knot_dname_t *zone_name, + const char *key_label, + dnssec_key_algorithm_t alg, + unsigned size, + kdnssec_generate_flags_t flags, + char **id, + dnssec_key_t **key) +{ + *key = NULL; + *id = NULL; + + int ret = dnssec_keystore_generate(keystore, alg, size, key_label, id); + if (ret != KNOT_EOK) { + return ret; + } + + ret = dnssec_key_new(key); + if (ret != KNOT_EOK) { + goto fail; + } + + ret = dnssec_key_set_dname(*key, zone_name); + if (ret != KNOT_EOK) { + goto fail; + } + + dnssec_key_set_flags(*key, dnskey_flags(flags & DNSKEY_GENERATE_SEP_ON)); + dnssec_key_set_algorithm(*key, alg); + + ret = dnssec_keystore_get_private(keystore, *id, *key); + if (ret != KNOT_EOK) { + goto fail; + } + + return KNOT_EOK; + +fail: + dnssec_key_free(*key); + *key = NULL; + free(*id); + *id = NULL; + return ret; +} + +static bool keytag_in_use(kdnssec_ctx_t *ctx, uint16_t keytag) +{ + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + uint16_t used = dnssec_key_get_keytag(ctx->zone->keys[i].key); + if (used == keytag) { + return true; + } + } + return false; +} + +#define GENERATE_KEYTAG_ATTEMPTS (20) + +static int generate_keytag_unconflict(kdnssec_ctx_t *ctx, + kdnssec_generate_flags_t flags, + char **id, + dnssec_key_t **key) +{ + unsigned size = (flags & DNSKEY_GENERATE_KSK) ? ctx->policy->ksk_size : + ctx->policy->zsk_size; + + const char *label = NULL; + + char label_buf[sizeof(knot_dname_txt_storage_t) + 16]; + if (ctx->policy->key_label && + knot_dname_to_str(label_buf, ctx->zone->dname, sizeof(label_buf)) != NULL) { + const char *key_type = (flags & DNSKEY_GENERATE_KSK) ? " KSK" : " ZSK" ; + strlcat(label_buf, key_type, sizeof(label_buf)); + label = label_buf; + } + + for (size_t i = 0; i < GENERATE_KEYTAG_ATTEMPTS; i++) { + dnssec_key_free(*key); + free(*id); + + int ret = generate_dnssec_key(ctx->keystore, ctx->zone->dname, label, + ctx->policy->algorithm, size, flags, + id, key); + if (ret != KNOT_EOK) { + return ret; + } + if (!keytag_in_use(ctx, dnssec_key_get_keytag(*key))) { + return KNOT_EOK; + } + } + + log_zone_notice(ctx->zone->dname, "generated key with conflicting keytag %hu", + dnssec_key_get_keytag(*key)); + return KNOT_EOK; +} + +int kdnssec_generate_key(kdnssec_ctx_t *ctx, kdnssec_generate_flags_t flags, + knot_kasp_key_t **key_ptr) +{ + assert(ctx); + assert(ctx->zone); + assert(ctx->keystore); + assert(ctx->policy); + + normalize_generate_flags(&flags); + + // generate key in the keystore + + char *id = NULL; + dnssec_key_t *dnskey = NULL; + + int r = generate_keytag_unconflict(ctx, flags, &id, &dnskey); + if (r != KNOT_EOK) { + return r; + } + + knot_kasp_key_t *key = calloc(1, sizeof(*key)); + if (!key) { + dnssec_key_free(dnskey); + free(id); + return KNOT_ENOMEM; + } + + key->id = id; + key->key = dnskey; + key->is_ksk = (flags & DNSKEY_GENERATE_KSK); + key->is_zsk = (flags & DNSKEY_GENERATE_ZSK); + key->timing.created = ctx->now; + + r = kasp_zone_append(ctx->zone, key); + free(key); + if (r != KNOT_EOK) { + dnssec_key_free(dnskey); + free(id); + return r; + } + + if (key_ptr) { + *key_ptr = &ctx->zone->keys[ctx->zone->num_keys - 1]; + } + + return KNOT_EOK; +} + +int kdnssec_share_key(kdnssec_ctx_t *ctx, const knot_dname_t *from_zone, const char *key_id) +{ + knot_dname_t *to_zone = knot_dname_copy(ctx->zone->dname, NULL); + if (to_zone == NULL) { + return KNOT_ENOMEM; + } + + int ret = kdnssec_ctx_commit(ctx); + if (ret != KNOT_EOK) { + free(to_zone); + return ret; + } + + ret = kasp_db_share_key(ctx->kasp_db, from_zone, ctx->zone->dname, key_id); + if (ret != KNOT_EOK) { + free(to_zone); + return ret; + } + + kasp_zone_clear(ctx->zone); + ret = kasp_zone_load(ctx->zone, to_zone, ctx->kasp_db, + &ctx->keytag_conflict); + free(to_zone); + return ret; +} + +int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr) +{ + assert(ctx); + assert(ctx->zone); + assert(ctx->keystore); + assert(ctx->policy); + + ssize_t key_index = key_ptr - ctx->zone->keys; + + if (key_index < 0 || key_index >= ctx->zone->num_keys) { + return KNOT_EINVAL; + } + + bool key_still_used_in_keystore = false; + int ret = kasp_db_delete_key(ctx->kasp_db, ctx->zone->dname, key_ptr->id, &key_still_used_in_keystore); + if (ret != KNOT_EOK) { + return ret; + } + + if (!key_still_used_in_keystore && !key_ptr->is_pub_only) { + ret = dnssec_keystore_remove(ctx->keystore, key_ptr->id); + if (ret != KNOT_EOK) { + return ret; + } + } + + dnssec_key_free(key_ptr->key); + free(key_ptr->id); + memmove(key_ptr, key_ptr + 1, (ctx->zone->num_keys - key_index - 1) * sizeof(*key_ptr)); + ctx->zone->num_keys--; + return KNOT_EOK; +} + +static bool is_published(knot_kasp_key_timing_t *timing, knot_time_t now) +{ + return (knot_time_cmp(timing->publish, now) <= 0 && + knot_time_cmp(timing->post_active, now) > 0 && + knot_time_cmp(timing->remove, now) > 0); +} + +static bool is_ready(knot_kasp_key_timing_t *timing, knot_time_t now) +{ + return (knot_time_cmp(timing->ready, now) <= 0 && + knot_time_cmp(timing->active, now) > 0); +} + +static bool is_active(knot_kasp_key_timing_t *timing, knot_time_t now) +{ + return (knot_time_cmp(timing->active, now) <= 0 && + knot_time_cmp(timing->retire, now) > 0 && + knot_time_cmp(timing->retire_active, now) > 0 && + knot_time_cmp(timing->remove, now) > 0); +} + +static bool alg_has_active_zsk(kdnssec_ctx_t *ctx, uint8_t alg) +{ + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + knot_kasp_key_t *k = &ctx->zone->keys[i]; + if (dnssec_key_get_algorithm(k->key) == alg && + k->is_zsk && is_active(&k->timing, ctx->now)) { + return true; + } + } + return false; +} + +static void fix_revoked_flag(knot_kasp_key_t *key) +{ + uint16_t flags = dnssec_key_get_flags(key->key); + if ((flags & DNSKEY_FLAGS_REVOKED) != DNSKEY_FLAGS_REVOKED) { + dnssec_key_set_flags(key->key, flags | DNSKEY_FLAGS_REVOKED); // FYI leading to change of keytag + } +} + +/*! + * \brief Get key feature flags from key parameters. + */ +static void set_key(knot_kasp_key_t *kasp_key, knot_time_t now, + zone_key_t *zone_key, bool same_alg_act_zsk) +{ + assert(kasp_key); + assert(zone_key); + + knot_kasp_key_timing_t *timing = &kasp_key->timing; + + zone_key->id = kasp_key->id; + zone_key->key = kasp_key->key; + + // next event computation + + knot_time_t next = 0; + knot_time_t timestamps[] = { + timing->pre_active, + timing->publish, + timing->ready, + timing->active, + timing->retire_active, + timing->retire, + timing->post_active, + timing->revoke, + timing->remove, + }; + + for (int i = 0; i < sizeof(timestamps) / sizeof(knot_time_t); i++) { + knot_time_t ts = timestamps[i]; + if (knot_time_cmp(now, ts) < 0 && knot_time_cmp(ts, next) < 0) { + next = ts; + } + } + + zone_key->next_event = next; + + zone_key->is_ksk = kasp_key->is_ksk; + zone_key->is_zsk = kasp_key->is_zsk; + + zone_key->is_public = is_published(timing, now); + zone_key->is_ready = (zone_key->is_ksk && is_ready(timing, now)); + zone_key->is_active = is_active(timing, now); + + zone_key->is_ksk_active_plus = zone_key->is_public && zone_key->is_ksk && !zone_key->is_active; // KSK is active+ whenever published + zone_key->is_zsk_active_plus = zone_key->is_ready && !same_alg_act_zsk; + if (knot_time_cmp(timing->pre_active, now) <= 0 && + knot_time_cmp(timing->ready, now) > 0 && + knot_time_cmp(timing->active, now) > 0 && + knot_time_cmp(timing->remove, now) > 0) { + zone_key->is_zsk_active_plus = zone_key->is_zsk; + // zone_key->is_ksk_active_plus = (knot_time_cmp(timing->publish, now) <= 0 && zone_key->is_ksk); // redundant, but helps understand + } + if (knot_time_cmp(timing->retire, now) <= 0 && + knot_time_cmp(timing->remove, now) > 0) { + zone_key->is_ksk_active_plus = false; + zone_key->is_public = zone_key->is_zsk; + } + if (knot_time_cmp(timing->retire_active, now) <= 0 && + knot_time_cmp(timing->retire, now) > 0 && + knot_time_cmp(timing->remove, now) > 0) { + zone_key->is_ksk_active_plus = zone_key->is_ksk; + zone_key->is_zsk_active_plus = !same_alg_act_zsk; + } // not "else" ! + if (knot_time_cmp(timing->post_active, now) <= 0 && + knot_time_cmp(timing->remove, now) > 0) { + zone_key->is_ksk_active_plus = false; + zone_key->is_zsk_active_plus = zone_key->is_zsk; + } + if (zone_key->is_ksk && + knot_time_cmp(timing->revoke, now) <= 0 && + knot_time_cmp(timing->remove, now) > 0) { + zone_key->is_ready = false; + zone_key->is_active = false; + zone_key->is_ksk_active_plus = true; + zone_key->is_public = true; + zone_key->is_revoked = true; + fix_revoked_flag(kasp_key); + } + if (kasp_key->is_pub_only) { + zone_key->is_active = false; + zone_key->is_ksk_active_plus = false; + zone_key->is_zsk_active_plus = false; + zone_key->is_pub_only = true; + } +} + +/*! + * \brief Check if algorithm is allowed with NSEC3. + */ +static bool is_nsec3_allowed(uint8_t algorithm) +{ + switch (algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + return false; + default: + return true; + } +} + +static int walk_algorithms(kdnssec_ctx_t *ctx, zone_keyset_t *keyset) +{ + if (ctx->policy->unsafe & UNSAFE_KEYSET) { + return KNOT_EOK; + } + + uint8_t alg_usage[256] = { 0 }; + bool have_active_alg = false; + + for (size_t i = 0; i < keyset->count; i++) { + zone_key_t *key = &keyset->keys[i]; + if (key->is_pub_only) { + continue; + } + uint8_t alg = dnssec_key_get_algorithm(key->key); + + if (ctx->policy->nsec3_enabled && !is_nsec3_allowed(alg)) { + log_zone_warning(ctx->zone->dname, "DNSSEC, key %d " + "cannot be used with NSEC3", + dnssec_key_get_keytag(key->key)); + key->is_public = false; + key->is_active = false; + key->is_ready = false; + key->is_ksk_active_plus = false; + key->is_zsk_active_plus = false; + continue; + } + + if (key->is_ksk && key->is_public) { alg_usage[alg] |= 1; } + if (key->is_zsk && key->is_public) { alg_usage[alg] |= 2; } + if (key->is_ksk && (key->is_active || key->is_ksk_active_plus)) { alg_usage[alg] |= 4; } + if (key->is_zsk && (key->is_active || key->is_zsk_active_plus)) { alg_usage[alg] |= 8; } + } + + for (size_t i = 0; i < sizeof(alg_usage); i++) { + if (!(alg_usage[i] & 3)) { + continue; // no public keys, ignore + } + switch (alg_usage[i]) { + case 15: // all keys ready for signing + have_active_alg = true; + break; + case 5: + case 10: + if (ctx->policy->offline_ksk) { + have_active_alg = true; + break; + } + // else FALLTHROUGH + default: + return KNOT_DNSSEC_EMISSINGKEYTYPE; + } + } + + if (!have_active_alg) { + return KNOT_DNSSEC_ENOKEY; + } + + return KNOT_EOK; +} + +/*! + * \brief Load private keys for active keys. + */ +static int load_private_keys(dnssec_keystore_t *keystore, zone_keyset_t *keyset) +{ + assert(keystore); + assert(keyset); + + for (size_t i = 0; i < keyset->count; i++) { + zone_key_t *key = &keyset->keys[i]; + if (!key->is_active && !key->is_ksk_active_plus && !key->is_zsk_active_plus) { + continue; + } + int r = dnssec_keystore_get_private(keystore, key->id, key->key); + switch (r) { + case DNSSEC_EOK: + case DNSSEC_KEY_ALREADY_PRESENT: + break; + default: + return r; + } + } + + return DNSSEC_EOK; +} + +/*! + * \brief Log information about zone keys. + */ +static void log_key_info(const zone_key_t *key, char *out, size_t out_len) +{ + assert(key); + assert(out); + + uint8_t alg_code = dnssec_key_get_algorithm(key->key); + const knot_lookup_t *alg = knot_lookup_by_id(knot_dnssec_alg_names, alg_code); + + char alg_code_str[8] = ""; + if (alg == NULL) { + (void)snprintf(alg_code_str, sizeof(alg_code_str), "%d", alg_code); + } + + (void)snprintf(out, out_len, "DNSSEC, key, tag %5d, algorithm %s%s%s%s%s%s", + dnssec_key_get_keytag(key->key), + (alg != NULL ? alg->name : alg_code_str), + (key->is_ksk ? (key->is_zsk ? ", CSK" : ", KSK") : ""), + (key->is_public ? ", public" : ""), + (key->is_ready ? ", ready" : ""), + (key->is_active ? ", active" : ""), + (key->is_ksk_active_plus || key->is_zsk_active_plus ? ", active+" : "")); +} + +static int log_key_sort(const void *a, const void *b) +{ + const key_info_t *x = a, *y = b; + return knot_time_cmp(x->key_time, y->key_time); +} + +/*! + * \brief Load zone keys and init cryptographic context. + */ +int load_zone_keys(kdnssec_ctx_t *ctx, zone_keyset_t *keyset_ptr, bool verbose) +{ + if (!ctx || !keyset_ptr) { + return KNOT_EINVAL; + } + + zone_keyset_t keyset = { 0 }; + + if (ctx->zone->num_keys < 1) { + log_zone_error(ctx->zone->dname, "DNSSEC, no keys are available"); + return KNOT_DNSSEC_ENOKEY; + } + + keyset.count = ctx->zone->num_keys; + keyset.keys = calloc(keyset.count, sizeof(zone_key_t)); + if (!keyset.keys) { + free_zone_keys(&keyset); + return KNOT_ENOMEM; + } + + key_info_t key_info[ctx->zone->num_keys]; + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + knot_kasp_key_t *kasp_key = &ctx->zone->keys[i]; + uint8_t kk_alg = dnssec_key_get_algorithm(kasp_key->key); + bool same_alg_zsk = alg_has_active_zsk(ctx, kk_alg); + set_key(kasp_key, ctx->now, &keyset.keys[i], same_alg_zsk); + if (verbose) { + log_key_info(&keyset.keys[i], key_info[i].msg, MAX_KEY_INFO); + if (knot_time_cmp(kasp_key->timing.pre_active, kasp_key->timing.publish) < 0) { + key_info[i].key_time = kasp_key->timing.pre_active; + } else { + key_info[i].key_time = kasp_key->timing.publish; + } + } + } + + // Sort the keys by publish/pre_active timestamps. + if (verbose) { + qsort(key_info, ctx->zone->num_keys, sizeof(key_info[0]), log_key_sort); + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + log_zone_info(ctx->zone->dname, "%s", key_info[i].msg); + } + } + + int ret = walk_algorithms(ctx, &keyset); + if (ret != KNOT_EOK) { + log_zone_error(ctx->zone->dname, "DNSSEC, keys validation failed (%s)", + knot_strerror(ret)); + free_zone_keys(&keyset); + return ret; + } + + ret = load_private_keys(ctx->keystore, &keyset); + ret = knot_error_from_libdnssec(ret); + if (ret != KNOT_EOK) { + log_zone_error(ctx->zone->dname, "DNSSEC, failed to load private " + "keys (%s)", knot_strerror(ret)); + free_zone_keys(&keyset); + return ret; + } + + *keyset_ptr = keyset; + + return KNOT_EOK; +} + +/*! + * \brief Free structure with zone keys and associated DNSSEC contexts. + */ +void free_zone_keys(zone_keyset_t *keyset) +{ + if (!keyset) { + return; + } + + for (size_t i = 0; i < keyset->count; i++) { + dnssec_binary_free(&keyset->keys[i].precomputed_ds); + } + + free(keyset->keys); + + memset(keyset, '\0', sizeof(*keyset)); +} + +/*! + * \brief Get timestamp of next key event. + */ +knot_time_t knot_get_next_zone_key_event(const zone_keyset_t *keyset) +{ + assert(keyset); + + knot_time_t result = 0; + + for (size_t i = 0; i < keyset->count; i++) { + zone_key_t *key = &keyset->keys[i]; + if (knot_time_cmp(key->next_event, result) < 0) { + result = key->next_event; + } + } + + return result; +} + +/*! + * \brief Compute DS record rdata from key + cache it. + */ +int zone_key_calculate_ds(zone_key_t *for_key, dnssec_key_digest_t digesttype, + dnssec_binary_t *out_donotfree) +{ + assert(for_key); + assert(out_donotfree); + + int ret = KNOT_EOK; + + if (for_key->precomputed_ds.data == NULL || for_key->precomputed_digesttype != digesttype) { + dnssec_binary_free(&for_key->precomputed_ds); + ret = dnssec_key_create_ds(for_key->key, digesttype, &for_key->precomputed_ds); + ret = knot_error_from_libdnssec(ret); + for_key->precomputed_digesttype = digesttype; + } + + *out_donotfree = for_key->precomputed_ds; + return ret; +} + +zone_sign_ctx_t *zone_sign_ctx(const zone_keyset_t *keyset, const kdnssec_ctx_t *dnssec_ctx) +{ + zone_sign_ctx_t *ctx = calloc(1, sizeof(*ctx) + keyset->count * sizeof(*ctx->sign_ctxs)); + if (ctx == NULL) { + return NULL; + } + + ctx->sign_ctxs = (dnssec_sign_ctx_t **)(ctx + 1); + ctx->count = keyset->count; + ctx->keys = keyset->keys; + ctx->dnssec_ctx = dnssec_ctx; + for (size_t i = 0; i < ctx->count; i++) { + int ret = dnssec_sign_new(&ctx->sign_ctxs[i], ctx->keys[i].key); + if (ret != DNSSEC_EOK) { + zone_sign_ctx_free(ctx); + return NULL; + } + } + + return ctx; +} + +zone_sign_ctx_t *zone_validation_ctx(const kdnssec_ctx_t *dnssec_ctx) +{ + size_t count = dnssec_ctx->zone->num_keys; + zone_sign_ctx_t *ctx = calloc(1, sizeof(*ctx) + count * sizeof(*ctx->sign_ctxs)); + if (ctx == NULL) { + return NULL; + } + + ctx->sign_ctxs = (dnssec_sign_ctx_t **)(ctx + 1); + ctx->count = count; + ctx->keys = NULL; + ctx->dnssec_ctx = dnssec_ctx; + for (size_t i = 0; i < ctx->count; i++) { + int ret = dnssec_sign_new(&ctx->sign_ctxs[i], dnssec_ctx->zone->keys[i].key); + if (ret != DNSSEC_EOK) { + zone_sign_ctx_free(ctx); + return NULL; + } + } + + return ctx; +} + +void zone_sign_ctx_free(zone_sign_ctx_t *ctx) +{ + if (ctx != NULL) { + for (size_t i = 0; i < ctx->count; i++) { + dnssec_sign_free(ctx->sign_ctxs[i]); + } + free(ctx); + } +} + +int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner, + const uint8_t *rdata, size_t rdlen) +{ + if (key == NULL || rdata == NULL || rdlen == 0) { + return KNOT_EINVAL; + } + + const dnssec_binary_t binary_key = { + .size = rdlen, + .data = (uint8_t *)rdata + }; + + dnssec_key_t *new_key = NULL; + int ret = dnssec_key_new(&new_key); + if (ret != DNSSEC_EOK) { + return knot_error_from_libdnssec(ret); + } + ret = dnssec_key_set_rdata(new_key, &binary_key); + if (ret != DNSSEC_EOK) { + dnssec_key_free(new_key); + return knot_error_from_libdnssec(ret); + } + if (owner != NULL) { + ret = dnssec_key_set_dname(new_key, owner); + if (ret != DNSSEC_EOK) { + dnssec_key_free(new_key); + return knot_error_from_libdnssec(ret); + } + } + + *key = new_key; + return KNOT_EOK; +} + +static bool soa_signed_by_key(const zone_key_t *key, const knot_rdataset_t *apex_rrsig) +{ + assert(key != NULL); + if (apex_rrsig == NULL) { + return false; + } + uint16_t keytag = dnssec_key_get_keytag(key->key); + + knot_rdata_t *rr = apex_rrsig->rdata; + for (int i = 0; i < apex_rrsig->count; i++) { + if (knot_rrsig_type_covered(rr) == KNOT_RRTYPE_SOA && + knot_rrsig_key_tag(rr) == keytag) { + return true; + } + rr = knot_rdataset_next(rr); + } + + return false; +} + +int is_soa_signed_by_all_zsks(const zone_keyset_t *keyset, + const knot_rdataset_t *apex_rrsig) +{ + if (keyset == NULL || keyset->count == 0) { + return false; + } + + for (size_t i = 0; i < keyset->count; i++) { + const zone_key_t *key = &keyset->keys[i]; + if (key->is_zsk && key->is_active && + !soa_signed_by_key(key, apex_rrsig)) { + return false; + } + } + + return true; +} |