Commit bf09d4bc authored by Daniel Salzman's avatar Daniel Salzman

Merge branch 'csk_rollovers' into 'master'

CSK rollovers

See merge request !856
parents ade18c04 de099379
......@@ -328,6 +328,11 @@ to the settings (see :ref:`KSK<policy_ksk-lifetime>` and :ref:`ZSK<policy_zsk-li
The algorithm rollover happens when the policy :ref:`algorithm<policy_algorithm>`
field is updated to a different value.
The signing scheme rollover happens when the policy :ref:`singing scheme<policy_single-type-signing>`
field is changed.
It's also possible to change the algorithm and signing scheme in one rollover.
The operator may check the next rollover phase time by watching the next zone signing time,
either in the log or via ``knotc zone-status``. There is no special log for finishing a rollover.
......
......@@ -233,6 +233,7 @@ static int serialize_key_params(const key_params_t *params, const knot_dname_t *
uint8_t flags = 0x02;
flags |= (params->is_ksk ? 0x01 : 0);
flags |= (params->is_pub_only ? 0x04 : 0);
flags |= (params->is_csk ? 0x08 : 0);
wire_ctx_write_u8(&wire, flags);
wire_ctx_write_u64(&wire, (uint64_t)params->timing.created);
wire_ctx_write_u64(&wire, (uint64_t)params->timing.pre_active);
......@@ -274,6 +275,10 @@ static int deserialize_key_params(key_params_t *params, const knot_db_val_t *key
uint8_t isksk_plus_flags = wire_ctx_read_u8(&wire);
params->is_ksk = ((isksk_plus_flags & (uint8_t)0x01) != (uint8_t)0x00);
params->is_pub_only = ((isksk_plus_flags & (uint8_t)0x04) != (uint8_t)0x00);
params->is_csk = ((isksk_plus_flags & (uint8_t)0x08) != (uint8_t)0x00);
if (params->is_csk && !params->is_ksk) {
return KNOT_EMALF;
}
if ((isksk_plus_flags & (uint8_t)0x02) != (uint8_t)0x00) {
params->timing.created = (knot_time_t)wire_ctx_read_u64(&wire);
params->timing.pre_active = (knot_time_t)wire_ctx_read_u64(&wire);
......
......@@ -98,6 +98,9 @@ static int params2kaspkey(const knot_dname_t *dname, key_params_t *params,
key->timing = params->timing;
key->is_pub_only = params->is_pub_only;
assert(params->is_ksk || !params->is_csk);
key->is_ksk = params->is_ksk;
key->is_zsk = (params->is_csk || !params->is_ksk);
return KNOT_EOK;
}
......@@ -111,6 +114,8 @@ static void kaspkey2params(knot_kasp_key_t *key, key_params_t *params)
dnssec_key_get_pubkey(key->key, &params->public_key);
params->algorithm = dnssec_key_get_algorithm(key->key);
params->is_ksk = dnssec_key_get_flags(key->key) == DNSKEY_FLAGS_KSK;
assert(params->is_ksk == key->is_ksk);
params->is_csk = (key->is_ksk && key->is_zsk);
params->timing = key->timing;
params->is_pub_only = key->is_pub_only;
}
......
......@@ -42,6 +42,7 @@ typedef struct {
typedef struct {
char *id;
bool is_ksk;
bool is_csk;
bool is_pub_only;
uint16_t keytag;
uint8_t algorithm;
......@@ -57,6 +58,8 @@ typedef struct {
dnssec_key_t *key; /*!< Instance of the key. */
knot_kasp_key_timing_t timing; /*!< Key timing information. */
bool is_pub_only;
bool is_ksk;
bool is_zsk;
} knot_kasp_key_t;
/*!
......
This diff is collapsed.
......@@ -161,8 +161,7 @@ int knot_dnssec_zone_sign(zone_update_t *update,
goto done;
}
result = load_zone_keys(ctx.zone, ctx.keystore,
ctx.policy->nsec3_enabled, ctx.now, &keyset, true);
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));
......@@ -247,8 +246,7 @@ int knot_dnssec_sign_update(zone_update_t *update, zone_sign_reschedule_t *resch
goto done;
}
result = load_zone_keys(ctx.zone, ctx.keystore,
ctx.policy->nsec3_enabled, ctx.now, &keyset, false);
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));
......
This diff is collapsed.
......@@ -60,12 +60,13 @@ uint16_t dnskey_flags(bool is_ksk);
* \brief Generate new key, store all details in new kasp key structure.
*
* \param ctx kasp context
* \param ksk true = generate KSK, false = generate ZSK
* \param ksk true = generate KSK or CSK, false = generate ZSK
* \param zsk true = generate ZSK or CSK, false = generate KSK
* \param key_ptr output if KNOT_EOK: new pointer to generated key
*
* \return KNOT_E*
*/
int kdnssec_generate_key(kdnssec_ctx_t *ctx, bool ksk, knot_kasp_key_t **key_ptr);
int kdnssec_generate_key(kdnssec_ctx_t *ctx, bool ksk, bool zsk, knot_kasp_key_t **key_ptr);
/*!
* \brief Take a key from another zone (copying info, sharing privkey).
......@@ -95,17 +96,13 @@ int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr);
/*!
* \brief Load zone keys and init cryptographic context.
*
* \param zone KASP zone.
* \param store KASP key store.
* \param nsec3_enabled Zone uses NSEC3 for authenticated denial.
* \param now Current time.
* \param ctx Zone signing context.
* \param keyset_ptr Resulting zone keyset.
* \param verbose Print key summary into log.
*
* \return Error code, KNOT_EOK if successful.
*/
int load_zone_keys(knot_kasp_zone_t *zone, dnssec_keystore_t *store,
bool nsec3_enabled, knot_time_t now, zone_keyset_t *keyset_ptr,
bool verbose);
int load_zone_keys(kdnssec_ctx_t *ctx, zone_keyset_t *keyset_ptr, bool verbose);
/*!
* \brief Get zone keys by a keytag.
......
......@@ -219,7 +219,7 @@ int event_parent_ds_q(conf_t *conf, zone_t *zone)
}
zone_keyset_t keyset = { 0 };
ret = load_zone_keys(ctx.zone, ctx.keystore, false, ctx.now, &keyset, false);
ret = load_zone_keys(&ctx, &keyset, false);
if (ret != KNOT_EOK) {
kdnssec_ctx_deinit(&ctx);
return ret;
......
......@@ -154,7 +154,7 @@ int keymgr_generate_key(kdnssec_ctx_t *ctx, int argc, char *argv[])
}
knot_kasp_key_t *key = NULL;
int ret = kdnssec_generate_key(ctx, isksk, &key);
int ret = kdnssec_generate_key(ctx, isksk, !isksk, &key);
if (ret != KNOT_EOK) {
return ret;
}
......@@ -315,6 +315,8 @@ int keymgr_import_bind(kdnssec_ctx_t *ctx, const char *import_file, bool pub_onl
kkey->key = key;
kkey->timing = timing;
kkey->is_pub_only = pub_only;
kkey->is_ksk = (dnssec_key_get_flags(kkey->key) == DNSKEY_FLAGS_KSK);
kkey->is_zsk = !kkey->is_ksk;
// append to zone
ret = kasp_zone_append(ctx->zone, kkey);
......
$ORIGIN com.
$TTL 1200
@ SOA ns admin 20110100 7 7 16 600
ns AAAA ::0
#!/usr/bin/env python3
"""
Check of automatic algorithm rollover scenario.
"""
import collections
import os
import shutil
import datetime
import subprocess
from subprocess import check_call
from dnstest.utils import *
from dnstest.keys import Keymgr
from dnstest.test import Test
# check zone if keys are present and used for signing
def check_zone(server, zone, dnskeys, dnskey_rrsigs, cdnskeys, soa_rrsigs, msg):
qdnskeys = server.dig("example.com", "DNSKEY", bufsize=4096)
found_dnskeys = qdnskeys.count("DNSKEY")
qdnskeyrrsig = server.dig("example.com", "DNSKEY", dnssec=True, bufsize=4096)
found_rrsigs = qdnskeyrrsig.count("RRSIG")
qcdnskey = server.dig("example.com", "CDNSKEY", bufsize=4096)
found_cdnskeys = qcdnskey.count("CDNSKEY")
qsoa = server.dig("example.com", "SOA", dnssec=True, bufsize=4096)
found_soa_rrsigs = qsoa.count("RRSIG")
check_log("DNSKEYs: %d (expected %d)" % (found_dnskeys, dnskeys));
check_log("RRSIGs: %d (expected %d)" % (found_soa_rrsigs, soa_rrsigs));
check_log("DNSKEY-RRSIGs: %d (expected %d)" % (found_rrsigs, dnskey_rrsigs));
check_log("CDNSKEYs: %d (expected %d)" % (found_cdnskeys, cdnskeys));
if found_dnskeys != dnskeys:
set_err("BAD DNSKEY COUNT: " + msg)
detail_log("!DNSKEYs not published and activated as expected: " + msg)
if found_soa_rrsigs != soa_rrsigs:
set_err("BAD RRSIG COUNT: " + msg)
detail_log("!RRSIGs not published and activated as expected: " + msg)
if found_rrsigs != dnskey_rrsigs:
set_err("BAD DNSKEY RRSIG COUNT: " + msg)
detail_log("!RRSIGs not published and activated as expected: " + msg)
if found_cdnskeys != cdnskeys:
set_err("BAD CDNSKEY COUNT: " + msg)
detail_log("!CDNSKEYs not published and activated as expected: " + msg)
detail_log(SEP)
# Valgrind delay breaks the timing!
if not server.valgrind:
server.zone_backup(zone, flush=True)
server.zone_verify(zone)
def wait_for_rrsig_count(t, server, rrtype, rrsig_count, timeout):
rtime = 0
while True:
qdnskeyrrsig = server.dig("example.com", rrtype, dnssec=True, bufsize=4096)
found_rrsigs = qdnskeyrrsig.count("RRSIG")
if found_rrsigs == rrsig_count:
break
rtime = rtime + 1
t.sleep(1)
if rtime > timeout:
break
def wait_for_dnskey_count(t, server, dnskey_count, timeout):
rtime = 0
while True:
qdnskeyrrsig = server.dig("example.com", "DNSKEY", dnssec=True, bufsize=4096)
found_dnskeys = qdnskeyrrsig.count("DNSKEY")
if found_dnskeys == dnskey_count:
break
rtime = rtime + 1
t.sleep(1)
if rtime > timeout:
break
def watch_alg_rollover(t, server, zone, before_keys, after_keys, desc, set_alg, set_stss, submission_cb):
check_zone(server, zone, before_keys, 1, 1, 1, desc + ": initial keys")
server.dnssec(zone).single_type_signing = set_stss
server.dnssec(zone).alg = set_alg
server.gen_confile()
server.reload()
wait_for_rrsig_count(t, server, "SOA", 2, 20)
check_zone(server, zone, before_keys, 1 if after_keys > 1 else 2, 1, 2, desc + ": pre active")
wait_for_dnskey_count(t, server, before_keys + after_keys, 20)
check_zone(server, zone, before_keys + after_keys, 2, 1, 2, desc + ": both algorithms active")
# wait for any change in CDS records
CDS1 = str(server.dig(ZONE, "CDS").resp.answer[0].to_rdataset())
t.sleep(3)
while CDS1 == str(server.dig(ZONE, "CDS").resp.answer[0].to_rdataset()):
t.sleep(1)
check_zone(server, zone, before_keys + after_keys, 2, 1, 2, desc + ": new KSK ready")
submission_cb()
t.sleep(4)
check_zone(server, zone, before_keys + after_keys, 2, 1, 2, desc + ": both still active")
wait_for_dnskey_count(t, server, after_keys, 20)
check_zone(server, zone, after_keys, 1 if before_keys > 1 else 2, 1, 2, desc + ": post active")
wait_for_rrsig_count(t, server, "SOA", 1, 20)
check_zone(server, zone, after_keys, 1, 1, 1, desc + ": old alg removed")
def watch_ksk_rollover(t, server, zone, before_keys, after_keys, total_keys, desc, set_stss, set_ksk_lifetime, submission_cb):
check_zone(server, zone, before_keys, 1, 1, 1, desc + ": initial keys")
orig_ksk_lifetime = server.dnssec(zone).ksk_lifetime
server.dnssec(zone).single_type_signing = set_stss
server.dnssec(zone).ksk_lifetime = set_ksk_lifetime if set_ksk_lifetime > 0 else orig_ksk_lifetime
server.gen_confile()
server.reload()
wait_for_dnskey_count(t, server, total_keys, 20)
t.sleep(3)
check_zone(server, zone, total_keys, 1, 1, 1, desc + ": published new")
wait_for_rrsig_count(t, server, "DNSKEY", 2, 20)
check_zone(server, zone, total_keys, 2, 1, 1 if before_keys > 1 and after_keys > 1 else 2, desc + ": new KSK ready")
server.dnssec(zone).ksk_lifetime = orig_ksk_lifetime
server.gen_confile()
server.reload()
submission_cb()
t.sleep(4)
check_zone(server, zone, total_keys, 2, 1, 1 if before_keys > 1 else 2, desc + ": both still active")
wait_for_rrsig_count(t, server, "DNSKEY", 1, 20)
if before_keys < 2 or after_keys > 1:
check_zone(server, zone, total_keys, 1, 1, 1, desc + ": old key retired")
# else skip the test as we have no control on KSK and ZSK retiring asynchronously
wait_for_dnskey_count(t, server, after_keys, 20)
check_zone(server, zone, after_keys, 1, 1, 1, desc + ": old key removed")
t = Test()
parent = t.server("knot")
parent_zone = t.zone("com.", storage=".")
t.link(parent_zone, parent)
child = t.server("knot")
child_zone = t.zone("example.com.")
t.link(child_zone, child)
def cds_submission():
cds = child.dig(ZONE, "CDS")
cds_rdata = cds.resp.answer[0].to_rdataset()[0].to_text()
up = parent.update(parent_zone)
up.add(ZONE, 3600, "DS", cds_rdata)
up.send("NOERROR")
child.zonefile_sync = 24 * 60 * 60
child.dnssec(child_zone).enable = True
child.dnssec(child_zone).manual = False
child.dnssec(child_zone).alg = "RSASHA512"
child.dnssec(child_zone).dnskey_ttl = 2
child.dnssec(child_zone).zsk_lifetime = 99999
child.dnssec(child_zone).ksk_lifetime = 300 # this can be possibly left also infinity
child.dnssec(child_zone).propagation_delay = 11
child.dnssec(child_zone).ksk_sbm_check = [ parent ]
child.dnssec(child_zone).ksk_sbm_check_interval = 2
# parameters
ZONE = "example.com."
t.start()
child.zone_wait(child_zone)
watch_alg_rollover(t, child, child_zone, 2, 1, "KZSK to CSK alg", "RSASHA256", True, cds_submission)
watch_ksk_rollover(t, child, child_zone, 1, 1, 2, "CSK rollover", True, 27, cds_submission)
watch_ksk_rollover(t, child, child_zone, 1, 2, 3, "CSK to KZSK", False, 0, cds_submission)
watch_ksk_rollover(t, child, child_zone, 2, 2, 3, "KSK rollover", False, 27, cds_submission)
watch_ksk_rollover(t, child, child_zone, 2, 1, 3, "KZSK to CSK", True, 0, cds_submission)
watch_alg_rollover(t, child, child_zone, 1, 1, "CSK to CSK alg", "RSASHA512", True, cds_submission)
watch_alg_rollover(t, child, child_zone, 1, 2, "CSK to KZSK alg", "RSASHA256", False, cds_submission)
t.end()
......@@ -16,7 +16,7 @@ TEST_CASES = {
"rsa_now_ecdsa_future": True,
"rsa_ecdsa_roll": True,
"stss_ksk": True,
"stss_zsk": True,
# "stss_zsk": True, # No longer supported.
"stss_two_ksk": True,
"stss_rsa256_rsa512": True,
"rsa_split_ecdsa_stss": True,
......@@ -73,8 +73,6 @@ knot.key_gen("rsa_ecdsa_roll", ksk="true", created=GEN, publish=PAST, rea
knot.key_gen("rsa_ecdsa_roll", ksk="false", created=GEN, publish=FUTU, ready=PAST, active=PAST, retire=INF, remove=INF)
# STSS: KSK only
knot.key_gen("stss_ksk", ksk="true", created=GEN, publish=PAST, ready=PAST, active=PAST, retire=INF, remove=INF)
# STSS: ZSK only
knot.key_gen("stss_zsk", ksk="false", created=GEN, publish=PAST, ready=PAST, active=PAST, retire=INF, remove=INF)
# STSS: two KSKs
knot.key_gen("stss_two_ksk", ksk="true", created=GEN, publish=PAST, ready=PAST, active=PAST, retire=INF, remove=INF)
knot.key_gen("stss_two_ksk", ksk="true", created=GEN, publish=PAST, ready=PAST, active=PAST, retire=INF, remove=INF)
......
......@@ -8,24 +8,23 @@ from dnstest.test import Test
t = Test()
knot = t.server("knot")
zones = t.zone_rnd(5, dnssec=False, records=10)
zones = t.zone_rnd(4, dnssec=False, records=10)
t.link(zones, knot)
t.start()
# one KSK
knot.gen_key(zones[0], ksk=True, alg="RSASHA256", key_len="512")
# one ZSK
knot.gen_key(zones[1], ksk=False, alg="RSASHA512", key_len="1024")
# one ZSK no longer supported
# multiple KSKs
knot.gen_key(zones[2], ksk=True, alg="RSASHA512", key_len="1024")
knot.gen_key(zones[2], ksk=True, alg="RSASHA256", key_len="512")
knot.gen_key(zones[1], ksk=True, alg="RSASHA512", key_len="1024")
knot.gen_key(zones[1], ksk=True, alg="RSASHA256", key_len="512")
# different algorithms: KSK+ZSK pair, one ZSK
knot.gen_key(zones[3], ksk=True, alg="RSASHA256", key_len="1024")
knot.gen_key(zones[3], ksk=False, alg="RSASHA256", key_len="1024")
knot.gen_key(zones[3], ksk=False, alg="RSASHA512", key_len="1024")
# different algorithms: KSK+ZSK pair, one KSK
knot.gen_key(zones[2], ksk=True, alg="RSASHA256", key_len="1024")
knot.gen_key(zones[2], ksk=False, alg="RSASHA256", key_len="1024")
knot.gen_key(zones[2], ksk=True, alg="RSASHA512", key_len="1024")
for zone in zones[:-1]:
knot.dnssec(zone).enable = True
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment