diff options
Diffstat (limited to 'src/knot/dnssec/nsec-chain.c')
-rw-r--r-- | src/knot/dnssec/nsec-chain.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/src/knot/dnssec/nsec-chain.c b/src/knot/dnssec/nsec-chain.c new file mode 100644 index 0000000..dddb8b2 --- /dev/null +++ b/src/knot/dnssec/nsec-chain.c @@ -0,0 +1,444 @@ +/* Copyright (C) 2018 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "knot/dnssec/nsec-chain.h" +#include "knot/dnssec/rrset-sign.h" +#include "knot/dnssec/zone-nsec.h" +#include "knot/dnssec/zone-sign.h" + +/* - NSEC chain construction ------------------------------------------------ */ + +/*! + * \brief Create NSEC RR set. + * + * \param rrset RRSet to be initialized. + * \param from Node that should contain the new RRSet. + * \param to Node that should be pointed to from 'from'. + * \param ttl Record TTL (SOA's minimum TTL). + * + * \return Error code, KNOT_EOK if successful. + */ +static int create_nsec_rrset(knot_rrset_t *rrset, const zone_node_t *from, + const zone_node_t *to, uint32_t ttl) +{ + assert(from); + assert(to); + knot_rrset_init(rrset, from->owner, KNOT_RRTYPE_NSEC, KNOT_CLASS_IN, ttl); + + // Create bitmap + dnssec_nsec_bitmap_t *rr_types = dnssec_nsec_bitmap_new(); + if (!rr_types) { + return KNOT_ENOMEM; + } + + bitmap_add_node_rrsets(rr_types, KNOT_RRTYPE_NSEC, from); + dnssec_nsec_bitmap_add(rr_types, KNOT_RRTYPE_NSEC); + dnssec_nsec_bitmap_add(rr_types, KNOT_RRTYPE_RRSIG); + + // Create RDATA + assert(to->owner); + size_t next_owner_size = knot_dname_size(to->owner); + size_t rdata_size = next_owner_size + dnssec_nsec_bitmap_size(rr_types); + uint8_t rdata[rdata_size]; + + // Fill RDATA + memcpy(rdata, to->owner, next_owner_size); + dnssec_nsec_bitmap_write(rr_types, rdata + next_owner_size); + dnssec_nsec_bitmap_free(rr_types); + + return knot_rrset_add_rdata(rrset, rdata, rdata_size, NULL); +} + +/*! + * \brief Connect two nodes by adding a NSEC RR into the first node. + * + * Callback function, signature chain_iterate_cb. + * + * \param a First node. + * \param b Second node (immediate follower of a). + * \param data Pointer to nsec_chain_iterate_data_t holding parameters + * including changeset. + * + * \return Error code, KNOT_EOK if successful. + */ +static int connect_nsec_nodes(zone_node_t *a, zone_node_t *b, + nsec_chain_iterate_data_t *data) +{ + assert(a); + assert(b); + assert(data); + + if (b->rrset_count == 0 || b->flags & NODE_FLAGS_NONAUTH) { + return NSEC_NODE_SKIP; + } + + int ret = KNOT_EOK; + + /*! + * If the node has no other RRSets than NSEC (and possibly RRSIGs), + * just remove the NSEC and its RRSIG, they are redundant + */ + if (node_rrtype_exists(b, KNOT_RRTYPE_NSEC) + && knot_nsec_empty_nsec_and_rrsigs_in_node(b)) { + ret = knot_nsec_changeset_remove(b, data->changeset); + if (ret != KNOT_EOK) { + return ret; + } + // Skip the 'b' node + return NSEC_NODE_SKIP; + } + + // create new NSEC + knot_rrset_t new_nsec; + ret = create_nsec_rrset(&new_nsec, a, b, data->ttl); + if (ret != KNOT_EOK) { + return ret; + } + + knot_rrset_t old_nsec = node_rrset(a, KNOT_RRTYPE_NSEC); + + if (!knot_rrset_empty(&old_nsec)) { + /* Convert old NSEC to lowercase, just in case it's not. */ + knot_rrset_t *old_nsec_lc = knot_rrset_copy(&old_nsec, NULL); + ret = knot_rrset_rr_to_canonical(old_nsec_lc); + if (ret != KNOT_EOK) { + knot_rrset_free(old_nsec_lc, NULL); + return ret; + } + + bool equal = knot_rrset_equal(&new_nsec, old_nsec_lc, + KNOT_RRSET_COMPARE_WHOLE); + equal = (equal && (old_nsec_lc->ttl == new_nsec.ttl)); + knot_rrset_free(old_nsec_lc, NULL); + + if (equal) { + // current NSEC is valid, do nothing + knot_rdataset_clear(&new_nsec.rrs, NULL); + return KNOT_EOK; + } + + ret = knot_nsec_changeset_remove(a, data->changeset); + if (ret != KNOT_EOK) { + knot_rdataset_clear(&new_nsec.rrs, NULL); + return ret; + } + } + + // Add new NSEC to the changeset (no matter if old was removed) + ret = changeset_add_addition(data->changeset, &new_nsec, 0); + knot_rdataset_clear(&new_nsec.rrs, NULL); + return ret; +} + +/* - API - iterations ------------------------------------------------------- */ + +/*! + * \brief Call a function for each piece of the chain formed by sorted nodes. + */ +int knot_nsec_chain_iterate_create(zone_tree_t *nodes, + chain_iterate_create_cb callback, + nsec_chain_iterate_data_t *data) +{ + assert(nodes); + assert(callback); + + trie_it_t *it = trie_it_begin(nodes); + if (!it) { + return KNOT_ENOMEM; + } + + if (trie_it_finished(it)) { + trie_it_free(it); + return KNOT_EINVAL; + } + + zone_node_t *first = (zone_node_t *)*trie_it_val(it); + zone_node_t *previous = first; + zone_node_t *current = first; + + trie_it_next(it); + + int result = KNOT_EOK; + while (!trie_it_finished(it)) { + current = (zone_node_t *)*trie_it_val(it); + + result = callback(previous, current, data); + if (result == NSEC_NODE_SKIP) { + // No NSEC should be created for 'current' node, skip + ; + } else if (result == KNOT_EOK) { + previous = current; + } else { + trie_it_free(it); + return result; + } + trie_it_next(it); + } + + trie_it_free(it); + + return result == NSEC_NODE_SKIP ? callback(previous, first, data) : + callback(current, first, data); +} + +inline static zone_node_t *it_val(trie_it_t *it) +{ + return (zone_node_t *)*trie_it_val(it); +} + +inline static zone_node_t *it_next0(trie_it_t *it, zone_node_t *first) +{ + trie_it_next(it); + return (trie_it_finished(it) ? first : it_val(it)); +} + +static zone_node_t *it_next1(trie_it_t *it, zone_node_t *first) +{ + zone_node_t *res; + do { + res = it_next0(it, first); + } while (knot_nsec_empty_nsec_and_rrsigs_in_node(res) || (res->flags & NODE_FLAGS_NONAUTH)); + return res; +} + +static zone_node_t *it_next2(trie_it_t *it, zone_node_t *first, changeset_t *ch) +{ + zone_node_t *res = it_next0(it, first); + while (knot_nsec_empty_nsec_and_rrsigs_in_node(res) || (res->flags & NODE_FLAGS_NONAUTH)) { + (void)knot_nsec_changeset_remove(res, ch); + res = it_next0(it, first); + } + return res; +} + +static int node_cmp(zone_node_t *a, zone_node_t *b, zone_node_t *first_a, zone_node_t *first_b) +{ + assert(knot_dname_is_equal(first_a->owner, first_b->owner)); + assert(knot_dname_cmp(first_a->owner, a->owner) <= 0); + assert(knot_dname_cmp(first_b->owner, b->owner) <= 0); + int rev = (a == first_a || b == first_b ? -1 : 1); + return rev * knot_dname_cmp(a->owner, b->owner); +} + +#define CHECK_RET if (ret != KNOT_EOK) goto cleanup + +int knot_nsec_chain_iterate_fix(zone_tree_t *old_nodes, zone_tree_t *new_nodes, + chain_iterate_create_cb callback, + nsec_chain_iterate_data_t *data) +{ + assert(old_nodes); + assert(new_nodes); + assert(callback); + + int ret = KNOT_EOK; + + trie_it_t *old_it = trie_it_begin(old_nodes), *new_it = trie_it_begin(new_nodes); + if (old_it == NULL || new_it == NULL) { + ret = KNOT_ENOMEM; + goto cleanup; + } + + if (trie_it_finished(new_it)) { + ret = KNOT_ENORECORD; + goto cleanup; + } + if (trie_it_finished(old_it)) { + ret = KNOT_ENORECORD; + goto cleanup; + } + + zone_node_t *old_first = it_val(old_it), *new_first = it_val(new_it); + + if (!knot_dname_is_equal(old_first->owner, new_first->owner)) { + // this may happen with NSEC3 (on NSEC, it will be apex) + // it can be solved, but it would complicate the code + // 1. find a common node in both trees (ENORECORD if none) + // 2. start from there and cycle around trie_it_finished() until hit first again + // 3. modify the dname comparison operator ! + ret = KNOT_ENORECORD; + goto cleanup; + } + + if (knot_nsec_empty_nsec_and_rrsigs_in_node(new_first)) { + ret = KNOT_EINVAL; + goto cleanup; + } + + zone_node_t *old_prev = old_first, *new_prev = new_first; + zone_node_t *old_curr = it_next1(old_it, old_first); + zone_node_t *new_curr = it_next2(new_it, new_first, data->changeset); + + while (1) { + bool bitmap_change = !node_bitmap_equal(old_prev, new_prev); + + int cmp = node_cmp(old_curr, new_curr, old_first, new_first); + if (bitmap_change && cmp == 0) { + // if cmp != 0, the nsec chain will be locally rebuilt anyway, + // so no need to update bitmap in such case + // overall, we now have dnames: old_prev == new_prev && old_curr == new_curr + ret = knot_nsec_changeset_remove(old_prev, data->changeset); + CHECK_RET; + ret = callback(new_prev, new_curr, data); + CHECK_RET; + } + + while (cmp != 0) { + if (cmp < 0) { + // a node was removed + ret = knot_nsec_changeset_remove(old_prev, data->changeset); + CHECK_RET; + ret = knot_nsec_changeset_remove(old_curr, data->changeset); + CHECK_RET; + old_prev = old_curr; + old_curr = it_next1(old_it, old_first); + ret = callback(new_prev, new_curr, data); + CHECK_RET; + } else { + // a node was added + ret = knot_nsec_changeset_remove(old_prev, data->changeset); + CHECK_RET; + ret = callback(new_prev, new_curr, data); + CHECK_RET; + new_prev = new_curr; + new_curr = it_next2(new_it, new_first, data->changeset); + ret = callback(new_prev, new_curr, data); + CHECK_RET; + } + cmp = node_cmp(old_curr, new_curr, old_first, new_first); + } + + if (old_curr == old_first && new_curr == new_first) { + break; + } + + old_prev = old_curr; + new_prev = new_curr; + old_curr = it_next1(old_it, old_first); + new_curr = it_next2(new_it, new_first, data->changeset); + } + +cleanup: + trie_it_free(old_it); + trie_it_free(new_it); + return ret; +} + +/* - API - utility functions ------------------------------------------------ */ + +/*! + * \brief Add entry for removed NSEC to the changeset. + */ +int knot_nsec_changeset_remove(const zone_node_t *n, changeset_t *changeset) +{ + if (changeset == NULL) { + return KNOT_EINVAL; + } + + int result = KNOT_EOK; + + knot_rrset_t nsec = node_rrset(n, KNOT_RRTYPE_NSEC); + if (knot_rrset_empty(&nsec)) { + nsec = node_rrset(n, KNOT_RRTYPE_NSEC3); + } + if (!knot_rrset_empty(&nsec)) { + // update changeset + result = changeset_add_removal(changeset, &nsec, 0); + if (result != KNOT_EOK) { + return result; + } + } + + knot_rrset_t rrsigs = node_rrset(n, KNOT_RRTYPE_RRSIG); + if (!knot_rrset_empty(&rrsigs)) { + knot_rrset_t synth_rrsigs; + knot_rrset_init(&synth_rrsigs, n->owner, KNOT_RRTYPE_RRSIG, + KNOT_CLASS_IN, rrsigs.ttl); + result = knot_synth_rrsig(KNOT_RRTYPE_NSEC, &rrsigs.rrs, + &synth_rrsigs.rrs, NULL); + if (result == KNOT_ENOENT) { + // Try removing NSEC3 RRSIGs + result = knot_synth_rrsig(KNOT_RRTYPE_NSEC3, &rrsigs.rrs, + &synth_rrsigs.rrs, NULL); + } + + if (result != KNOT_EOK) { + knot_rdataset_clear(&synth_rrsigs.rrs, NULL); + if (result != KNOT_ENOENT) { + return result; + } + return KNOT_EOK; + } + + // store RRSIG + result = changeset_add_removal(changeset, &synth_rrsigs, 0); + knot_rdataset_clear(&synth_rrsigs.rrs, NULL); + } + + return result; +} + +/*! + * \brief Checks whether the node is empty or eventually contains only NSEC and + * RRSIGs. + */ +bool knot_nsec_empty_nsec_and_rrsigs_in_node(const zone_node_t *n) +{ + assert(n); + for (int i = 0; i < n->rrset_count; ++i) { + knot_rrset_t rrset = node_rrset_at(n, i); + if (rrset.type != KNOT_RRTYPE_NSEC && + rrset.type != KNOT_RRTYPE_RRSIG) { + return false; + } + } + + return true; +} + +/* - API - Chain creation --------------------------------------------------- */ + +/*! + * \brief Create new NSEC chain, add differences from current into a changeset. + */ +int knot_nsec_create_chain(const zone_contents_t *zone, uint32_t ttl, + changeset_t *changeset) +{ + assert(zone); + assert(zone->nodes); + assert(changeset); + + nsec_chain_iterate_data_t data = { ttl, changeset, zone }; + + return knot_nsec_chain_iterate_create(zone->nodes, + connect_nsec_nodes, &data); +} + +int knot_nsec_fix_chain(const zone_contents_t *old_zone, const zone_contents_t *new_zone, + uint32_t ttl, changeset_t *changeset) +{ + assert(old_zone); + assert(new_zone); + assert(old_zone->nodes); + assert(new_zone->nodes); + assert(changeset); + + nsec_chain_iterate_data_t data = { ttl, changeset, new_zone }; + + return knot_nsec_chain_iterate_fix(old_zone->nodes, new_zone->nodes, + connect_nsec_nodes, &data); +} |