From a24ef2960317422db53da8af89c0daa3bb9564eb Mon Sep 17 00:00:00 2001 From: Jan Kadlec <jan.kadlec@nic.cz> Date: Mon, 9 Dec 2013 18:28:25 +0100 Subject: [PATCH] DNSSEC/ chain fix: Initial NSEC3 chain fixing. --- src/libknot/dnssec/zone-nsec.c | 366 ++++++++++++++++++++++++++++++--- src/libknot/dnssec/zone-sign.c | 1 + src/libknot/dnssec/zone-sign.h | 1 + 3 files changed, 341 insertions(+), 27 deletions(-) diff --git a/src/libknot/dnssec/zone-nsec.c b/src/libknot/dnssec/zone-nsec.c index 8d79bff7dd..0a7f9c1724 100644 --- a/src/libknot/dnssec/zone-nsec.c +++ b/src/libknot/dnssec/zone-nsec.c @@ -50,6 +50,9 @@ typedef struct { typedef int (*chain_iterate_cb)(knot_node_t *, knot_node_t *, void *); typedef int (*chain_iterate_nsec_cb)(knot_dname_t *, knot_dname_t *, void *); +typedef int (*chain_iterate_nsec3_cb)(knot_dname_t *, knot_dname_t *, + knot_dname_t *, knot_dname_t *, + void *); /*! * \brief Call a function for each piece of the chain formed by sorted nodes. @@ -168,6 +171,57 @@ static int chain_iterate_nsec(hattrie_t *nodes, chain_iterate_nsec_cb callback, callback(NULL, current, data); } +static int chain_iterate_nsec3(hattrie_t *nodes, chain_iterate_nsec3_cb callback, + void *data) +{ + assert(nodes); + assert(callback); + + bool sorted = true; + hattrie_iter_t *it = hattrie_iter_begin(nodes, sorted); + + if (!it) { + return KNOT_ENOMEM; + } + + if (hattrie_iter_finished(it)) { + hattrie_iter_free(it); + return KNOT_EINVAL; + } + + knot_dname_t *previous_original = NULL; + knot_dname_t *previous_hashed = NULL; + knot_dname_t *current_original = NULL; + knot_dname_t *current_hashed = NULL; + + int result = KNOT_EOK; + while (!hattrie_iter_finished(it)) { + signed_info_t *val = (signed_info_t *)(*hattrie_iter_val(it)); + current_original = val->dname; + current_hashed = val->hashed_dname; + + result = callback(previous_original, previous_hashed, + current_original, current_hashed, data); + if (result == NSEC_NODE_SKIP) { + // No NSEC should be created for 'current' node, skip + ; + } else if (result == KNOT_EOK) { + previous_original = current_original; + previous_hashed = current_hashed; + } else { + hattrie_iter_free(it); + return result; + } + hattrie_iter_next(it); + } + + hattrie_iter_free(it); + + return result == NSEC_NODE_SKIP && previous_original ? + callback(NULL, NULL, previous_original, previous_hashed, data) : + callback(NULL, NULL, current_original, current_hashed, data); +} + /*! * \brief Add entry for removed NSEC to the changeset. * @@ -480,7 +534,8 @@ static size_t nsec3_rdata_size(const knot_nsec3_params_t *params, * \note Content of next hash field is not changed. */ static void nsec3_fill_rdata(uint8_t *rdata, const knot_nsec3_params_t *params, - const bitmap_t *rr_types, uint32_t ttl) + const bitmap_t *rr_types, + const uint8_t *next_hashed, uint32_t ttl) { assert(rdata); assert(params); @@ -501,6 +556,9 @@ static void nsec3_fill_rdata(uint8_t *rdata, const knot_nsec3_params_t *params, *rdata = hash_length; // hash length rdata += 1; /*memset(rdata, '\0', hash_len);*/ // hash (unknown) + if (next_hashed) { + memcpy(rdata, next_hashed, hash_length); + } rdata += hash_length; bitmap_write(rr_types, rdata); // RR types bit map } @@ -511,6 +569,7 @@ static void nsec3_fill_rdata(uint8_t *rdata, const knot_nsec3_params_t *params, static knot_rrset_t *create_nsec3_rrset(knot_dname_t *owner, const knot_nsec3_params_t *params, const bitmap_t *rr_types, + const uint8_t *next_hashed, uint32_t ttl) { assert(owner); @@ -530,7 +589,7 @@ static knot_rrset_t *create_nsec3_rrset(knot_dname_t *owner, return NULL; } - nsec3_fill_rdata(rdata, params, rr_types, ttl); + nsec3_fill_rdata(rdata, params, rr_types, next_hashed, ttl); return rrset; } @@ -556,7 +615,8 @@ static knot_node_t *create_nsec3_node(knot_dname_t *owner, } knot_rrset_t *nsec3_rrset; - nsec3_rrset = create_nsec3_rrset(owner, nsec3_params, rr_types, ttl); + nsec3_rrset = create_nsec3_rrset(owner, nsec3_params, rr_types, NULL, + ttl); if (!nsec3_rrset) { knot_node_free(&new_node); return NULL; @@ -588,14 +648,16 @@ static int connect_nsec3_nodes(knot_node_t *a, knot_node_t *b, void *data) assert(a->rrset_count == 1); - uint8_t algorithm = knot_rdata_nsec3_algorithm(a->rrset_tree[0], 0); + knot_rrset_t *a_rrset = knot_node_get_rrset(a, KNOT_RRTYPE_NSEC3); + assert(a_rrset); + uint8_t algorithm = knot_rdata_nsec3_algorithm(a_rrset, 0); if (algorithm == 0) { return KNOT_EINVAL; } uint8_t *raw_hash = NULL; uint8_t raw_length = 0; - knot_rdata_nsec3_next_hashed(a->rrset_tree[0], 0, &raw_hash, &raw_length); + knot_rdata_nsec3_next_hashed(a_rrset, 0, &raw_hash, &raw_length); if (raw_hash == NULL) { return KNOT_EINVAL; } @@ -952,6 +1014,7 @@ static int update_changes_with_non_terminals(const knot_zone_contents_t *zone, // Reinsert ahtable values into trie (dname already converted) hhash_iter_t ith = { '\0' }; hhash_iter_begin(nterminal_t, &ith, sort); + printf("FIX: NSEC with nonterminals:\n"); for (; !hhash_iter_finished(&ith); hhash_iter_next(&ith)) { // Store keys from table directly to trie uint16_t key_size = 0; @@ -971,6 +1034,7 @@ static int update_changes_with_non_terminals(const knot_zone_contents_t *zone, hhash_free(nterminal_t); return KNOT_ENOMEM; } + printf("FIX: %s\n", knot_dname_to_str(info->dname)); *hattrie_get(sorted_changes, k, key_size) = info; } @@ -996,15 +1060,25 @@ static int create_nsec3_hashes_from_trie(const hattrie_t *sorted_changes, return KNOT_ERROR; } + printf("FIX: Created NSEC3:\n"); for (; !hattrie_iter_finished(itt); hattrie_iter_next(itt)) { + signed_info_t *val = (signed_info_t *)(*hattrie_iter_val(itt)); + knot_dname_t *original_dname = val->dname; knot_dname_t *nsec3_name = - create_nsec3_owner((knot_dname_t *)*hattrie_iter_val(itt), + create_nsec3_owner(original_dname, zone->apex->owner, &zone->nsec3_params); if (nsec3_name == NULL) { return KNOT_ERROR; } - hattrie_insert_dname(*out, nsec3_name); + val->hashed_dname = nsec3_name; + + // Convert NSEC3 hash to sortable + uint8_t lf[KNOT_DNAME_MAXLEN]; + knot_dname_lf(lf, nsec3_name, NULL); + // Store into new trie + printf("FIX: %s\n", knot_dname_to_str(nsec3_name)); + *hattrie_get(*out, (char *)lf+1, *lf) = val; } hattrie_iter_free(itt); return KNOT_EOK; @@ -1080,20 +1154,113 @@ static int update_nsec(const knot_node_t *from, const knot_node_t *to, return KNOT_EOK; } +static int update_nsec3(const knot_dname_t *from, const knot_dname_t *to, + const knot_node_t *covered_node, + knot_changeset_t *out_ch, + const knot_zone_contents_t *zone, uint32_t soa_min) +{ + assert(from && to && out_ch && zone); + // Get old NSEC3 RR (there might not be any) + const knot_node_t *from_node = knot_zone_contents_find_nsec3_node(zone, + from); + const knot_rrset_t *old_nsec3 = from_node ? + knot_node_rrset(from_node, + KNOT_RRTYPE_NSEC3) : NULL; + + // Create new NSEC3 - start with binary next hashed name + uint8_t *b32_hash = (uint8_t *)knot_dname_to_str(to); + assert(zone->nsec3_params.algorithm != 0); + size_t b32_length = + knot_nsec3_hash_b32_length(zone->nsec3_params.algorithm); + if (b32_hash == NULL) { + return KNOT_ENOMEM; + } + uint8_t *binary_next = NULL; + int32_t written = base32hex_decode_alloc(b32_hash, b32_length, + &binary_next); + free(b32_hash); + if (written < 0) { + return written; + } + + knot_rrset_t *gen_nsec3 = NULL; + // Create or reuse + if (covered_node) { + // Use bitmap from given node + bitmap_t bm = { '\0' }; + bitmap_add_node_rrsets(&bm, covered_node); + // Create owner + knot_dname_t *owner = knot_dname_copy(from); + if (owner == NULL) { + return KNOT_ENOMEM; + } + + // Create the RRSet + gen_nsec3 = create_nsec3_rrset(owner, &zone->nsec3_params, + &bm, binary_next, soa_min); + if (gen_nsec3 == NULL) { + knot_dname_free(&owner); + return KNOT_ERROR; + } + + } else { + assert(old_nsec3); + // Reuse bitmap and data from old NSEC3 + int ret = knot_rrset_deep_copy_no_sig(old_nsec3, &gen_nsec3); + if (ret != KNOT_EOK) { + return ret; + } + uint8_t *next_hashed = NULL; + uint8_t next_hashed_size; + knot_rdata_nsec3_next_hashed(gen_nsec3, 0, &next_hashed, + &next_hashed_size); + assert(next_hashed); + // TODO beware of algo change! + memcpy(next_hashed, binary_next, next_hashed_size); + } + + if (old_nsec3 && knot_rrset_equal(old_nsec3, gen_nsec3, + KNOT_RRSET_COMPARE_WHOLE)) { + // Nothing to update + knot_rrset_deep_free(&gen_nsec3, 1); + return KNOT_EOK; + } else { + // Drop old + int ret = changeset_remove_nsec(old_nsec3, out_ch); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&gen_nsec3, 1); + return ret; + } + + // Add new + ret = knot_changeset_add_rrset(out_ch, gen_nsec3, + KNOT_CHANGESET_ADD); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&gen_nsec3, 1); + return ret; + } + } + + return KNOT_EOK; +} + typedef struct chain_fix_data { const knot_zone_contents_t *zone; knot_changeset_t *out_ch; const knot_zone_keys_t *zone_keys; const knot_dnssec_policy_t *policy; const knot_dname_t *next_dname; + const hattrie_t *sorted_changes; } chain_fix_data_t; static const knot_node_t *find_prev_nsec_node(const knot_zone_contents_t *z, - const knot_node_t *n) + const knot_dname_t *d) { - // Find previous node for the node - bool nsec_node_found = false; - const knot_node_t *prev_zone_node = n; + // Find previous node for the dname, return node that will be used later + const knot_node_t *prev_zone_node = knot_zone_contents_find_previous(z, + d); + bool nsec_node_found = !knot_node_is_non_auth(prev_zone_node) && + !only_nsec_in_node(prev_zone_node); while (!nsec_node_found) { // Get previous node from zone tree prev_zone_node = @@ -1101,7 +1268,7 @@ static const knot_node_t *find_prev_nsec_node(const knot_zone_contents_t *z, prev_zone_node->owner); assert(prev_zone_node); // Sanity check - if (knot_dname_is_equal(n->owner, prev_zone_node->owner)) { + if (knot_dname_is_equal(d, prev_zone_node->owner)) { if (z->apex == prev_zone_node) { // Only apex in zone and changed in DDNS assert(hattrie_size(z->nodes) == 1); @@ -1121,7 +1288,50 @@ static const knot_node_t *find_prev_nsec_node(const knot_zone_contents_t *z, return prev_zone_node; } -static int handle_removed_node(const knot_node_t *node, +static bool covered_node_deleted(const knot_zone_contents_t *z, + const knot_dname_t *d_hashed, + const hattrie_t *sorted_changes) +{ + uint8_t lf[KNOT_DNAME_MAXLEN]; + knot_dname_lf(lf, d_hashed, NULL); + value_t *val = hattrie_tryget((hattrie_t *)sorted_changes, + (char *)lf+1, *lf); + if (val == NULL) { + return false; + } else { + signed_info_t *info = (signed_info_t *)(*val); + assert(knot_dname_is_equal(info->hashed_dname, d_hashed)); + // Get normal node + const knot_node_t *normal_node = + knot_zone_contents_find_node(z, info->dname); + return normal_node == NULL; + } +} + +static const knot_node_t *find_prev_nsec3_node(const knot_zone_contents_t *z, + const knot_dname_t *d_hashed, + const hattrie_t *sorted_changes) +{ + // Find previous node for the node + const knot_node_t *prev_nsec3_node = + knot_zone_contents_find_previous_nsec3(z, d_hashed); + assert(prev_nsec3_node); + bool prev_nsec3_found = !covered_node_deleted(z, + prev_nsec3_node->owner, + sorted_changes); + while (!prev_nsec3_found) { + prev_nsec3_node = + knot_zone_contents_find_previous_nsec3(z, + prev_nsec3_node->owner); + assert(prev_nsec3_node); + prev_nsec3_found = !covered_node_deleted(z, + prev_nsec3_node->owner, + sorted_changes); + } + return prev_nsec3_node; +} + +static int handle_deleted_node(const knot_node_t *node, const knot_node_t *prev_zone_node, chain_fix_data_t *fix_data) { @@ -1131,18 +1341,17 @@ static int handle_removed_node(const knot_node_t *node, if (ret != KNOT_EOK) { return ret; } - // Fix if we have next dname, store it otherwise - if (fix_data->next_dname) { - const knot_node_t *next_node = - knot_zone_contents_find_node(fix_data->zone, - fix_data->next_dname); - fix_data->next_dname = NULL; - return update_nsec(prev_zone_node, next_node, fix_data->out_ch, - 3600, - prev_zone_node == fix_data->zone->apex); - } else { + + /* + * This node should be ignored, but we might need the next dname from + * previous node. + */ + if (!fix_data->next_dname) { fix_data->next_dname = knot_rdata_nsec_next(old_nsec); + } else { + assert(0); } + return NSEC_NODE_SKIP; } @@ -1164,7 +1373,7 @@ static int fix_nsec_chain(knot_dname_t *a, knot_dname_t *b, void *d) // Find previous node in zone const knot_node_t *prev_zone_node = find_prev_nsec_node(fix_data->zone, - b_node); + b); if (prev_zone_node == NULL) { return KNOT_ERROR; } @@ -1172,7 +1381,7 @@ static int fix_nsec_chain(knot_dname_t *a, knot_dname_t *b, void *d) // Handle removals bool node_deleted = only_nsec_in_node(b_node); if (node_deleted) { - return handle_removed_node(b_node, prev_zone_node, fix_data); + return handle_deleted_node(b_node, prev_zone_node, fix_data); } // Find out whether the previous node is also part of the changeset. @@ -1224,6 +1433,107 @@ static int fix_nsec_chain(knot_dname_t *a, knot_dname_t *b, void *d) return KNOT_EOK; } +static const knot_dname_t *next_dname_from_nsec3_rrset(const knot_rrset_t *rr, + const knot_dname_t *zone_apex) +{ + uint8_t *next_hashed = NULL; + uint8_t hashed_size = 0; + knot_rdata_nsec3_next_hashed(rr, 0, &next_hashed, &hashed_size); + uint8_t catted_hash[hashed_size + knot_dname_size(zone_apex)]; + memcpy(catted_hash, next_hashed, hashed_size); + memcpy(catted_hash + hashed_size, zone_apex, knot_dname_size(zone_apex)); + assert(knot_dname_wire_check(catted_hash, + catted_hash + hashed_size + + knot_dname_size(zone_apex), NULL)); + knot_dname_t *next_dname = knot_dname_copy(catted_hash); + return next_dname; +} + +static int fix_nsec3_chain(knot_dname_t *a, knot_dname_t *a_hash, + knot_dname_t *b, knot_dname_t *b_hash, + void *d) +{ + assert(b && b_hash); + chain_fix_data_t *fix_data = (chain_fix_data_t *)d; + assert(fix_data); + // Get changed nodes from zone + const knot_node_t *a_node = knot_zone_contents_find_node(fix_data->zone, + a); + const knot_node_t *b_node = knot_zone_contents_find_node(fix_data->zone, + b); + const knot_node_t *b_nsec3_node = + knot_zone_contents_find_nsec3_node(fix_data->zone, b_hash); + // Find previous node in zone + const knot_node_t *prev_nsec3_node = + find_prev_nsec3_node(fix_data->zone, b_hash, + fix_data->sorted_changes); + if (prev_nsec3_node == NULL) { + return KNOT_ERROR; + } + // Handle removals + bool node_deleted = b_node == NULL; + if (node_deleted) { + assert(b_nsec3_node); + return handle_deleted_node(b_nsec3_node, + prev_nsec3_node, fix_data); + } + + // Find out whether the previous node is also part of the changeset. + bool dname_equal = + a ? knot_dname_is_equal(prev_nsec3_node->owner, a_hash) : false; + if (dname_equal || knot_dname_cmp(a_hash, prev_nsec3_node->owner) > 0) { + // No valid data for the previous node, create the forward NSEC + return update_nsec3(a_hash, b_hash, a_node, fix_data->out_ch, + fix_data->zone, 3600); + } else { + bool next_dname_equal = fix_data->next_dname && + knot_dname_is_equal(fix_data->next_dname, b_hash); + bool use_next = fix_data->next_dname && !next_dname_equal; + if (use_next) { + const knot_node_t *next_node = + knot_zone_contents_find_nsec3_node(fix_data->zone, + fix_data->next_dname); + int ret = update_nsec3(a_hash ? a_hash : b_hash, + next_node->owner, a_node, + fix_data->out_ch, + fix_data->zone, 3600); + fix_data->next_dname = NULL; + return ret; + } + + const knot_rrset_t *nsec3_rrset = NULL; + const knot_node_t *next_node = NULL; + if (next_dname_equal || node_deleted) { + // Updating node, extract next + nsec3_rrset = knot_node_rrset(b_nsec3_node, KNOT_RRTYPE_NSEC3); + assert(nsec3_rrset); + prev_nsec3_node = b_nsec3_node; + const knot_dname_t *next_dname = next_dname_from_nsec3_rrset(nsec3_rrset, + fix_data->zone->apex->owner); + if (next_dname == NULL) { + return KNOT_ENOMEM; + } + next_node = knot_zone_contents_find_nsec3_node(fix_data->zone, + next_dname); + } else { + // Previous node was not changed in DDNS, it has to have NSEC + nsec3_rrset = knot_node_rrset(prev_nsec3_node, + KNOT_RRTYPE_NSEC3); + next_node = b_node; + assert(nsec3_rrset); + } + + // Store next node for next iterations + fix_data->next_dname = next_dname_from_nsec3_rrset(nsec3_rrset, + fix_data->zone->apex->owner); + // Fix NSEC + return update_nsec3(prev_nsec3_node->owner, next_node->owner, + NULL, fix_data->out_ch, fix_data->zone, 3600); + } + + return KNOT_EOK; +} + /* - public API ------------------------------------------------------------ */ /*! @@ -1374,9 +1684,11 @@ int knot_zone_fix_chain(const knot_zone_contents_t *zone, ret = create_nsec3_hashes_from_trie(sorted_changes, zone, &nsec3_names); - assert(0); -// ret = chain_iterate(nsec3_names, fix_nsec3_chain, &fix_data); + // TODO finalize + ret = chain_iterate_nsec3(nsec3_names, fix_nsec3_chain, + &fix_data); } else { + // TODO finalize ret = chain_iterate_nsec(sorted_changes, fix_nsec_chain, &fix_data); } diff --git a/src/libknot/dnssec/zone-sign.c b/src/libknot/dnssec/zone-sign.c index 6bb778fe87..2da525fb3e 100644 --- a/src/libknot/dnssec/zone-sign.c +++ b/src/libknot/dnssec/zone-sign.c @@ -1102,6 +1102,7 @@ static int free_helper_trie_node(value_t *val, void *d) } free(info->type_list); knot_dname_free(&info->dname); + knot_dname_free(&info->hashed_dname); free(info); return KNOT_EOK; } diff --git a/src/libknot/dnssec/zone-sign.h b/src/libknot/dnssec/zone-sign.h index 3a508b5a0d..ae1c14b0db 100644 --- a/src/libknot/dnssec/zone-sign.h +++ b/src/libknot/dnssec/zone-sign.h @@ -41,6 +41,7 @@ typedef struct type_node { typedef struct signed_info { knot_dname_t *dname; + knot_dname_t *hashed_dname; list_t *type_list; } signed_info_t; -- GitLab