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.
#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>
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;
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);
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);
}
}
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. */
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)) {
"Selecting key pair for bundle %s: ", timestr);
fflush(stderr);
}
+ first = false;
key = dk->key;
*expiration = inact;
goto output;
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) {
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;
isc_result_totext(ret));
}
+ first = false;
+
output:
isc_buffer_clear(&buf);
ret = dst_key_buildfilename(key, 0, NULL, &buf);
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);
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;
}
}
if (noop) {
- fatal("policy '%s' has no zsks", ksr->policy);
+ fatal("no keys created for policy '%s'", ksr->policy);
}
/* Cleanup */
cleanup(&keys, kasp);
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':
case 'l':
ksr.configfile = isc_commandline_argument;
break;
+ case 'o':
+ ksr.ksk = true;
+ break;
case 'V':
version(program);
break;
Synopsis
~~~~~~~~
-:program:`dnssec-ksr` [**-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** date/offset] [**-F**] [**-f** file] [**-h**] [**-i** date/offset] [**-K** directory] [**-k** policy] [**-l** file] [**-o**] [**-V**] [**-v** level] {command} {zone}
Description
~~~~~~~~~~~
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.
.. 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
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":
::
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:
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
~~~~~~~~
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"),
# 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)
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
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
# 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:
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
_, 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)
# 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)
# 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")
# 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")
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
# 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")
# 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")
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,
* 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) {
syncpublish = zrrsig_present;
}
}
- dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpublish);
+ dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncpublish);
}
/*
*/
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 {
/*
}
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.