dnssec: implemented re-salt option on every ZSK roll occasion

This commit is contained in:
Libor Peltan 2021-11-29 17:44:49 +01:00 committed by Daniel Salzman
parent d5a1cb1c99
commit 89662f91bf
11 changed files with 106 additions and 19 deletions

View file

@ -1511,6 +1511,9 @@ A validity period of newly issued salt field.
.sp
Zero value means infinity.
.sp
Special value \fI\-1\fP triggers re\-salt every time when active ZSK changes.
This optimizes the number of big changes to the zone.
.sp
\fIDefault:\fP 30 days
.SS signing\-threads
.sp

View file

@ -1648,6 +1648,9 @@ A validity period of newly issued salt field.
Zero value means infinity.
Special value *-1* triggers re-salt every time when active ZSK changes.
This optimizes the number of big changes to the zone.
*Default:* 30 days
.. _policy_signing-threads:

View file

@ -375,7 +375,7 @@ static const yp_item_t desc_policy[] = {
{ C_NSEC3_ITER, YP_TINT, YP_VINT = { 0, UINT16_MAX, 10 }, CONF_IO_FRLD_ZONES },
{ C_NSEC3_OPT_OUT, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
{ C_NSEC3_SALT_LEN, YP_TINT, YP_VINT = { 0, UINT8_MAX, 8 }, CONF_IO_FRLD_ZONES },
{ C_NSEC3_SALT_LIFETIME, YP_TINT, YP_VINT = { 0, UINT32_MAX, DAYS(30), YP_STIME },
{ C_NSEC3_SALT_LIFETIME, YP_TINT, YP_VINT = { -1, UINT32_MAX, DAYS(30), YP_STIME },
CONF_IO_FRLD_ZONES },
{ C_SIGNING_THREADS, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } },
{ C_KSK_SBM, YP_TREF, YP_VREF = { C_SBM }, CONF_IO_FRLD_ZONES,

View file

@ -109,7 +109,7 @@ typedef struct {
// NSEC3
bool nsec3_enabled;
bool nsec3_opt_out;
uint32_t nsec3_salt_lifetime; // like knot_time_t
int64_t nsec3_salt_lifetime; // like knot_time_t
uint16_t nsec3_iterations;
uint8_t nsec3_salt_length;
// zone

View file

@ -66,7 +66,8 @@ static int generate_salt(dnssec_binary_t *salt, uint16_t length)
return KNOT_EOK;
}
int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_time_t *when_resalt)
int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, bool soa_rrsigs_ok,
knot_time_t *salt_changed, knot_time_t *when_resalt)
{
int ret = KNOT_EOK;
@ -74,11 +75,13 @@ int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_
return KNOT_EOK;
}
if (ctx->zone->nsec3_salt.size != ctx->policy->nsec3_salt_length || ctx->zone->nsec3_salt_created == 0) {
if (ctx->policy->nsec3_salt_lifetime < 0 && !soa_rrsigs_ok) {
*when_resalt = ctx->now;
} else 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 {
} else if (ctx->policy->nsec3_salt_lifetime > 0) {
*when_resalt = knot_time_plus(ctx->zone->nsec3_salt_created, ctx->policy->nsec3_salt_lifetime);
}
@ -98,7 +101,9 @@ int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_
*salt_changed = ctx->now;
}
// continue to planning next resalt even if NOK
*when_resalt = knot_time_plus(ctx->now, ctx->policy->nsec3_salt_lifetime);
if (ctx->policy->nsec3_salt_lifetime > 0) {
*when_resalt = knot_time_plus(ctx->now, ctx->policy->nsec3_salt_lifetime);
}
}
return ret;
@ -140,16 +145,6 @@ int knot_dnssec_zone_sign(zone_update_t *update,
goto done;
}
// perform nsec3resalt if pending
if (roll_flags & KEY_ROLL_ALLOW_NSEC3RESALT) {
result = knot_dnssec_nsec3resalt(&ctx, &reschedule->last_nsec3resalt, &reschedule->next_nsec3resalt);
if (result != KNOT_EOK) {
log_zone_error(zone_name, "DNSSEC, failed to update NSEC3 salt (%s)",
knot_strerror(result));
goto done;
}
}
ctx.rrsig_drop_existing = flags & ZONE_SIGN_DROP_SIGNATURES;
conf_val_t val = conf_zone_get(conf, C_ZONEMD_GENERATE, zone_name);
@ -176,6 +171,18 @@ int knot_dnssec_zone_sign(zone_update_t *update,
goto done;
}
// perform nsec3resalt if pending
if (roll_flags & KEY_ROLL_ALLOW_NSEC3RESALT) {
knot_rdataset_t *rrsig = node_rdataset(update->new_cont->apex, KNOT_RRTYPE_RRSIG);
bool issbaz = is_soa_signed_by_all_zsks(&keyset, rrsig);
result = knot_dnssec_nsec3resalt(&ctx, issbaz, &reschedule->last_nsec3resalt, &reschedule->next_nsec3resalt);
if (result != KNOT_EOK) {
log_zone_error(zone_name, "DNSSEC, failed to update NSEC3 salt (%s)",
knot_strerror(result));
goto done;
}
}
log_zone_info(zone_name, "DNSSEC, signing started");
knot_time_t next_resign = 0;

View file

@ -103,12 +103,14 @@ int knot_dnssec_sign_update(zone_update_t *update, conf_t *conf);
* proper DNSSEC chain.
*
* \param ctx zone signing context
* \param soa_rrsigs_ok Zone is signed by current active ZSKs.
* \param salt_changed output if KNOT_EOK: when was the salt last changed? (either ctx->now or 0)
* \param when_resalt output: timestamp when next resalt takes place
*
* \return KNOT_E*
*/
int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, knot_time_t *salt_changed, knot_time_t *when_resalt);
int knot_dnssec_nsec3resalt(kdnssec_ctx_t *ctx, bool soa_rrsigs_ok,
knot_time_t *salt_changed, knot_time_t *when_resalt);
/*!
* \brief When DNSSEC signing failed, re-plan on this time.

View file

@ -715,3 +715,41 @@ int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner,
*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;
}

View file

@ -202,3 +202,12 @@ void zone_sign_ctx_free(zone_sign_ctx_t *ctx);
*/
int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner,
const uint8_t *rdata, size_t rdlen);
/*!
* \brief Tell if apex SOA is signed by all active ZSKs.
*
* \param keyset Zone key set.
* \param apex_rrsig Apex RRSIG RRSet.
*/
int is_soa_signed_by_all_zsks(const zone_keyset_t *keyset,
const knot_rdataset_t *apex_rrsig);

View file

@ -29,7 +29,7 @@ int event_nsec3resalt(conf_t *conf, zone_t *zone)
return ret;
}
ret = knot_dnssec_nsec3resalt(&kctx, &salt_changed, &next_resalt);
ret = knot_dnssec_nsec3resalt(&kctx, true, &salt_changed, &next_resalt);
if (ret == KNOT_EOK && salt_changed != 0) {
zone_events_schedule_now(zone, ZONE_EVENT_DNSSEC);
zone->timers.last_resalt = kctx.now;

View file

@ -130,7 +130,9 @@ void replan_from_timers(conf_t *conf, zone_t *zone)
resalt = now;
} else {
val = conf_id_get(conf, C_POLICY, C_NSEC3_SALT_LIFETIME, &policy);
resalt = zone->timers.last_resalt + conf_int(&val);
if (conf_int(&val) > 0) {
resalt = zone->timers.last_resalt + conf_int(&val);
}
}
}

View file

@ -7,6 +7,21 @@ Check of automatic ZSK rollover with changing zone TTLs.
from dnstest.utils import *
from dnstest.test import Test
def get_salt(server, zone):
resp = server.dig(zone[0].name, "NSEC3PARAM")
return resp.resp.answer[0].to_rdataset()[0].to_text().split()[-1]
last_salt = ""
def check_salt(server, zone, shall_differ):
global last_salt
salt = get_salt(server, zone)
if shall_differ != (salt != last_salt):
msg_not = " " if shall_differ else " not "
detail_log("Salt %s, Last salt %s, shall%sdiffer" % (salt, last_salt, msg_not))
set_err("NSEC3 salt shall%sdiffer" % msg_not)
last_salt = salt
def dnskey_count(server, zone):
return server.dig(zone[0].name, "DNSKEY", dnssec=False).count("DNSKEY")
@ -47,17 +62,25 @@ master.dnssec(zone).manual = False
master.dnssec(zone).dnskey_ttl = 3
master.dnssec(zone).zsk_lifetime = 16
master.dnssec(zone).propagation_delay = 3
master.dnssec(zone).nsec3 = True
master.dnssec(zone).nsec3_salt_lifetime = -1
t.start()
master.zone_wait(zone)
check_salt(master, zone, True)
wait4key(t, master, zone, 3, -1, 6, 20, "ZSK publish") # new ZSK published
old_key = zsk_keytag(master, zone)
check_salt(master, zone, False)
wait4key(t, master, zone, 3, old_key, 4, 8, "ZSK switch") # active ZSK switched
check_salt(master, zone, True)
up = master.update(zone)
up.delete("longttl.example.com.", "A") # zone max TTL decreases
up.send()
master.ctl("zone-sign")
wait4key(t, master, zone, 2, old_key, 9, 14, "ZSK remove") # old ZSK removed
check_salt(master, zone, False)
t.end()