diff options
Diffstat (limited to 'src/knot/dnssec/zone-events.c')
-rw-r--r-- | src/knot/dnssec/zone-events.c | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/src/knot/dnssec/zone-events.c b/src/knot/dnssec/zone-events.c new file mode 100644 index 0000000..b259b20 --- /dev/null +++ b/src/knot/dnssec/zone-events.c @@ -0,0 +1,356 @@ +/* Copyright (C) 2020 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 "libdnssec/error.h" +#include "libdnssec/random.h" +#include "libknot/libknot.h" +#include "knot/conf/conf.h" +#include "knot/common/log.h" +#include "knot/dnssec/key-events.h" +#include "knot/dnssec/policy.h" +#include "knot/dnssec/zone-events.h" +#include "knot/dnssec/zone-keys.h" +#include "knot/dnssec/zone-nsec.h" +#include "knot/dnssec/zone-sign.h" +#include "knot/zone/adjust.h" + +static int sign_init(zone_contents_t *zone, zone_sign_flags_t flags, zone_sign_roll_flags_t roll_flags, + knot_time_t adjust_now, knot_lmdb_db_t *kaspdb, kdnssec_ctx_t *ctx, + zone_sign_reschedule_t *reschedule) +{ + assert(zone); + assert(ctx); + + const knot_dname_t *zone_name = zone->apex->owner; + + int r = kdnssec_ctx_init(conf(), ctx, zone_name, kaspdb, NULL); + if (r != KNOT_EOK) { + return r; + } + if (adjust_now) { + ctx->now = adjust_now; + } + + // perform nsec3resalt if pending + + if (roll_flags & KEY_ROLL_ALLOW_NSEC3RESALT) { + r = knot_dnssec_nsec3resalt(ctx, &reschedule->last_nsec3resalt, &reschedule->next_nsec3resalt); + if (r != KNOT_EOK) { + return r; + } + } + + // update policy based on the zone content + update_policy_from_zone(ctx->policy, zone); + + // perform key rollover if needed + r = knot_dnssec_key_rollover(ctx, roll_flags, reschedule); + if (r != KNOT_EOK) { + return r; + } + + // RRSIG handling + + ctx->rrsig_drop_existing = flags & ZONE_SIGN_DROP_SIGNATURES; + + return KNOT_EOK; +} + +static knot_time_t schedule_next(kdnssec_ctx_t *kctx, const zone_keyset_t *keyset, + knot_time_t keys_expire, knot_time_t rrsigs_expire) +{ + knot_time_t rrsigs_refresh = knot_time_add(rrsigs_expire, -(knot_timediff_t)kctx->policy->rrsig_refresh_before); + knot_time_t zone_refresh = knot_time_min(keys_expire, rrsigs_refresh); + + knot_time_t dnskey_update = knot_get_next_zone_key_event(keyset); + knot_time_t next = knot_time_min(zone_refresh, dnskey_update); + + return next; +} + +static int generate_salt(dnssec_binary_t *salt, uint16_t length) +{ + assert(salt); + dnssec_binary_t new_salt = { 0 }; + + if (length > 0) { + int r = dnssec_binary_alloc(&new_salt, length); + if (r != KNOT_EOK) { + return knot_error_from_libdnssec(r); + } + + r = dnssec_random_binary(&new_salt); + if (r != KNOT_EOK) { + dnssec_binary_free(&new_salt); + return knot_error_from_libdnssec(r); + } + } + + dnssec_binary_free(salt); + *salt = new_salt; + + return KNOT_EOK; +} + +int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_time_t *when_resalt) +{ + int ret = KNOT_EOK; + + if (!ctx->policy->nsec3_enabled) { + return KNOT_EOK; + } + + if (ctx->zone->nsec3_salt.size != ctx->policy->nsec3_salt_length || ctx->zone->nsec3_salt_created == 0) { + *when_resalt = ctx->now; + } else if (knot_time_cmp(ctx->now, ctx->zone->nsec3_salt_created) < 0) { + return KNOT_EINVAL; + } else { + *when_resalt = knot_time_plus(ctx->zone->nsec3_salt_created, ctx->policy->nsec3_salt_lifetime); + } + + if (knot_time_cmp(*when_resalt, ctx->now) <= 0) { + if (ctx->policy->nsec3_salt_length == 0) { + ctx->zone->nsec3_salt.size = 0; + ctx->zone->nsec3_salt_created = ctx->now; + *salt_changed = ctx->now; + *when_resalt = 0; + return kdnssec_ctx_commit(ctx); + } + + ret = generate_salt(&ctx->zone->nsec3_salt, ctx->policy->nsec3_salt_length); + if (ret == KNOT_EOK) { + ctx->zone->nsec3_salt_created = ctx->now; + ret = kdnssec_ctx_commit(ctx); + *salt_changed = ctx->now; + } + // continue to planning next resalt even if NOK + *when_resalt = knot_time_plus(ctx->now, ctx->policy->nsec3_salt_lifetime); + } + + return ret; +} + +int knot_dnssec_zone_sign(zone_update_t *update, + zone_sign_flags_t flags, + zone_sign_roll_flags_t roll_flags, + knot_time_t adjust_now, + zone_sign_reschedule_t *reschedule) +{ + if (!update || !reschedule) { + return KNOT_EINVAL; + } + + int result = KNOT_ERROR; + const knot_dname_t *zone_name = update->new_cont->apex->owner; + kdnssec_ctx_t ctx = { 0 }; + zone_keyset_t keyset = { 0 }; + + // signing pipeline + + result = sign_init(update->new_cont, flags, roll_flags, adjust_now, + update->zone->kaspdb, &ctx, reschedule); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to initialize (%s)", + knot_strerror(result)); + goto done; + } + + result = load_zone_keys(&ctx, &keyset, true); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to load keys (%s)", + knot_strerror(result)); + goto done; + } + + log_zone_info(zone_name, "DNSSEC, signing started"); + + knot_time_t next_resign = 0; + result = knot_zone_sign_update_dnskeys(update, &keyset, &ctx, &next_resign); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to update DNSKEY records (%s)", + knot_strerror(result)); + goto done; + } + + result = zone_adjust_contents(update->new_cont, adjust_cb_flags, NULL, + false, false, 1, update->a_ctx->node_ptrs); + if (result != KNOT_EOK) { + return result; + } + + result = knot_zone_create_nsec_chain(update, &ctx); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to create NSEC%s chain (%s)", + ctx.policy->nsec3_enabled ? "3" : "", + knot_strerror(result)); + goto done; + } + + knot_time_t zone_expire = 0; + result = knot_zone_sign(update, &keyset, &ctx, &zone_expire); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to sign zone content (%s)", + knot_strerror(result)); + goto done; + } + + // SOA finishing + + if (zone_update_no_change(update) && + !knot_zone_sign_soa_expired(update->new_cont, &keyset, &ctx)) { + log_zone_info(zone_name, "DNSSEC, zone is up-to-date"); + goto done; + } + + if (!(flags & ZONE_SIGN_KEEP_SERIAL) && zone_update_to(update) == NULL) { + result = zone_update_increment_soa(update, conf()); + if (result == KNOT_EOK) { + result = knot_zone_sign_soa(update, &keyset, &ctx); + } + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to update SOA record (%s)", + knot_strerror(result)); + goto done; + } + } + + log_zone_info(zone_name, "DNSSEC, successfully signed"); + +done: + if (result == KNOT_EOK) { + reschedule->next_sign = schedule_next(&ctx, &keyset, next_resign, zone_expire); + } else { + reschedule->next_sign = knot_dnssec_failover_delay(&ctx); + } + + free_zone_keys(&keyset); + kdnssec_ctx_deinit(&ctx); + + return result; +} + +int knot_dnssec_sign_update(zone_update_t *update, zone_sign_reschedule_t *reschedule) +{ + if (update == NULL || reschedule == NULL) { + return KNOT_EINVAL; + } + + int result = KNOT_ERROR; + const knot_dname_t *zone_name = update->new_cont->apex->owner; + kdnssec_ctx_t ctx = { 0 }; + zone_keyset_t keyset = { 0 }; + + result = sign_init(update->new_cont, 0, 0, 0, update->zone->kaspdb, &ctx, reschedule); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to initialize (%s)", + knot_strerror(result)); + goto done; + } + + result = load_zone_keys(&ctx, &keyset, false); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to load keys (%s)", + knot_strerror(result)); + goto done; + } + + result = zone_adjust_contents(update->new_cont, adjust_cb_flags, NULL, + false, false, 1, update->a_ctx->node_ptrs); + if (result != KNOT_EOK) { + goto done; + } + + knot_time_t expire_at = 0; + result = knot_zone_sign_update(update, &keyset, &ctx, &expire_at); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to sign changeset (%s)", + knot_strerror(result)); + goto done; + } + + result = knot_zone_fix_nsec_chain(update, &keyset, &ctx); + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to fix NSEC%s chain (%s)", + ctx.policy->nsec3_enabled ? "3" : "", + knot_strerror(result)); + goto done; + } + + bool soa_changed = (knot_soa_serial(node_rdataset(update->zone->contents->apex, KNOT_RRTYPE_SOA)->rdata) != + knot_soa_serial(node_rdataset(update->new_cont->apex, KNOT_RRTYPE_SOA)->rdata)); + + if (zone_update_no_change(update) && !soa_changed && + !knot_zone_sign_soa_expired(update->new_cont, &keyset, &ctx)) { + log_zone_info(zone_name, "DNSSEC, zone is up-to-date"); + goto done; + } + + if (!soa_changed) { + // incrementing SOA just of it has not been modified by the update + result = zone_update_increment_soa(update, conf()); + } + if (result == KNOT_EOK) { + result = knot_zone_sign_soa(update, &keyset, &ctx); + } + if (result != KNOT_EOK) { + log_zone_error(zone_name, "DNSSEC, failed to update SOA record (%s)", + knot_strerror(result)); + goto done; + } + + log_zone_info(zone_name, "DNSSEC, successfully signed"); + +done: + if (result == KNOT_EOK) { + reschedule->next_sign = schedule_next(&ctx, &keyset, 0, expire_at); + } + + free_zone_keys(&keyset); + kdnssec_ctx_deinit(&ctx); + + return result; +} + +knot_time_t knot_dnssec_failover_delay(const kdnssec_ctx_t *ctx) +{ + if (ctx->policy == NULL) { + return ctx->now + 3600; // failed before allocating ctx->policy, use default + } else { + return ctx->now + ctx->policy->rrsig_prerefresh; + } +} + +int knot_dnssec_validate_zone(zone_update_t *update, bool incremental) +{ + kdnssec_ctx_t ctx = { 0 }; + int ret = kdnssec_validation_ctx(conf(), &ctx, update->new_cont); + if (ret == KNOT_EOK) { + ret = knot_zone_check_nsec_chain(update, &ctx, incremental); + } + if (ret == KNOT_EOK) { + knot_time_t unused = 0; + assert(ctx.validation_mode); + if (incremental) { + ret = knot_zone_sign_update(update, NULL, &ctx, &unused); + } else { + ret = knot_zone_sign(update, NULL, &ctx, &unused); + } + } + kdnssec_ctx_deinit(&ctx); + return ret; +} |