From fa828c5da78e82eee4834d2fdd4ca6281c297251 Mon Sep 17 00:00:00 2001 From: Libor Peltan Date: Fri, 7 Jan 2022 17:06:45 +0100 Subject: [PATCH] sematic-checks: use verification routines from dnssec-verify --- src/knot/dnssec/context.c | 12 +- src/knot/zone/semantic-check.c | 763 +----------------- ...c3_delegation.signed => delegation.signed} | 2 +- tests/knot/test_semantic_check.in | 44 +- 4 files changed, 64 insertions(+), 757 deletions(-) rename tests/knot/semantic_check_data/{no_error_nsec3_delegation.signed => delegation.signed} (98%) diff --git a/src/knot/dnssec/context.c b/src/knot/dnssec/context.c index 1e045b376..e1b668e6a 100644 --- a/src/knot/dnssec/context.c +++ b/src/knot/dnssec/context.c @@ -309,10 +309,14 @@ int kdnssec_validation_ctx(conf_t *conf, kdnssec_ctx_t *ctx, const zone_contents } policy_from_zone(ctx->policy, zone); - conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->apex->owner); - conf_id_fix_default(&policy_id); - conf_val_t num_threads = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id); - ctx->policy->signing_threads = conf_int(&num_threads); + if (conf != NULL) { + conf_val_t policy_id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->apex->owner); + conf_id_fix_default(&policy_id); + conf_val_t num_threads = conf_id_get(conf, C_POLICY, C_SIGNING_THREADS, &policy_id); + ctx->policy->signing_threads = conf_int(&num_threads); + } else { + ctx->policy->signing_threads = 1; + } int ret = kasp_zone_from_contents(ctx->zone, zone, ctx->policy->single_type_signing, ctx->policy->nsec3_enabled, &ctx->policy->nsec3_iterations, diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c index a4cee7b2f..8f611566d 100644 --- a/src/knot/zone/semantic-check.c +++ b/src/knot/zone/semantic-check.c @@ -15,19 +15,16 @@ */ #include -#include -#include -#include -#include + +#include "knot/zone/semantic-check.h" #include "libdnssec/error.h" -#include "contrib/base32hex.h" +#include "libdnssec/key.h" #include "contrib/string.h" #include "libknot/libknot.h" -#include "knot/zone/semantic-check.h" -#include "knot/dnssec/rrset-sign.h" +#include "knot/dnssec/key-events.h" #include "knot/dnssec/zone-keys.h" -#include "knot/dnssec/zone-nsec.h" +#include "knot/updates/zone-update.h" static const char *error_messages[SEM_ERR_UNKNOWN + 1] = { [SEM_ERR_SOA_NONE] = @@ -153,7 +150,6 @@ typedef enum { typedef struct { zone_contents_t *zone; sem_handler_t *handler; - const zone_node_t *next_nsec; check_level_t level; time_t time; } semchecks_data_t; @@ -164,13 +160,6 @@ static int check_dname(const zone_node_t *node, semchecks_data_t *data); static int check_delegation(const zone_node_t *node, semchecks_data_t *data); static int check_submission(const zone_node_t *node, semchecks_data_t *data); static int check_ds(const zone_node_t *node, semchecks_data_t *data); -static int check_nsec(const zone_node_t *node, semchecks_data_t *data); -static int check_nsec3(const zone_node_t *node, semchecks_data_t *data); -static int check_nsec3_opt_out(const zone_node_t *node, semchecks_data_t *data); -static int check_rrsig(const zone_node_t *node, semchecks_data_t *data); -static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data); -static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data); -static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data); struct check_function { int (*function)(const zone_node_t *, semchecks_data_t *); @@ -185,247 +174,11 @@ static const struct check_function CHECK_FUNCTIONS[] = { { check_delegation, MANDATORY | SOFT }, // mandatory for apex, optional for others { check_ds, OPTIONAL }, { check_submission, NSEC | NSEC3 }, - { check_rrsig, NSEC | NSEC3 }, - { check_rrsig_signed, NSEC | NSEC3 }, - { check_nsec_bitmap, NSEC | NSEC3 }, - { check_nsec, NSEC }, - { check_nsec3, NSEC3 }, - { check_nsec3_presence, NSEC3 }, - { check_nsec3_opt_out, NSEC3 }, }; static const int CHECK_FUNCTIONS_LEN = sizeof(CHECK_FUNCTIONS) / sizeof(struct check_function); -static int check_signature(const knot_rdata_t *rrsig, const dnssec_key_t *key, - const knot_rrset_t *covered) -{ - if (!rrsig || !key || !dnssec_key_can_verify(key)) { - return KNOT_EINVAL; - } - - int ret = KNOT_EOK; - dnssec_sign_ctx_t *sign_ctx = NULL; - - dnssec_binary_t signature = { - .size = knot_rrsig_signature_len(rrsig), - .data = (uint8_t *)knot_rrsig_signature(rrsig) - }; - if (!signature.data || !signature.size) { - ret = KNOT_EINVAL; - goto fail; - } - - if (dnssec_sign_new(&sign_ctx, key) != KNOT_EOK) { - ret = KNOT_ENOMEM; - goto fail; - } - - if (knot_sign_ctx_add_data(sign_ctx, rrsig->data, covered) != KNOT_EOK) { - ret = KNOT_ENOMEM; - goto fail; - } - - if (dnssec_sign_verify(sign_ctx, false, &signature) != KNOT_EOK) { - ret = KNOT_EINVAL; - goto fail; - } - -fail: - dnssec_sign_free(sign_ctx); - return ret; -} - -/*! - * \brief Semantic check - RRSIG rdata. - * - * \param handler Pointer on function to be called in case of negative check. - * \param zone The zone the rrset is in. - * \param node The node in the zone contents. - * \param rrsig RRSIG rdata. - * \param rrset RRSet signed by the RRSIG. - * \param context The time stamp we check the rrsig validity according to. - * \param level Level of the check. - * \param verified Out: the RRSIG has been verified to be signed by existing DNSKEY. - * - * \retval KNOT_EOK on success. - * \return Appropriate error code if error was found. - */ -static int check_rrsig_rdata(sem_handler_t *handler, - const zone_contents_t *zone, - const zone_node_t *node, - const knot_rdata_t *rrsig, - const knot_rrset_t *rrset, - time_t context, - check_level_t level, - bool *verified) -{ - /* Prepare additional info string. */ - char info_str[50] = ""; - char type_str[16] = ""; - knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str)); - int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str); - if (ret < 0 || ret >= sizeof(info_str)) { - return KNOT_ENOMEM; - } - - if (knot_rrsig_type_covered(rrsig) != rrset->type) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_TYPE_COVERED, - info_str); - } - - /* label number at the 2nd index should be same as owner's */ - uint8_t labels_rdata = knot_rrsig_labels(rrsig); - - size_t tmp = knot_dname_labels(rrset->owner, NULL) - labels_rdata; - if (tmp != 0) { - /* if name has wildcard, label must not be included */ - if (!knot_dname_is_wildcard(rrset->owner)) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_LABELS, - info_str); - } else if (tmp != 1) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_LABELS, - info_str); - } - } - - /* Check original TTL. */ - uint32_t original_ttl = knot_rrsig_original_ttl(rrsig); - if (original_ttl != rrset->ttl) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_TTL, - info_str); - } - - /* Check for expired signature. */ - if (knot_rrsig_sig_expiration(rrsig) < context) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_EXPIRATION, - info_str); - } - - /* Check inception */ - if (knot_rrsig_sig_inception(rrsig) > context) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_INCEPTION, - info_str); - } - - /* Check signer name. */ - const knot_dname_t *signer = knot_rrsig_signer_name(rrsig); - if (!knot_dname_is_equal(signer, zone->apex->owner)) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_RDATA_OWNER, - info_str); - } - - /* Verify with public key - only one RRSIG of covered record needed */ - if (level & OPTIONAL && !*verified) { - const knot_rdataset_t *dnskeys = node_rdataset(zone->apex, KNOT_RRTYPE_DNSKEY); - if (dnskeys == NULL) { - return KNOT_EOK; - } - - for (int i = 0; i < dnskeys->count; i++) { - knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i); - uint16_t flags = knot_dnskey_flags(dnskey); - uint8_t proto = knot_dnskey_proto(dnskey); - /* RFC 4034 2.1.1 & 2.1.2 */ - if (flags & DNSKEY_FLAGS_ZSK && proto == 3) { - dnssec_key_t *key; - - ret = dnssec_key_from_rdata(&key, zone->apex->owner, - dnskey->data, dnskey->len); - if (ret != KNOT_EOK) { - continue; - } - - if (dnssec_key_get_keytag(key) != knot_rrsig_key_tag(rrsig)) { - dnssec_key_free(key); - continue; - } - - ret = check_signature(rrsig, key, rrset); - dnssec_key_free(key); - if (ret == KNOT_EOK) { - *verified = true; - break; - } - } - } - } - - return KNOT_EOK; -} - -static int check_rrsig_signed(const zone_node_t *node, semchecks_data_t *data) -{ - /* signed rrsig - nonsense */ - if (node_rrtype_is_signed(node, KNOT_RRTYPE_RRSIG)) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_RRSIG_SIGNED, NULL); - } - - return KNOT_EOK; -} -/*! - * \brief Semantic check - RRSet's RRSIG. - * - * \param handler Pointer on function to be called in case of negative check. - * \param zone The zone the rrset is in. - * \param node The node in the zone contents. - * \param rrset RRSet signed by the RRSIG. - * \param context The time stamp we check the rrsig validity according to. - * \param level Level of the check. - * - * \retval KNOT_EOK on success. - * \return Appropriate error code if error was found. - */ -static int check_rrsig_in_rrset(sem_handler_t *handler, - const zone_contents_t *zone, - const zone_node_t *node, - const knot_rrset_t *rrset, - time_t context, - check_level_t level) -{ - if (handler == NULL || node == NULL || rrset == NULL) { - return KNOT_EINVAL; - } - /* Prepare additional info string. */ - char info_str[50] = ""; - char type_str[16] = ""; - knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str)); - int ret = snprintf(info_str, sizeof(info_str), "(record type %s)", type_str); - if (ret < 0 || ret >= sizeof(info_str)) { - return KNOT_ENOMEM; - } - - knot_rrset_t node_rrsigs = node_rrset(node, KNOT_RRTYPE_RRSIG); - - knot_rdataset_t rrsigs; - knot_rdataset_init(&rrsigs); - ret = knot_synth_rrsig(rrset->type, &node_rrsigs.rrs, &rrsigs, NULL); - if (ret != KNOT_EOK && ret != KNOT_ENOENT) { - return ret; - } - if (ret == KNOT_ENOENT) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_NO_RRSIG, info_str); - return KNOT_EOK; - } - - bool verified = false; - knot_rdata_t *rrsig = rrsigs.rdata; - for (uint16_t i = 0; ret == KNOT_EOK && i < rrsigs.count; ++i) { - ret = check_rrsig_rdata(handler, zone, node, rrsig, rrset, - context, level, &verified); - rrsig = knot_rdataset_next(rrsig); - } - /* Only one rrsig of covered record needs to be verified by DNSKEY. */ - if (!verified) { - handler->cb(handler, zone, node->owner, SEM_ERR_RRSIG_UNVERIFIABLE, - info_str); - } - - knot_rdataset_clear(&rrsigs, NULL); - return KNOT_EOK;; -} - /*! * \brief Check if glue record for delegation is present. * @@ -647,397 +400,6 @@ static int check_ds(const zone_node_t *node, semchecks_data_t *data) return KNOT_EOK; } -/*! - * \brief Run all semantic check related to RRSIG record - * - * \param node Node to check - * \param data Semantic checks context data - */ -static int check_rrsig(const zone_node_t *node, semchecks_data_t *data) -{ - assert(node); - if (node->flags & NODE_FLAGS_NONAUTH) { - return KNOT_EOK; - } - - bool deleg = node->flags & NODE_FLAGS_DELEG; - - int ret = KNOT_EOK; - - int rrset_count = node->rrset_count; - for (int i = 0; ret == KNOT_EOK && i < rrset_count; i++) { - knot_rrset_t rrset = node_rrset_at(node, i); - if (rrset.type == KNOT_RRTYPE_RRSIG) { - continue; - } - if (deleg && rrset.type != KNOT_RRTYPE_NSEC && - rrset.type != KNOT_RRTYPE_DS ) { - continue; - } - - ret = check_rrsig_in_rrset(data->handler, data->zone, node, &rrset, - data->time, data->level); - } - return ret; -} - -/*! - * \brief Add all RR types from a node into the bitmap. - */ -static void bitmap_add_all_node_rrsets(dnssec_nsec_bitmap_t *bitmap, - const zone_node_t *node) -{ - bool deleg = node->flags & NODE_FLAGS_DELEG; - for (int i = 0; i < node->rrset_count; i++) { - knot_rrset_t rr = node_rrset_at(node, i); - if (deleg && (rr.type != KNOT_RRTYPE_NS && - rr.type != KNOT_RRTYPE_DS && - rr.type != KNOT_RRTYPE_NSEC && - rr.type != KNOT_RRTYPE_RRSIG)) { - continue; - } - dnssec_nsec_bitmap_add(bitmap, rr.type); - } -} - -static char *nsec3_info(const knot_dname_t *owner, char *out, size_t out_len) -{ - knot_dname_txt_storage_t buff; - char *str = knot_dname_to_str(buff, owner, sizeof(buff)); - if (str == NULL) { - return NULL; - } - - int ret = snprintf(out, out_len, "(NSEC3 owner=%s)", str); - if (ret <= 0 || ret >= out_len) { - return NULL; - } - - return out; -} - -/*! - * \brief Check NSEC and NSEC3 type bitmap - * - * \param node Node to check - * \param data Semantic checks context data - */ -static int check_nsec_bitmap(const zone_node_t *node, semchecks_data_t *data) -{ - assert(node); - if (node->flags & NODE_FLAGS_NONAUTH) { - return KNOT_EOK; - } - - bool nsec = data->level & NSEC; - knot_rdataset_t *nsec_rrs = NULL; - - zone_node_t *nsec3_node = node_nsec3_get(node); - - if (nsec) { - nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC); - } else if (nsec3_node != NULL) { - nsec_rrs = node_rdataset(nsec3_node, KNOT_RRTYPE_NSEC3); - } - if (nsec_rrs == NULL) { - return KNOT_EOK; - } - - // create NSEC bitmap from node - dnssec_nsec_bitmap_t *node_bitmap = dnssec_nsec_bitmap_new(); - if (node_bitmap == NULL) { - return KNOT_ENOMEM; - } - bitmap_add_all_node_rrsets(node_bitmap, node); - - uint16_t node_wire_size = dnssec_nsec_bitmap_size(node_bitmap); - uint8_t *node_wire = malloc(node_wire_size); - if (node_wire == NULL) { - dnssec_nsec_bitmap_free(node_bitmap); - return KNOT_ENOMEM; - } - dnssec_nsec_bitmap_write(node_bitmap, node_wire); - dnssec_nsec_bitmap_free(node_bitmap); - - // get NSEC bitmap from NSEC node - const uint8_t *nsec_wire = NULL; - uint16_t nsec_wire_size = 0; - if (nsec) { - nsec_wire = knot_nsec_bitmap(nsec_rrs->rdata); - nsec_wire_size = knot_nsec_bitmap_len(nsec_rrs->rdata); - } else { - nsec_wire = knot_nsec3_bitmap(nsec_rrs->rdata); - nsec_wire_size = knot_nsec3_bitmap_len(nsec_rrs->rdata); - } - - if (node_wire_size != nsec_wire_size || - memcmp(node_wire, nsec_wire, node_wire_size) != 0) { - char buff[50 + KNOT_DNAME_TXT_MAXLEN]; - char *info = nsec ? NULL : nsec3_info(nsec3_node->owner, - buff, sizeof(buff)); - data->handler->cb(data->handler, data->zone, node->owner, - (nsec ? SEM_ERR_NSEC_RDATA_BITMAP : SEM_ERR_NSEC3_RDATA_BITMAP), - info); - } - - free(node_wire); - return KNOT_EOK; -} - -/*! - * \brief Run NSEC related semantic checks - * - * \param node Node to check - * \param data Semantic checks context data - */ -static int check_nsec(const zone_node_t *node, semchecks_data_t *data) -{ - assert(node); - if (node->flags & NODE_FLAGS_NONAUTH) { - return KNOT_EOK; - } - - if (node->rrset_count == 0) { // empty nonterminal - return KNOT_EOK; - } - - /* check for NSEC record */ - const knot_rdataset_t *nsec_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC); - if (nsec_rrs == NULL) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC_NONE, NULL); - return KNOT_EOK; - } - - /* Test that only one record is in the NSEC RRSet */ - if (nsec_rrs->count != 1) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC_RDATA_MULTIPLE, NULL); - } - - if (data->next_nsec != node) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC_RDATA_CHAIN, NULL); - } - - /* - * Test that NSEC chain is coherent. - * We have already checked that every - * authoritative node contains NSEC record - * so checking should only be matter of testing - * the next link in each node. - */ - knot_dname_storage_t next_domain; - if (knot_dname_store(next_domain, knot_nsec_next(nsec_rrs->rdata)) == 0) { - return KNOT_EINVAL; - } - knot_dname_to_lower(next_domain); - - data->next_nsec = zone_contents_find_node(data->zone, next_domain); - if (data->next_nsec == NULL) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC_RDATA_CHAIN, NULL); - } - - return KNOT_EOK; -} - -static bool nsec3_optout_allow(const zone_node_t *node) -{ - return (node->flags & NODE_FLAGS_DELEG) && !node_rrtype_exists(node, KNOT_RRTYPE_DS); -} - -/*! - * \brief Check if node has NSEC3 node. - * - * \param node Node to check - * \param data Semantic checks context data - */ -static int check_nsec3_presence(const zone_node_t *node, semchecks_data_t *data) -{ - bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0; - bool empty = (node->flags & NODE_FLAGS_EMPTY) != 0; - - if (!nsec3_optout_allow(node) && auth && !empty) { - if (node_nsec3_get(node) == NULL) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_NONE, NULL); - } - } - - return KNOT_EOK; -} - -/*! - * \brief Check NSEC3 opt-out. - * - * \param node Node to check - * \param data Semantic checks context data - */ -static int check_nsec3_opt_out(const zone_node_t *node, semchecks_data_t *data) -{ - if (!(node_nsec3_get(node) == NULL && node->flags & (NODE_FLAGS_DELEG | NODE_FLAGS_EMPTY))) { - return KNOT_EOK; - } - /* Insecure delegation, check whether it is part of opt-out span. */ - - const zone_node_t *nsec3_previous = NULL; - const zone_node_t *nsec3_node; - zone_contents_find_nsec3_for_name(data->zone, node->owner, &nsec3_node, - &nsec3_previous); - - if (nsec3_previous == NULL) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_NONE, NULL); - return KNOT_EOK; - } - - const knot_rdataset_t *previous_rrs; - previous_rrs = node_rdataset(nsec3_previous, KNOT_RRTYPE_NSEC3); - assert(previous_rrs); - - /* Check for opt-out flag. */ - uint8_t flags = knot_nsec3_flags(previous_rrs->rdata); - if (!(flags & 1)) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT, NULL); - } - - return KNOT_EOK; -} - -/*! - * \brief Run checks related to NSEC3. - * - * Check NSEC3 node for given node. - * Check if NSEC3 chain is coherent and cyclic. - * \param node Node to check - * \param data Semantic checks context data - */ -static int check_nsec3(const zone_node_t *node, semchecks_data_t *data) -{ - assert(node); - bool auth = (node->flags & NODE_FLAGS_NONAUTH) == 0; - bool deleg = (node->flags & NODE_FLAGS_DELEG) != 0; - - if (!auth && !deleg) { - return KNOT_EOK; - } - - zone_node_t *nsec3_node = node_nsec3_get(node); - if (nsec3_node == NULL) { - return KNOT_EOK; - } - - dnssec_nsec3_params_t params_apex = { 0 }; - int ret = KNOT_EOK; - - char buff[50 + KNOT_DNAME_TXT_MAXLEN]; - char *info = nsec3_info(nsec3_node->owner, buff, sizeof(buff)); - - knot_rrset_t nsec3_rrs = node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3); - if (knot_rrset_empty(&nsec3_rrs)) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_NONE, info); - goto nsec3_cleanup; - } - - knot_rrset_t soa_rrset = node_rrset(data->zone->apex, KNOT_RRTYPE_SOA); - assert(!knot_rrset_empty(&soa_rrset)); - uint32_t minimum_ttl = knot_soa_minimum(soa_rrset.rrs.rdata); - if (nsec3_rrs.ttl != MIN(minimum_ttl, soa_rrset.ttl)) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_RDATA_TTL, info); - } - - // Check parameters. - const knot_rdataset_t *nsec3param = node_rdataset(data->zone->apex, - KNOT_RRTYPE_NSEC3PARAM); - dnssec_binary_t rdata = { - .size = nsec3param->rdata->len, - .data = nsec3param->rdata->data - }; - ret = dnssec_nsec3_params_from_rdata(¶ms_apex, &rdata); - if (ret != DNSSEC_EOK) { - ret = knot_error_from_libdnssec(ret); - goto nsec3_cleanup; - } - - if (knot_nsec3_flags(nsec3_rrs.rrs.rdata) > 1) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_RDATA_FLAGS, info); - } - - dnssec_binary_t salt = { - .size = knot_nsec3_salt_len(nsec3_rrs.rrs.rdata), - .data = (uint8_t *)knot_nsec3_salt(nsec3_rrs.rrs.rdata), - }; - - if (dnssec_binary_cmp(&salt, ¶ms_apex.salt)) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_RDATA_SALT, info); - } - - if (knot_nsec3_alg(nsec3_rrs.rrs.rdata) != params_apex.algorithm) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_RDATA_ALG, info); - } - - if (knot_nsec3_iters(nsec3_rrs.rrs.rdata) != params_apex.iterations) { - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_RDATA_ITERS, info); - } - - // Get next nsec3 node. - const zone_node_t *apex = data->zone->apex; - const uint8_t *next_dname_str = knot_nsec3_next(nsec3_rrs.rrs.rdata); - uint8_t next_dname_str_size = knot_nsec3_next_len(nsec3_rrs.rrs.rdata); - knot_dname_storage_t next_dname; - ret = knot_nsec3_hash_to_dname(next_dname, sizeof(next_dname), - next_dname_str, next_dname_str_size, - apex->owner); - if (ret != KNOT_EOK) { - goto nsec3_cleanup; - } - - const zone_node_t *next_nsec3 = zone_contents_find_nsec3_node(data->zone, - next_dname); - if (next_nsec3 == NULL || node_prev(next_nsec3) != nsec3_node) { - uint8_t *next = NULL; - int32_t next_len = knot_base32hex_encode_alloc(next_dname_str, - next_dname_str_size, - &next); - char *hash_info = NULL; - if (next != NULL) { - hash_info = sprintf_alloc("(next hash %.*s)", next_len, next); - free(next); - } - data->handler->cb(data->handler, data->zone, node->owner, - SEM_ERR_NSEC3_RDATA_CHAIN, hash_info); - free(hash_info); - } - - ret = check_rrsig(nsec3_node, data); - if (ret != KNOT_EOK) { - goto nsec3_cleanup; - } - - // Check that the node only contains NSEC3 and RRSIG. - for (int i = 0; ret == KNOT_EOK && i < nsec3_node->rrset_count; i++) { - knot_rrset_t rrset = node_rrset_at(nsec3_node, i); - uint16_t type = rrset.type; - if (type != KNOT_RRTYPE_NSEC3 && type != KNOT_RRTYPE_RRSIG) { - data->handler->cb(data->handler, data->zone, nsec3_node->owner, - SEM_ERR_NSEC3_EXTRA_RECORD, NULL); - } - } - -nsec3_cleanup: - dnssec_nsec3_params_free(¶ms_apex); - - return ret; -} - /*! * \brief Check if apex node contains SOA record * @@ -1134,27 +496,6 @@ static int check_dname(const zone_node_t *node, semchecks_data_t *data) return KNOT_EOK; } -/*! - * \brief Check that NSEC chain is cyclic. - * - * Run only once per zone. Check that last NSEC node points to first one. - * \param data Semantic checks context data - */ -static int check_nsec_cyclic(semchecks_data_t *data) -{ - if (data->next_nsec == NULL) { - data->handler->cb(data->handler, data->zone, data->zone->apex->owner, - SEM_ERR_NSEC_RDATA_CHAIN, NULL); - return KNOT_EOK; - } - if (!knot_dname_is_equal(data->next_nsec->owner, data->zone->apex->owner)) { - data->handler->cb(data->handler, data->zone, data->next_nsec->owner, - SEM_ERR_NSEC_RDATA_CHAIN, NULL); - } - - return KNOT_EOK; -} - /*! * \brief Call all semantic checks for each node. * @@ -1203,59 +544,37 @@ static void check_nsec3param(knot_rdataset_t *nsec3param, zone_contents_t *zone, } } -static void check_dnskey(zone_contents_t *zone, sem_handler_t *handler) +static sem_error_t err_dnssec2sem(int err) { - const knot_rdataset_t *dnskeys = node_rdataset(zone->apex, KNOT_RRTYPE_DNSKEY); - if (dnskeys == NULL) { - handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_NONE, NULL); - return; - } - - for (int i = 0; i < dnskeys->count; i++) { - knot_rdata_t *dnskey = knot_rdataset_at(dnskeys, i); - dnssec_key_t *key; - int ret = dnssec_key_from_rdata(&key, zone->apex->owner, - dnskey->data, dnskey->len); - if (ret == KNOT_EOK) { - dnssec_key_free(key); - } else { - handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_INVALID, NULL); - } - - if (knot_dnskey_proto(dnskey) != 3) { - handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_RDATA_PROTOCOL, - NULL); - } - - dnssec_key_algorithm_t alg = knot_dnskey_alg(dnskey); - if (!dnssec_algorithm_key_support(alg)) { - char *info = sprintf_alloc("(unsupported algorithm %d)", alg); - handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_INVALID, info); - free(info); - } + switch (err) { + case KNOT_DNSSEC_ENOSIG: + return SEM_ERR_RRSIG_UNVERIFIABLE; + case KNOT_DNSSEC_ENSEC_BITMAP: + return SEM_ERR_NSEC_RDATA_BITMAP; + case KNOT_DNSSEC_ENSEC_CHAIN: + return SEM_ERR_NSEC_RDATA_CHAIN; + case KNOT_DNSSEC_ENSEC3_OPTOUT: + return SEM_ERR_NSEC3_INSECURE_DELEGATION_OPT; + default: + return SEM_ERR_UNKNOWN; } } -static int mark_nsec3_optout(zone_node_t *node, _unused_ void *ctx) +static int verify_dnssec(zone_contents_t *zone, sem_handler_t *handler, time_t time) { - if (nsec3_optout_allow(node) && node_nsec3_get(node) == NULL) { - do { - assert(!(node->flags & NODE_FLAGS_APEX)); - node->flags |= NODE_FLAGS_EMPTY; - node = node_parent(node); - node->children--; - } while (node->rrset_count == 0 && node->children == 0 && node_nsec3_get(node) == NULL); + zone_update_t fake_up = { .new_cont = zone, }; + int ret = knot_dnssec_validate_zone(&fake_up, NULL, time, false); + if (fake_up.validation_hint.node != NULL) { // validation found an issue + char type_str[16] = { 0 }; + knot_rrtype_to_string(fake_up.validation_hint.rrtype, type_str, sizeof(type_str)); + handler->cb(handler, zone, fake_up.validation_hint.node, err_dnssec2sem(ret), type_str); + return KNOT_EOK; + } else if (ret == KNOT_INVALID_PUBLIC_KEY) { // validation failed due to invalid DNSKEY + handler->cb(handler, zone, zone->apex->owner, SEM_ERR_DNSKEY_INVALID, NULL); + return KNOT_EOK; + } else { // validation failed by itself + return ret; } - return KNOT_EOK; -} - -static int unmark_nsec3_optout(zone_node_t *node, _unused_ void *ctx) -{ - if (node->flags & NODE_FLAGS_EMPTY) { - node->flags &= ~NODE_FLAGS_EMPTY; - node_parent(node)->children++; - } - return KNOT_EOK; } int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_handler_t *handler, @@ -1272,7 +591,6 @@ int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_ semchecks_data_t data = { .handler = handler, .zone = zone, - .next_nsec = zone->apex, .level = MANDATORY, .time = time, }; @@ -1293,20 +611,10 @@ int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_ } else { data.level |= NSEC; } - check_dnskey(zone, handler); } } - if (data.level & NSEC3) { - int ret = zone_tree_apply(zone->nodes, mark_nsec3_optout, NULL); - if (ret != KNOT_EOK) { - return ret; - } - } int ret = zone_contents_apply(zone, do_checks_in_tree, &data); - if (data.level & NSEC3) { - (void)zone_tree_apply(zone->nodes, unmark_nsec3_optout, NULL); - } if (ret != KNOT_EOK) { return ret; } @@ -1314,13 +622,10 @@ int sem_checks_process(zone_contents_t *zone, semcheck_optional_t optional, sem_ return KNOT_ESEMCHECK; } - // check cyclic chain after every node was checked - if (data.level & NSEC) { - check_nsec_cyclic(&data); - } - if (data.handler->fatal_error) { - return KNOT_ESEMCHECK; + if (optional == SEMCHECK_DNSSEC_ON || + (optional == SEMCHECK_DNSSEC_AUTO && zone->dnssec)) { + ret = verify_dnssec(zone, handler, time); } - return KNOT_EOK; + return ret; } diff --git a/tests/knot/semantic_check_data/no_error_nsec3_delegation.signed b/tests/knot/semantic_check_data/delegation.signed similarity index 98% rename from tests/knot/semantic_check_data/no_error_nsec3_delegation.signed rename to tests/knot/semantic_check_data/delegation.signed index 4c85a4b3a..200721679 100644 --- a/tests/knot/semantic_check_data/no_error_nsec3_delegation.signed +++ b/tests/knot/semantic_check_data/delegation.signed @@ -1,4 +1,4 @@ -; Zone without any semantic error +; Delegation NS and glue signed despite mustn't. example.com. 3600 IN SOA dns1.example.com. hostmaster.example.com. ( 2010111220 ; serial diff --git a/tests/knot/test_semantic_check.in b/tests/knot/test_semantic_check.in index 3cc054238..614446ab9 100644 --- a/tests/knot/test_semantic_check.in +++ b/tests/knot/test_semantic_check.in @@ -95,34 +95,31 @@ expect_error "glue_apex_one.missing" 0 1 "$NS_GLUE" expect_error "glue_besides.missing" 0 1 "$NS_GLUE" expect_error "glue_deleg.missing" 0 1 "$NS_GLUE" expect_error "glue_in_apex.missing" 0 1 "$NS_GLUE" -expect_error "different_signer_name.signed" 0 1 "$RRSIG_RDATA_DNSKEY_OWNER \(record type NSEC\)" -expect_error "different_signer_name.signed" 0 1 "$RRSIG_UNVERIFIABLE \(record type NSEC\)" -expect_error "no_rrsig.signed" 0 1 "$RRSIG_NO_RRSIG \(record type A\)" -expect_error "no_rrsig.signed" 0 1 "$RRSIG_NO_RRSIG \(record type NSEC\)" -expect_error "no_rrsig_with_delegation.signed" 0 1 "$RRSIG_NO_RRSIG \(record type NSEC\)" +expect_error "different_signer_name.signed" 0 1 "$RRSIG_UNVERIFIABLE" +expect_error "no_rrsig.signed" 0 1 "$RRSIG_UNVERIFIABLE" +expect_error "no_rrsig_with_delegation.signed" 0 1 "$RRSIG_UNVERIFIABLE" expect_error "nsec_broken_chain_01.signed" 0 1 "$NSEC_RDATA_CHAIN" expect_error "nsec_broken_chain_02.signed" 0 1 "$NSEC_RDATA_CHAIN" -expect_error "nsec_missing.signed" 0 1 "$NSEC_NONE" -expect_error "nsec_multiple.signed" 0 1 "$NSEC_RDATA_MULTIPLE" +expect_error "nsec_missing.signed" 0 1 "$NSEC_RDATA_BITMAP" +expect_error "nsec_multiple.signed" 0 1 "$NSEC_RDATA_BITMAP" expect_error "nsec_wrong_bitmap_01.signed" 0 1 "$NSEC_RDATA_BITMAP" expect_error "nsec_wrong_bitmap_02.signed" 0 1 "$NSEC_RDATA_BITMAP" -expect_error "nsec3_missing.signed" 0 1 "$NSEC3_NONE" -expect_error "nsec3_optout_ent.invalid" 0 1 "$NSEC3_NONE" -expect_error "nsec3_wrong_bitmap_01.signed" 0 1 "$NSEC3_RDATA_BITMAP" -expect_error "nsec3_wrong_bitmap_02.signed" 0 1 "$NSEC3_RDATA_BITMAP" -expect_error "nsec3_ds.signed" 0 1 "$NSEC3_NONE" +expect_error "nsec3_missing.signed" 0 1 "$NSEC_RDATA_BITMAP" +expect_error "nsec3_optout_ent.invalid" 0 1 "$NSEC_RDATA_BITMAP" +expect_error "nsec3_wrong_bitmap_01.signed" 0 1 "$NSEC_RDATA_BITMAP" +expect_error "nsec3_wrong_bitmap_02.signed" 0 1 "$NSEC_RDATA_BITMAP" +expect_error "nsec3_ds.signed" 0 1 "$NSEC_RDATA_BITMAP" expect_error "nsec3_optout.signed" 0 1 "$NSEC3_INSECURE_DELEGATION_OPT" -expect_error "nsec3_chain_01.signed" 0 1 "$NSEC3_RDATA_CHAIN" -expect_error "nsec3_chain_02.signed" 0 2 "$NSEC3_RDATA_CHAIN" -expect_error "nsec3_chain_03.signed" 0 2 "$NSEC3_RDATA_CHAIN" -expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC3_ALG" -expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC3_ITERS" +expect_error "nsec3_chain_01.signed" 0 1 "$NSEC_RDATA_CHAIN" +expect_error "nsec3_chain_02.signed" 0 1 "$NSEC_RDATA_CHAIN" +expect_error "nsec3_chain_03.signed" 0 1 "$NSEC_RDATA_CHAIN" +expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC_RDATA_BITMAP" expect_error "nsec3_param_invalid.signed" 0 1 "$NSEC3PARAM_FLAGS" -expect_error "rrsig_signed.signed" 0 1 "$RRSIG_SIGNED" -expect_error "rrsig_rdata_ttl.signed" 0 1 "$RRSIG_RDATA_TTL \(record type A\)" -expect_error "duplicate.signature" 0 7 "$RRSIG_EXPIRED" -expect_error "missing.signed" 0 1 "$NSEC_NONE" -expect_error "dnskey_param_error.signed" 0 1 "$DNSKEY_PROTO" +expect_error "rrsig_signed.signed" 0 1 "$RRSIG_UNVERIFIABLE" +expect_error "rrsig_rdata_ttl.signed" 0 1 "$RRSIG_UNVERIFIABLE" +expect_error "duplicate.signature" 0 1 "$RRSIG_UNVERIFIABLE" +expect_error "missing.signed" 0 1 "$NSEC_RDATA_BITMAP" +expect_error "dnskey_param_error.signed" 0 1 "$RRSIG_UNVERIFIABLE" # FIXME this shall rather fail on invalid pubkey expect_error "invalid_ds.signed" 0 2 "$DS_ALG \(keytag 60485\)" expect_error "cdnskey.invalid" 0 1 "$CDS_NOT_MATCH" expect_error "cdnskey.invalid.param" 0 1 "$CDS_NOT_MATCH" @@ -133,10 +130,10 @@ expect_error "cdnskey.orphan.cds" 0 1 "$CDS_NOT_MATCH" expect_error "cdnskey.orphan.cdnskey" 0 1 "$CDNSKEY_NO_CDS" expect_error "cdnskey.delete.invalid.cds" 0 1 "$CDNSKEY_DELETE" expect_error "cdnskey.delete.invalid.cdnskey" 0 1 "$CDNSKEY_DELETE" +expect_error "delegation.signed" 0 1 "$NSEC_RDATA_BITMAP" test_correct "rrsig_ttl.signed" test_correct "no_error_delegation_bitmap.signed" -test_correct "no_error_nsec3_delegation.signed" test_correct "no_error_nsec3_optout.signed" test_correct "glue_wildcard.valid" test_correct "glue_no_foreign.valid" @@ -178,5 +175,6 @@ test_correct_no_dnssec "cdnskey.orphan.cds" test_correct_no_dnssec "cdnskey.orphan.cdnskey" test_correct_no_dnssec "cdnskey.delete.invalid.cds" test_correct_no_dnssec "cdnskey.delete.invalid.cdnskey" +test_correct_no_dnssec "delegation.signed" rm $LOG