diff --git a/bin/dnssec/dnssec-ksr.c b/bin/dnssec/dnssec-ksr.c index 9919fd4210..10982f6c22 100644 --- a/bin/dnssec/dnssec-ksr.c +++ b/bin/dnssec/dnssec-ksr.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -61,16 +62,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; @@ -139,6 +143,15 @@ usage(int ret) { exit(ret); } +static isc_stdtime_t +between(isc_stdtime_t t, isc_stdtime_t start, isc_stdtime_t end) { + isc_stdtime_t r = end; + if (t > 0 && t > start && t < end) { + r = t; + } + return (r); +} + static void checkparams(ksr_ctx_t *ksr, const char *command) { if (ksr->configfile == NULL) { @@ -239,6 +252,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); @@ -246,6 +260,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); } @@ -313,9 +328,9 @@ 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; @@ -327,9 +342,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)) { @@ -420,18 +441,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) { @@ -468,15 +489,28 @@ 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, + (inception == ksr->start)); + } + 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; @@ -610,12 +644,13 @@ fail: return (next_bundle); } -static void +static isc_stdtime_t sign_rrset(ksr_ctx_t *ksr, isc_stdtime_t inception, isc_stdtime_t expiration, dns_rdataset_t *rrset, dns_dnsseckeylist_t *keys) { dns_rdatalist_t *rrsiglist = NULL; dns_rdataset_t rrsigset = DNS_RDATASET_INIT; isc_result_t ret; + isc_stdtime_t next_bundle = expiration; UNUSED(ksr); @@ -659,6 +694,25 @@ sign_rrset(ksr_ctx_t *ksr, isc_stdtime_t inception, isc_stdtime_t expiration, unsigned char rdatabuf[SIG_FORMATSIZE]; isc_stdtime_t clockskew = inception - 3600; + isc_stdtime_t pub = 0, act = 0, inact = 0, del = 0; + + /* Determine next bundle. */ + (void)dst_key_gettime(dk->key, DST_TIME_PUBLISH, &pub); + (void)dst_key_gettime(dk->key, DST_TIME_ACTIVATE, &act); + (void)dst_key_gettime(dk->key, DST_TIME_INACTIVE, &inact); + (void)dst_key_gettime(dk->key, DST_TIME_DELETE, &del); + next_bundle = between(pub, inception, next_bundle); + next_bundle = between(act, inception, next_bundle); + next_bundle = between(inact, inception, next_bundle); + next_bundle = between(del, inception, next_bundle); + + if (act > inception) { + continue; + } + if (inact != 0 && inception >= inact) { + continue; + } + rrsig = isc_mem_get(mctx, sizeof(*rrsig)); dns_rdata_init(rrsig); isc_buffer_init(&buf, rdatabuf, sizeof(rdatabuf)); @@ -680,21 +734,25 @@ sign_rrset(ksr_ctx_t *ksr, isc_stdtime_t inception, isc_stdtime_t expiration, dns_rdatalist_tordataset(rrsiglist, &rrsigset); print_rdata(&rrsigset); freerrset(&rrsigset); + + return (next_bundle); } /* * Create the DNSKEY, CDS, and CDNSKEY records beloing to the KSKs * listed in 'keys'. */ -static void -create_ksk(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_dnsseckeylist_t *keys, - dns_rdataset_t *dnskeyset, dns_rdataset_t *cdnskeyset, - dns_rdataset_t *cdsset) { +static isc_stdtime_t +get_keymaterial(ksr_ctx_t *ksr, dns_kasp_t *kasp, isc_stdtime_t inception, + isc_stdtime_t next_inception, dns_dnsseckeylist_t *keys, + dns_rdataset_t *dnskeyset, dns_rdataset_t *cdnskeyset, + dns_rdataset_t *cdsset) { + dns_kasp_digestlist_t digests = dns_kasp_digests(kasp); dns_rdatalist_t *dnskeylist = isc_mem_get(mctx, sizeof(*dnskeylist)); dns_rdatalist_t *cdnskeylist = isc_mem_get(mctx, sizeof(*cdnskeylist)); dns_rdatalist_t *cdslist = isc_mem_get(mctx, sizeof(*cdslist)); isc_result_t ret = ISC_R_SUCCESS; - dns_kasp_digestlist_t digests = dns_kasp_digests(kasp); + isc_stdtime_t next_bundle = next_inception; dns_rdatalist_init(dnskeylist); dnskeylist->rdclass = dns_rdataclass_in; @@ -714,31 +772,73 @@ create_ksk(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_dnsseckeylist_t *keys, for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(*keys); dk != NULL; dk = ISC_LIST_NEXT(dk, link)) { + bool published = true; isc_buffer_t buf; isc_buffer_t *newbuf; dns_rdata_t *rdata; isc_region_t r; isc_region_t rcds; + isc_stdtime_t pub = 0, del = 0; unsigned char kskbuf[DST_KEY_MAXSIZE]; unsigned char cdnskeybuf[DST_KEY_MAXSIZE]; unsigned char cdsbuf[DNS_DS_BUFFERSIZE]; /* KSK */ - newbuf = NULL; - rdata = isc_mem_get(mctx, sizeof(*rdata)); - dns_rdata_init(rdata); + (void)dst_key_gettime(dk->key, DST_TIME_PUBLISH, &pub); + (void)dst_key_gettime(dk->key, DST_TIME_DELETE, &del); + next_bundle = between(pub, inception, next_bundle); + next_bundle = between(del, inception, next_bundle); - isc_buffer_init(&buf, kskbuf, sizeof(kskbuf)); - CHECK(dst_key_todns(dk->key, &buf)); - isc_buffer_usedregion(&buf, &r); - isc_buffer_allocate(mctx, &newbuf, r.length); - isc_buffer_putmem(newbuf, r.base, r.length); - isc_buffer_usedregion(newbuf, &r); - dns_rdata_fromregion(rdata, dns_rdataclass_in, - dns_rdatatype_dnskey, &r); - ISC_LIST_APPEND(dnskeylist->rdata, rdata, link); - ISC_LIST_APPEND(cleanup_list, newbuf, link); - isc_buffer_clear(newbuf); + if (pub > inception) { + published = false; + } + if (del != 0 && inception >= del) { + published = false; + } + + if (published) { + newbuf = NULL; + rdata = isc_mem_get(mctx, sizeof(*rdata)); + dns_rdata_init(rdata); + + isc_buffer_init(&buf, kskbuf, sizeof(kskbuf)); + CHECK(dst_key_todns(dk->key, &buf)); + isc_buffer_usedregion(&buf, &r); + isc_buffer_allocate(mctx, &newbuf, r.length); + isc_buffer_putmem(newbuf, r.base, r.length); + isc_buffer_usedregion(newbuf, &r); + dns_rdata_fromregion(rdata, dns_rdataclass_in, + dns_rdatatype_dnskey, &r); + ISC_LIST_APPEND(dnskeylist->rdata, rdata, link); + ISC_LIST_APPEND(cleanup_list, newbuf, link); + isc_buffer_clear(newbuf); + } + + published = true; + if (dns_kasp_cdnskey(kasp) || !ISC_LIST_EMPTY(digests)) { + pub = 0; + del = 0; + (void)dst_key_gettime(dk->key, DST_TIME_SYNCPUBLISH, + &pub); + (void)dst_key_gettime(dk->key, DST_TIME_SYNCDELETE, + &del); + + next_bundle = between(pub, inception, next_bundle); + next_bundle = between(del, inception, next_bundle); + + if (pub != 0 && pub > inception) { + published = false; + } + if (del != 0 && inception >= del) { + published = false; + } + } else { + published = false; + } + + if (!published) { + continue; + } /* CDNSKEY */ newbuf = NULL; @@ -792,35 +892,98 @@ create_ksk(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_dnsseckeylist_t *keys, dns_rdatalist_tordataset(dnskeylist, dnskeyset); dns_rdatalist_tordataset(cdnskeylist, cdnskeyset); dns_rdatalist_tordataset(cdslist, cdsset); - return; + + return (next_bundle); fail: fatal("failed to create KSK/CDS/CDNSKEY"); + return (0); } static void -sign_bundle(ksr_ctx_t *ksr, isc_stdtime_t inception, - isc_stdtime_t next_inception, dns_rdatalist_t *rdatalist, - dns_rdataset_t *cds, dns_rdataset_t *cdnskey, +sign_bundle(ksr_ctx_t *ksr, dns_kasp_t *kasp, isc_stdtime_t inception, + isc_stdtime_t next_inception, dns_rdatalist_t *zsklist, dns_dnsseckeylist_t *keys) { - dns_rdataset_t rrset = DNS_RDATASET_INIT; - isc_stdtime_t expiration; + isc_stdtime_t expiration = inception + ksr->sigvalidity; + isc_stdtime_t next_bundle = next_inception; + dns_rdataset_t zsk; + + dns_rdataset_init(&zsk); + dns_rdatalist_tordataset(zsklist, &zsk); - dns_rdataset_init(&rrset); - dns_rdatalist_tordataset(rdatalist, &rrset); - expiration = inception + ksr->sigvalidity; while (inception <= next_inception) { - sign_rrset(ksr, inception, expiration, &rrset, keys); - if (dns_rdataset_count(cdnskey) > 0) { - sign_rrset(ksr, inception, expiration, cdnskey, keys); + isc_stdtime_t next_time = next_bundle; + + /* DNSKEY RRset */ + dns_rdatalist_t *dnskeylist; + dnskeylist = isc_mem_get(mctx, sizeof(*dnskeylist)); + dns_rdatalist_init(dnskeylist); + dnskeylist->rdclass = dns_rdataclass_in; + dnskeylist->type = dns_rdatatype_dnskey; + dnskeylist->ttl = ksr->ttl; + + dns_rdataset_t ksk, cdnskey, cds, rrset; + dns_rdataset_init(&ksk); + dns_rdataset_init(&cdnskey); + dns_rdataset_init(&cds); + dns_rdataset_init(&rrset); + next_time = get_keymaterial(ksr, kasp, inception, next_time, + keys, &ksk, &cdnskey, &cds); + if (next_bundle > next_time) { + next_bundle = next_time; } - if (dns_rdataset_count(cds) > 0) { - sign_rrset(ksr, inception, expiration, cds, keys); + + for (isc_result_t r = dns_rdatalist_first(&ksk); + r == ISC_R_SUCCESS; r = dns_rdatalist_next(&ksk)) + { + dns_rdata_t *clone = isc_mem_get(mctx, sizeof(*clone)); + dns_rdata_init(clone); + dns_rdatalist_current(&ksk, clone); + ISC_LIST_APPEND(dnskeylist->rdata, clone, link); } + + for (isc_result_t r = dns_rdatalist_first(&zsk); + r == ISC_R_SUCCESS; r = dns_rdatalist_next(&zsk)) + { + dns_rdata_t *clone = isc_mem_get(mctx, sizeof(*clone)); + dns_rdata_init(clone); + dns_rdatalist_current(&zsk, clone); + ISC_LIST_APPEND(dnskeylist->rdata, clone, link); + } + + dns_rdatalist_tordataset(dnskeylist, &rrset); + next_time = sign_rrset(ksr, inception, expiration, &rrset, + keys); + if (next_bundle > next_time) { + next_bundle = next_time; + } + freerrset(&ksk); + freerrset(&rrset); + + /* CDNSKEY */ + if (dns_rdataset_count(&cdnskey) > 0) { + (void)sign_rrset(ksr, inception, expiration, &cdnskey, + keys); + } + freerrset(&cdnskey); + + /* CDS */ + if (dns_rdataset_count(&cds) > 0) { + (void)sign_rrset(ksr, inception, expiration, &cds, + keys); + } + freerrset(&cds); + + /* Next response bundle. */ inception = expiration - ksr->sigrefresh; + if (inception > next_bundle) { + inception = next_bundle; + } expiration = inception + ksr->sigvalidity; + next_bundle = expiration; } - freerrset(&rrset); + + freerrset(&zsk); } static isc_result_t @@ -907,9 +1070,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); @@ -920,7 +1086,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; @@ -928,7 +1094,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); @@ -1007,9 +1173,6 @@ sign(ksr_ctx_t *ksr) { dns_dnsseckeylist_t keys; dns_kasp_t *kasp = NULL; dns_rdatalist_t *rdatalist = NULL; - dns_rdataset_t ksk = DNS_RDATASET_INIT; - dns_rdataset_t cdnskey = DNS_RDATASET_INIT; - dns_rdataset_t cds = DNS_RDATASET_INIT; isc_result_t ret; isc_stdtime_t inception; isc_lex_t *lex = NULL; @@ -1042,9 +1205,6 @@ sign(ksr_ctx_t *ksr) { isc_result_totext(ret)); } - /* KSK, CDS and CDNSKEY */ - create_ksk(ksr, kasp, &keys, &ksk, &cdnskey, &cds); - for (ret = isc_lex_gettoken(lex, opt, &token); ret == ISC_R_SUCCESS; ret = isc_lex_gettoken(lex, opt, &token)) { @@ -1094,8 +1254,8 @@ sign(ksr_ctx_t *ksr) { if (have_bundle) { /* Sign previous bundle */ - sign_bundle(ksr, inception, next_inception, - rdatalist, &cds, &cdnskey, &keys); + sign_bundle(ksr, kasp, inception, + next_inception, rdatalist, &keys); fprintf(stdout, "\n"); } @@ -1105,15 +1265,7 @@ sign(ksr_ctx_t *ksr) { rdatalist->rdclass = dns_rdataclass_in; rdatalist->type = dns_rdatatype_dnskey; rdatalist->ttl = ksr->ttl; - for (isc_result_t r = dns_rdatalist_first(&ksk); - r == ISC_R_SUCCESS; r = dns_rdatalist_next(&ksk)) - { - dns_rdata_t *clone = - isc_mem_get(mctx, sizeof(*clone)); - dns_rdata_init(clone); - dns_rdatalist_current(&ksk, clone); - ISC_LIST_APPEND(rdatalist->rdata, clone, link); - } + inception = next_inception; have_bundle = true; @@ -1172,8 +1324,7 @@ sign(ksr_ctx_t *ksr) { /* Final bundle */ if (have_bundle && rdatalist != NULL) { - sign_bundle(ksr, inception, ksr->end, rdatalist, &cds, &cdnskey, - &keys); + sign_bundle(ksr, kasp, inception, ksr->end, rdatalist, &keys); } else { fatal("bad KSR file %s(%lu): no bundles", ksr->file, isc_lex_getsourceline(lex)); @@ -1185,11 +1336,6 @@ sign(ksr_ctx_t *ksr) { timestr, PACKAGE_VERSION); fail: - /* Clean up */ - freerrset(&ksk); - freerrset(&cdnskey); - freerrset(&cds); - isc_lex_destroy(&lex); cleanup(&keys, kasp); } @@ -1212,7 +1358,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': @@ -1249,6 +1395,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; diff --git a/bin/dnssec/dnssec-ksr.rst b/bin/dnssec/dnssec-ksr.rst index 8a0f507770..b8d6c43d95 100644 --- a/bin/dnssec/dnssec-ksr.rst +++ b/bin/dnssec/dnssec-ksr.rst @@ -21,7 +21,7 @@ dnssec-ksr - Create signed key response (SKR) files for offline KSK setups Synopsis ~~~~~~~~ -:program:`dnssec-ksr` [**-e** date/offset] [**-F**] [**-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 ~~~~~~~~~~~ @@ -51,6 +51,10 @@ Options mode if the underlying crytographic library supports running in FIPS mode. +.. option:: -f + + This option sets the SKR file to be signed when issuing a ``sign`` command. + .. option:: -h This option prints a short summary of the options and arguments to @@ -76,6 +80,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. @@ -98,9 +107,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 @@ -123,7 +131,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": :: @@ -136,7 +144,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: @@ -144,7 +153,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 ~~~~~~~~ diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 7dd2c1d502..1fbb489319 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -431,8 +431,11 @@ def _check_dnskeys(dnskeys, keys, cdnskey=False): has_dnskey = True break - assert has_dnskey - numkeys += 1 + if not cdnskey: + assert has_dnskey + + if has_dnskey: + numkeys += 1 return numkeys @@ -541,17 +544,17 @@ def check_apex(server, zone, ksks, zsks): # test cdnskey query cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY) - assert len(cdnskeys) > 0 check_dnskeys(cdnskeys, ksks, zsks, cdnskey=True) - assert len(rrsigs) > 0 - check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks) + if len(cdnskeys) > 0: + assert len(rrsigs) > 0 + check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks) # test cds query cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS) - assert len(cds) > 0 check_cds(cds, ksks) - assert len(rrsigs) > 0 - check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks) + if len(cds) > 0: + assert len(rrsigs) > 0 + check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks) def check_subdomain(server, zone, ksks, zsks): diff --git a/bin/tests/system/ksr/ns1/named.conf.in b/bin/tests/system/ksr/ns1/named.conf.in index 75710b42dc..7283069321 100644 --- a/bin/tests/system/ksr/ns1/named.conf.in +++ b/bin/tests/system/ksr/ns1/named.conf.in @@ -85,3 +85,11 @@ dnssec-policy "two-tone" { zsk lifetime P3M algorithm @DEFAULT_ALGORITHM@; }; }; + +dnssec-policy "ksk-roll" { + offline-ksk yes; + keys { + ksk lifetime P6M algorithm @DEFAULT_ALGORITHM@; + zsk lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + }; +}; diff --git a/bin/tests/system/ksr/ns1/setup.sh b/bin/tests/system/ksr/ns1/setup.sh index a04b01a23e..2179ab251d 100644 --- a/bin/tests/system/ksr/ns1/setup.sh +++ b/bin/tests/system/ksr/ns1/setup.sh @@ -26,3 +26,4 @@ cp template.db.in last-bundle.test.db cp template.db.in in-the-middle.test.db cp template.db.in unlimited.test.db cp template.db.in two-tone.test.db +cp template.db.in ksk-roll.test.db diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py index 793df81c4c..8fcdbdb7d8 100644 --- a/bin/tests/system/ksr/tests_ksr.py +++ b/bin/tests/system/ksr/tests_ksr.py @@ -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 @@ -311,10 +324,17 @@ def check_signedkeyresponse( line_no += 1 # expect cdnskey + have_cdnskey = False if cdnskey: for key in sorted(ksks): - published = key.get_timing("Publish") - removed = key.get_timing("Delete", must_exist=False) + published = key.get_timing("SyncPublish") + if between(published, inception, next_bundle): + next_bundle = published + + removed = key.get_timing("SyncDelete", must_exist=False) + if between(removed, inception, next_bundle): + next_bundle = removed + if published > inception: continue if removed is not None and inception >= removed: @@ -323,7 +343,9 @@ def check_signedkeyresponse( # the cdnskey of this ksk must be in the ksr assert key.dnskey_equals(lines[line_no], cdnskey=True) line_no += 1 + have_cdnskey = True + if have_cdnskey: # expect rrsig(cdnskey) for key in sorted(ksks): active = key.get_timing("Activate") @@ -341,10 +363,17 @@ def check_signedkeyresponse( line_no += 1 # expect cds + have_cds = False if cds != "": for key in sorted(ksks): - published = key.get_timing("Publish") - removed = key.get_timing("Delete", must_exist=False) + published = key.get_timing("SyncPublish") + if between(published, inception, next_bundle): + next_bundle = published + + removed = key.get_timing("SyncDelete", must_exist=False) + if between(removed, inception, next_bundle): + next_bundle = removed + if published > inception: continue if removed is not None and inception >= removed: @@ -355,7 +384,9 @@ def check_signedkeyresponse( for alg in expected_cds: assert key.cds_equals(lines[line_no], alg.strip()) line_no += 1 + have_cds = True + if have_cds: # expect rrsig(cds) for key in sorted(ksks): active = key.get_timing("Activate") @@ -399,7 +430,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 +455,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 +644,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 +723,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 +804,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 +877,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 +994,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") @@ -1050,3 +1103,77 @@ def test_ksr_twotone(servers): isctest.kasp.check_apex(ns1, zone, ksks, zsks) # - check subdomain isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) + + +def test_ksr_kskroll(servers): + zone = "ksk-roll.test" + policy = "ksk-roll" + n = 1 + + # create ksk + kskdir = "ns1/offline" + out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o") + ksks = keystr_to_keylist(out, kskdir) + assert len(ksks) == 2 + + lifetime = timedelta(days=31 * 6) + check_keys(ksks, lifetime) + + # 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") + zsks = keystr_to_keylist(out, zskdir) + assert len(zsks) == 1 + + check_keys(zsks, None) + + # check that 'dnssec-ksr request' creates correct ksr + now = zsks[0].get_timing("Created") + until = now + timedelta(days=365) + out, _ = ksr(zone, policy, "request", options=f"-K {zskdir} -i {now} -e +1y") + + fname = f"{zone}.ksr.{n}" + with open(fname, "w", encoding="utf-8") as file: + file.write(out) + + check_keysigningrequest(out, zsks, now, until) + + # check that 'dnssec-ksr sign' creates correct skr + out, _ = ksr( + zone, policy, "sign", options=f"-K {kskdir} -f {fname} -i {now} -e +1y" + ) + + skrfile = f"{zone}.skr.{n}" + with open(skrfile, "w", encoding="utf-8") as file: + file.write(out) + + refresh = -432000 # 5 days + check_signedkeyresponse(out, zone, ksks, zsks, now, until, refresh) + + # add zone + ns1 = servers["ns1"] + ns1.rndc( + f"addzone {zone} " + + "{ type primary; file " + + f'"{zone}.db"; dnssec-policy {policy}; ' + + "};", + log=False, + ) + + # import skr + shutil.copyfile(skrfile, f"ns1/{skrfile}") + ns1.rndc(f"skr -import {skrfile} {zone}", log=False) + + # test zone is correctly signed + # - check rndc dnssec -status output + isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy) + # - zone is signed + isctest.kasp.check_zone_is_signed(ns1, zone) + # - dnssec_verify + isctest.kasp.check_dnssec_verify(ns1, zone) + # - check keys + check_keys(zsks, None, with_state=True) + # - check apex + isctest.kasp.check_apex(ns1, zone, ksks, zsks) + # - check subdomain + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h index 61cf3df7aa..deb007623f 100644 --- a/lib/dns/include/dns/keymgr.h +++ b/lib/dns/include/dns/keymgr.h @@ -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, diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c index f1cc28ed25..1f5ab70c9b 100644 --- a/lib/dns/keymgr.c +++ b/lib/dns/keymgr.c @@ -163,26 +163,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) { @@ -196,7 +195,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); } /* @@ -1862,7 +1861,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 { /* @@ -1894,7 +1893,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.