2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-31 06:25:31 +00:00

dnssec-ksr keygen -o to create KSKs

Add an option to dnssec-ksr keygen, -o, to create KSKs instead of ZSKs.
This way, we can create a set of KSKS for a given period too.

For KSKs we also need to set timing metadata, including "SyncPublish"
and "SyncDelete". This functionality already exists in keymgr.c so
let's make the function accessible.

Replace dnssec-keygen calls with dnssec-ksr keygen for KSK in the
ksr system test and check keys for created KSKs as well. This requires
a slight modification of the check_keys function to take into account
KSK timings and metadata.

(cherry picked from commit 680aedb595)
This commit is contained in:
Matthijs Mekking
2024-09-03 17:24:22 +02:00
committed by Mark Andrews
parent 1adcb2945e
commit a92fb659d3
5 changed files with 168 additions and 83 deletions

View File

@@ -25,6 +25,7 @@
#include <dns/callbacks.h>
#include <dns/dnssec.h>
#include <dns/fixedname.h>
#include <dns/keymgr.h>
#include <dns/keyvalues.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
@@ -63,16 +64,19 @@ struct ksr_ctx {
bool setstart;
bool setend;
/* keygen */
bool ksk;
dns_ttl_t ttl;
dns_secalg_t alg;
int size;
time_t lifetime;
time_t parentpropagation;
time_t propagation;
time_t publishsafety;
time_t retiresafety;
time_t sigrefresh;
time_t sigvalidity;
time_t signdelay;
time_t ttlds;
time_t ttlsig;
};
typedef struct ksr_ctx ksr_ctx_t;
@@ -243,6 +247,7 @@ get_dnskeys(ksr_ctx_t *ksr, dns_dnsseckeylist_t *keys) {
static void
setcontext(ksr_ctx_t *ksr, dns_kasp_t *kasp) {
ksr->parentpropagation = dns_kasp_parentpropagationdelay(kasp);
ksr->propagation = dns_kasp_zonepropagationdelay(kasp);
ksr->publishsafety = dns_kasp_publishsafety(kasp);
ksr->retiresafety = dns_kasp_retiresafety(kasp);
@@ -250,6 +255,7 @@ setcontext(ksr_ctx_t *ksr, dns_kasp_t *kasp) {
ksr->sigrefresh = dns_kasp_sigrefresh(kasp);
ksr->signdelay = dns_kasp_signdelay(kasp);
ksr->ttl = dns_kasp_dnskeyttl(kasp);
ksr->ttlds = dns_kasp_dsttl(kasp);
ksr->ttlsig = dns_kasp_zonemaxttl(kasp, true);
}
@@ -317,12 +323,13 @@ freerrset(dns_rdataset_t *rdataset) {
}
static void
create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
isc_stdtime_t inception, isc_stdtime_t active,
isc_stdtime_t *expiration) {
create_key(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_kasp_key_t *kaspkey,
dns_dnsseckeylist_t *keys, isc_stdtime_t inception,
isc_stdtime_t active, isc_stdtime_t *expiration) {
bool conflict = false;
bool freekey = false;
bool show_progress = true;
bool first = true;
char algstr[DNS_SECALG_FORMATSIZE];
char filename[PATH_MAX + 1];
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
@@ -331,9 +338,15 @@ create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
isc_buffer_t buf;
isc_result_t ret;
isc_stdtime_t prepub;
uint16_t flags = DNS_KEYOWNER_ZONE;
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
/* ZSK or KSK? */
if (ksr->ksk) {
flags |= DNS_KEYFLAG_KSK;
}
/* Check algorithm and size. */
dns_secalg_format(ksr->alg, algstr, sizeof(algstr));
if (!dst_algorithm_supported(ksr->alg)) {
@@ -407,6 +420,7 @@ create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
"Selecting key pair for bundle %s: ", timestr);
fflush(stderr);
}
first = false;
key = dk->key;
*expiration = inact;
goto output;
@@ -424,18 +438,18 @@ create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
ret = dns_keystore_keygen(
ksr->keystore, name, ksr->policy,
dns_rdataclass_in, mctx, ksr->alg, ksr->size,
DNS_KEYOWNER_ZONE, &key);
flags, &key);
} else if (show_progress) {
ret = dst_key_generate(
name, ksr->alg, ksr->size, 0, DNS_KEYOWNER_ZONE,
DNS_KEYPROTO_DNSSEC, dns_rdataclass_in, NULL,
mctx, &key, &progress);
ret = dst_key_generate(name, ksr->alg, ksr->size, 0,
flags, DNS_KEYPROTO_DNSSEC,
dns_rdataclass_in, NULL, mctx,
&key, &progress);
fflush(stderr);
} else {
ret = dst_key_generate(
name, ksr->alg, ksr->size, 0, DNS_KEYOWNER_ZONE,
DNS_KEYPROTO_DNSSEC, dns_rdataclass_in, NULL,
mctx, &key, NULL);
ret = dst_key_generate(name, ksr->alg, ksr->size, 0,
flags, DNS_KEYPROTO_DNSSEC,
dns_rdataclass_in, NULL, mctx,
&key, NULL);
}
if (ret != ISC_R_SUCCESS) {
@@ -472,15 +486,27 @@ create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
prepub = ksr->ttl + ksr->publishsafety + ksr->propagation;
dst_key_setttl(key, ksr->ttl);
dst_key_setnum(key, DST_NUM_LIFETIME, ksr->lifetime);
dst_key_setbool(key, DST_BOOL_KSK, false);
dst_key_setbool(key, DST_BOOL_ZSK, true);
dst_key_setbool(key, DST_BOOL_KSK, ksr->ksk);
dst_key_setbool(key, DST_BOOL_ZSK, !ksr->ksk);
dst_key_settime(key, DST_TIME_CREATED, ksr->now);
dst_key_settime(key, DST_TIME_PUBLISH, (active - prepub));
dst_key_settime(key, DST_TIME_ACTIVATE, active);
if (ksr->ksk) {
dns_keymgr_settime_syncpublish(key, kasp, first);
}
if (ksr->lifetime > 0) {
isc_stdtime_t inactive = (active + ksr->lifetime);
isc_stdtime_t remove = ksr->ttlsig + ksr->propagation +
ksr->retiresafety + ksr->signdelay;
isc_stdtime_t remove;
if (ksr->ksk) {
remove = ksr->ttlds + ksr->parentpropagation +
ksr->retiresafety;
dst_key_settime(key, DST_TIME_SYNCDELETE, inactive);
} else {
remove = ksr->ttlsig + ksr->propagation +
ksr->retiresafety + ksr->signdelay;
}
dst_key_settime(key, DST_TIME_INACTIVE, inactive);
dst_key_settime(key, DST_TIME_DELETE, (inactive + remove));
*expiration = inactive;
@@ -496,6 +522,8 @@ create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
isc_result_totext(ret));
}
first = false;
output:
isc_buffer_clear(&buf);
ret = dst_key_buildfilename(key, 0, NULL, &buf);
@@ -911,9 +939,12 @@ keygen(ksr_ctx_t *ksr) {
for (dns_kasp_key_t *kk = ISC_LIST_HEAD(dns_kasp_keys(kasp));
kk != NULL; kk = ISC_LIST_NEXT(kk, link))
{
if (dns_kasp_key_ksk(kk)) {
if (dns_kasp_key_ksk(kk) && !ksr->ksk) {
/* only ZSKs allowed */
continue;
} else if (dns_kasp_key_zsk(kk) && ksr->ksk) {
/* only KSKs allowed */
continue;
}
ksr->alg = dns_kasp_key_algorithm(kk);
ksr->lifetime = dns_kasp_key_lifetime(kk);
@@ -924,7 +955,7 @@ keygen(ksr_ctx_t *ksr) {
for (isc_stdtime_t inception = ksr->start, act = ksr->start;
inception < ksr->end; inception += ksr->lifetime)
{
create_zsk(ksr, kk, &keys, inception, act, &act);
create_key(ksr, kasp, kk, &keys, inception, act, &act);
if (ksr->lifetime == 0) {
/* unlimited lifetime, but not infinite loop */
break;
@@ -932,7 +963,7 @@ keygen(ksr_ctx_t *ksr) {
}
}
if (noop) {
fatal("policy '%s' has no zsks", ksr->policy);
fatal("no keys created for policy '%s'", ksr->policy);
}
/* Cleanup */
cleanup(&keys, kasp);
@@ -1216,7 +1247,7 @@ main(int argc, char *argv[]) {
isc_commandline_errprint = false;
#define OPTIONS "E:e:Ff:hi:K:k:l:v:V"
#define OPTIONS "E:e:Ff:hi:K:k:l:ov:V"
while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
switch (ch) {
case 'E':
@@ -1253,6 +1284,9 @@ main(int argc, char *argv[]) {
case 'l':
ksr.configfile = isc_commandline_argument;
break;
case 'o':
ksr.ksk = true;
break;
case 'V':
version(program);
break;

View File

@@ -21,7 +21,7 @@ dnssec-ksr - Create signed key response (SKR) files for offline KSK setups
Synopsis
~~~~~~~~
:program:`dnssec-ksr` [**-E** engine] [**-e** date/offset] [**-F**] [**-f** file] [**-h**] [**-i** date/offset] [**-K** directory] [**-k** policy] [**-l** file] [**-V**] [**-v** level] {command} {zone}
:program:`dnssec-ksr` [**-E** engine] [**-e** date/offset] [**-F**] [**-f** file] [**-h**] [**-i** date/offset] [**-K** directory] [**-k** policy] [**-l** file] [**-o**] [**-V**] [**-v** level] {command} {zone}
Description
~~~~~~~~~~~
@@ -88,6 +88,11 @@ Options
This option provides a configuration file that contains a ``dnssec-policy``
statement (matching the policy set with :option:`-k`).
.. option:: -o
Normally when pregenerating keys, ZSKs are created. When this option is
set, create KSKs instead.
.. option:: -V
This option prints version information.
@@ -110,9 +115,8 @@ Commands
.. option:: keygen
Pregenerate a number of zone signing keys (ZSKs), given a DNSSEC policy and
an interval. The number of generated keys depends on the interval and the
ZSK lifetime.
Pregenerate a number of keys, given a DNSSEC policy and an interval. The
number of generated keys depends on the interval and the key lifetime.
.. option:: request
@@ -135,7 +139,7 @@ occurred.
Examples
~~~~~~~~
When you need to generate keys for the zone "example.com" for the next year,
When you need to generate ZSKs for the zone "example.com" for the next year,
given a ``dnssec-policy`` named "mypolicy":
::
@@ -148,7 +152,8 @@ Creating a KSR for the same zone and period can be done with:
dnssec-ksr -i now -e +1y -k mypolicy -l named.conf request example.com > ksr.txt
Typically you would now transfer the KSR to the system that has access to the KSK.
Typically you would now transfer the KSR to the system that has access to
the KSK.
Signing the KSR created above can be done with:
@@ -156,7 +161,8 @@ Signing the KSR created above can be done with:
dnssec-ksr -i now -e +1y -k kskpolicy -l named.conf -f ksr.txt sign example.com
Make sure that the DNSSEC parameters in ``kskpolicy`` match those in ``mypolicy``.
Make sure that the DNSSEC parameters in ``kskpolicy`` match those
in ``mypolicy``.
See Also
~~~~~~~~

View File

@@ -55,28 +55,6 @@ def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
return [Key(name, keydir) for name in keystr.split()]
def keygen(zone, policy, keydir, when="now"):
keygen_command = [
os.environ.get("KEYGEN"),
"-l",
"ns1/named.conf",
"-fK",
"-K",
keydir,
"-k",
policy,
"-P",
when,
"-A",
when,
"-P",
"sync",
when,
zone,
]
return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
def ksr(zone, policy, action, options="", raise_on_exception=True):
ksr_command = [
os.environ.get("KSR"),
@@ -123,12 +101,22 @@ def check_keys(
# retired: zsk-lifetime
if lifetime is not None:
retired = active + lifetime
# removed: ttlsig + retire-safety + sign-delay + propagation
removed = retired + timedelta(days=10, hours=1, minutes=5)
if key.is_ksk():
# removed: ttlds + retire-safety + parent-propagation
removed = retired + timedelta(days=1, hours=2)
else:
# removed: ttlsig + retire-safety + sign-delay + propagation
removed = retired + timedelta(days=10, hours=1, minutes=5)
else:
retired = None
removed = None
goal = "hidden"
state_dnskey = "hidden"
state_zrrsig = "hidden"
state_krrsig = "hidden"
state_ds = "hidden"
if retired is None or between(now, published, retired):
goal = "omnipresent"
pubdelay = published + timedelta(hours=2, minutes=5)
@@ -136,24 +124,34 @@ def check_keys(
if between(now, published, pubdelay):
state_dnskey = "rumoured"
state_krrsig = "rumoured"
else:
state_dnskey = "omnipresent"
state_krrsig = "omnipresent"
if between(now, active, signdelay):
state_zrrsig = "rumoured"
if key.is_ksk():
state_ds = "hidden"
else:
state_zrrsig = "omnipresent"
else:
goal = "hidden"
state_dnskey = "hidden"
state_zrrsig = "hidden"
if between(now, active, signdelay):
state_zrrsig = "rumoured"
else:
state_zrrsig = "omnipresent"
with open(key.statefile, "r", encoding="utf-8") as file:
metadata = file.read()
assert f"Algorithm: {alg}" in metadata
assert f"Length: {size}" in metadata
assert "KSK: no" in metadata
assert "ZSK: yes" in metadata
if key.is_ksk():
assert "KSK: yes" in metadata
else:
assert "KSK: no" in metadata
if key.is_zsk():
assert "ZSK: yes" in metadata
else:
assert "ZSK: no" in metadata
assert f"Published: {published}" in metadata
assert f"Active: {active}" in metadata
@@ -169,9 +167,18 @@ def check_keys(
if with_state:
assert f"GoalState: {goal}" in metadata
assert f"DNSKEYState: {state_dnskey}" in metadata
assert f"ZRRSIGState: {state_zrrsig}" in metadata
assert "KRRSIGState:" not in metadata
assert "DSState:" not in metadata
if key.is_ksk():
assert f"KRRSIGState: {state_krrsig}" in metadata
assert f"DSState: {state_ds}" in metadata
else:
assert "KRRSIGState:" not in metadata
assert "DSState:" not in metadata
if key.is_zsk():
assert f"ZRRSIGState: {state_zrrsig}" in metadata
else:
assert "ZRRSIGState:" not in metadata
num += 1
@@ -264,6 +271,9 @@ def check_signedkeyresponse(
# expect ksks
for key in sorted(ksks):
published = key.get_timing("Publish")
if between(published, inception, next_bundle):
next_bundle = published
removed = key.get_timing("Delete", must_exist=False)
if published > inception:
@@ -271,6 +281,9 @@ def check_signedkeyresponse(
if removed is not None and inception >= removed:
continue
if between(removed, inception, next_bundle):
next_bundle = removed
# this ksk must be in the ksr
assert key.dnskey_equals(lines[line_no])
line_no += 1
@@ -399,7 +412,7 @@ def test_ksr_errors():
_, err = ksr(
"csk.test", "csk", "keygen", options="-K ns1 -e +2y", raise_on_exception=False
)
assert "dnssec-ksr: fatal: policy 'csk' has no zsks" in err
assert "dnssec-ksr: fatal: no keys created for policy 'csk'" in err
# check that 'dnssec-ksr request' errors on missing end date
_, err = ksr("common.test", "common", "request", raise_on_exception=False)
@@ -424,10 +437,12 @@ def test_ksr_common(servers):
# create ksk
kskdir = "ns1/offline"
out = keygen(zone, policy, kskdir)
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None)
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
out, _ = ksr(zone, policy, "keygen", options="-i now -e +1y")
zsks = keystr_to_keylist(out)
@@ -611,13 +626,13 @@ def test_ksr_lastbundle(servers):
# create ksk
kskdir = "ns1/offline"
now = KeyTimingMetadata.now()
offset = -timedelta(days=365)
when = now + offset - timedelta(days=1)
out = keygen(zone, policy, kskdir, when=str(when))
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1d -o")
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None, offset=offset)
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1d")
@@ -690,13 +705,13 @@ def test_ksr_inthemiddle(servers):
# create ksk
kskdir = "ns1/offline"
now = KeyTimingMetadata.now()
offset = -timedelta(days=365)
when = now + offset - timedelta(days=1)
out = keygen(zone, policy, kskdir, when=str(when))
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1y -o")
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None, offset=offset)
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1y")
@@ -771,7 +786,7 @@ def check_ksr_rekey_logs_error(server, zone, policy, offset, end):
now = KeyTimingMetadata.now()
then = now + offset
until = now + end
out = keygen(zone, policy, kskdir, when=str(then))
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i {then} -e {until} -o")
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
@@ -844,10 +859,12 @@ def test_ksr_unlimited(servers):
# create ksk
kskdir = "ns1/offline"
out = keygen(zone, policy, kskdir)
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +2y -o")
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 1
check_keys(ksks, None)
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +2y")
@@ -959,10 +976,28 @@ def test_ksr_twotone(servers):
# create ksk
kskdir = "ns1/offline"
out = keygen(zone, policy, kskdir)
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
ksks = keystr_to_keylist(out, kskdir)
assert len(ksks) == 2
ksks_defalg = []
ksks_altalg = []
for ksk in ksks:
alg = ksk.get_metadata("Algorithm")
if alg == os.environ.get("DEFAULT_ALGORITHM_NUMBER"):
ksks_defalg.append(ksk)
elif alg == os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER"):
ksks_altalg.append(ksk)
assert len(ksks_defalg) == 1
assert len(ksks_altalg) == 1
check_keys(ksks_defalg, None)
alg = os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER")
size = os.environ.get("ALTERNATIVE_BITS")
check_keys(ksks_altalg, None, alg, size)
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
zskdir = "ns1"
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")

View File

@@ -24,6 +24,17 @@
ISC_LANG_BEGINDECLS
void
dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first);
/*%<
* Set the SyncPublish time (when the DS may be submitted to the parent).
* If 'first' is true, also make sure that the zone signatures are omnipresent.
*
* Requires:
*\li 'key' is a valid DNSSEC key.
*\li 'kasp' is a valid DNSSEC policy.
*/
isc_result_t
dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
isc_mem_t *mctx, dns_dnsseckeylist_t *keyring,

View File

@@ -164,26 +164,25 @@ keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) {
* Set the SyncPublish time (when the DS may be submitted to the parent).
*
*/
static void
keymgr_settime_syncpublish(dns_dnsseckey_t *key, dns_kasp_t *kasp, bool first) {
void
dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first) {
isc_stdtime_t published, syncpublish;
bool ksk = false;
isc_result_t ret;
REQUIRE(key != NULL);
REQUIRE(key->key != NULL);
ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &published);
ret = dst_key_gettime(key, DST_TIME_PUBLISH, &published);
if (ret != ISC_R_SUCCESS) {
return;
}
ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk);
if (ret != ISC_R_SUCCESS || !ksk) {
return;
}
syncpublish = published + dst_key_getttl(key->key) +
syncpublish = published + dst_key_getttl(key) +
dns_kasp_zonepropagationdelay(kasp) +
dns_kasp_publishsafety(kasp);
if (first) {
@@ -197,7 +196,7 @@ keymgr_settime_syncpublish(dns_dnsseckey_t *key, dns_kasp_t *kasp, bool first) {
syncpublish = zrrsig_present;
}
}
dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpublish);
dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncpublish);
}
/*
@@ -1863,7 +1862,7 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
*/
dst_key_settime(new_key->key, DST_TIME_PUBLISH, now);
dst_key_settime(new_key->key, DST_TIME_ACTIVATE, now);
keymgr_settime_syncpublish(new_key, kasp, true);
dns_keymgr_settime_syncpublish(new_key->key, kasp, true);
active = now;
} else {
/*
@@ -1895,7 +1894,7 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
}
dst_key_settime(new_key->key, DST_TIME_PUBLISH, prepub);
dst_key_settime(new_key->key, DST_TIME_ACTIVATE, active);
keymgr_settime_syncpublish(new_key, kasp, false);
dns_keymgr_settime_syncpublish(new_key->key, kasp, false);
/*
* Retire predecessor.