diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index 87c9254008..9aeeac10cc 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -115,7 +115,7 @@ dlz string { dnssec-policy string { dnskey-ttl duration; - keys { ( csk | ksk | zsk ) ( key-directory ) lifetime duration + keys { ( csk | ksk | zsk ) ( key-directory ) lifetime ( duration | unlimited ) algorithm integer [ integer ]; ... }; max-zone-ttl duration; parent-ds-ttl duration; diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf index b4d3c1e562..19420f2dfd 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -19,7 +19,7 @@ dnssec-policy "test" { keys { ksk key-directory lifetime P1Y algorithm 13 256; zsk key-directory lifetime P30D algorithm 13; - csk key-directory lifetime P30D algorithm 8 2048; + csk key-directory lifetime unlimited algorithm 8 2048; }; max-zone-ttl 86400; parent-ds-ttl 7200; diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in index 2f78aa8146..38a656b0d3 100644 --- a/bin/tests/system/kasp/ns3/named.conf.in +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -45,6 +45,13 @@ zone "default.kasp" { dnssec-policy "default"; }; +/* Key lifetime unlimited. */ +zone "unlimited.kasp" { + type master; + file "unlimited.kasp.db"; + dnssec-policy "unlimited"; +}; + /* A master zone with dnssec-policy, no keys created. */ zone "rsasha1.kasp" { type master; diff --git a/bin/tests/system/kasp/ns3/policies/kasp.conf b/bin/tests/system/kasp/ns3/policies/kasp.conf index fa60476e65..e0ce931d90 100644 --- a/bin/tests/system/kasp/ns3/policies/kasp.conf +++ b/bin/tests/system/kasp/ns3/policies/kasp.conf @@ -9,6 +9,14 @@ * information regarding copyright ownership. */ +dnssec-policy "unlimited" { + dnskey-ttl 1234; + + keys { + csk key-directory lifetime unlimited algorithm 13; + }; +}; + dnssec-policy "rsasha1" { dnskey-ttl 1234; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index c39df821af..a1c1806839 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -43,7 +43,8 @@ U="UNRETENTIVE" # Set up zones that will be initially signed. # for zn in default rsasha1 dnssec-keygen some-keys legacy-keys pregenerated \ - rumoured rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 inherit + rumoured rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 \ + inherit unlimited do setup "${zn}.kasp" cp template.db.in "$zonefile" diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 7a7b4d9eb0..47d8e05747 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -1026,6 +1026,22 @@ check_keys check_apex check_subdomain +# +# Zone: unlimited.kasp. +# +zone_properties "ns3" "unlimited.kasp" "unlimited" "1234" "1" "10.53.0.3" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_clear "KEY2" +key_clear "KEY3" +# The first key is immediately published and activated. +key_timings "KEY1" "published" "active" "none" "none" "none" +# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. +key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" +check_keys +check_apex +check_subdomain +dnssec_verify + # # Zone: inherit.kasp. # @@ -1451,7 +1467,7 @@ check_subdomain # ns5/override.inherit.signed # ns5/inherit.override.signed key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" -key_timings "KEY1" "published" "active" "none" "none" "none" "none" +key_timings "KEY1" "published" "active" "none" "none" "none" key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" zone_properties "ns2" "signed.tld" "default" "3600" "1" "10.53.0.2" @@ -1496,7 +1512,7 @@ dnssec_verify # ns5/override.override.unsigned # ns5/override.none.unsigned key_properties "KEY1" "csk" "0" "14" "ECDSAP384SHA384" "384" "yes" "yes" -key_timings "KEY1" "published" "active" "none" "none" "none" "none" +key_timings "KEY1" "published" "active" "none" "none" "none" key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" zone_properties "ns4" "inherit.inherit.signed" "test" "3600" "1" "10.53.0.4" diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 1fd3e16e63..28f88f89da 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -11113,7 +11113,7 @@ example.com CNAME rpz-tcp-only. keys { - ksk key-directory lifetime P5Y algorithm 8 2048; + ksk key-directory lifetime unlimited algorithm 8 2048; zsk key-directory lifetime P30D algorithm 8; csk key-directory lifetime P6MT12H3M15S algorithm 13; }; @@ -11133,16 +11133,27 @@ example.com CNAME rpz-tcp-only. key-directory. - The third token tells how long the key may be used. In the - example the first key has a lifetime of 5 years, the second - key may be used for 30 days and the third key has a rather - peculiar lifetime of 6 months, 12 hours, 3 minutes and 15 - seconds. + The lifetime parameter specifies how + long a key may be used before rolling over. In the + example above, the first key will have an unlimited + lifetime, the second key may be used for 30 days, and the + third key has a rather peculiar lifetime of 6 months, + 12 hours, 3 minutes and 15 seconds. A lifetime of 0 + seconds is the same as unlimited. - The last token(s) are the key's algorithm and algorithm - length. The length may be omitted as shown in the - example for the second and third key. + Note that the lifetime of a key may be extended if + retiring it too soon would cause validation failures: + for example, if the key were configured to roll more + frequently than its TTL, its lifetime would + automatically be extended to account for this. + + + The algorithm parameter(s) are the key's + algorithm, expressed numerically, and its size in bits. The + size may be omitted, as shown in the example for the + second and third keys; in this case an appropriate + default size will be used. diff --git a/doc/arm/dnssec-policy.grammar.xml b/doc/arm/dnssec-policy.grammar.xml index d3e21a4918..08e3890e6b 100644 --- a/doc/arm/dnssec-policy.grammar.xml +++ b/doc/arm/dnssec-policy.grammar.xml @@ -14,7 +14,7 @@ dnssec-policy string { dnskey-ttl duration; - keys { ( csk | ksk | zsk ) key-directory lifetime duration algorithm integer [ integer ] ; ... }; + keys { ( csk | ksk | zsk ) key-directory lifetime ( duration | unlimited ) algorithm integer [ integer ] ; ... }; max-zone-ttl duration; parent-ds-ttl duration; parent-propagation-delay duration; diff --git a/doc/design/dnssec-policy b/doc/design/dnssec-policy index ae16195518..69951814dd 100644 --- a/doc/design/dnssec-policy +++ b/doc/design/dnssec-policy @@ -199,9 +199,9 @@ is referred to as a CSK. Below is an example configuration for the three types of keys: ``` keys { - ksk key-directory lifetime P5Y algorithm ECDSAP256SHA256; + ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256; zsk key-directory lifetime P30D algorithm ECDSAP256SHA256; - csk key-directory lifetime PT0S algorithm 8 2048; + csk key-directory lifetime P5Y algorithm 8 2048; }; ``` diff --git a/doc/misc/dnssec-policy.default.conf b/doc/misc/dnssec-policy.default.conf index 58283f2a0e..1e12aec43e 100644 --- a/doc/misc/dnssec-policy.default.conf +++ b/doc/misc/dnssec-policy.default.conf @@ -2,7 +2,7 @@ dnssec-policy "default" { // Keys keys { - csk key-directory lifetime 0 algorithm 13; + csk key-directory lifetime unlimited algorithm 13; }; // Key timings diff --git a/doc/misc/options b/doc/misc/options index 57b2a4393a..cf66ac3a97 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -23,7 +23,7 @@ dlz { dnssec-policy { dnskey-ttl ; - keys { ( csk | ksk | zsk ) ( key-directory ) lifetime + keys { ( csk | ksk | zsk ) ( key-directory ) lifetime ( | unlimited ) algorithm [ ]; ... }; max-zone-ttl ; parent-ds-ttl ; diff --git a/doc/misc/options.active b/doc/misc/options.active index 0adfbfa9ec..20fc8d3b37 100644 --- a/doc/misc/options.active +++ b/doc/misc/options.active @@ -23,7 +23,7 @@ dlz { dnssec-policy { dnskey-ttl ; - keys { ( csk | ksk | zsk ) ( key-directory ) lifetime + keys { ( csk | ksk | zsk ) ( key-directory ) lifetime ( | unlimited ) algorithm [ ]; ... }; max-zone-ttl ; parent-ds-ttl ; diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h index ad3dd81d17..1b02820ba0 100644 --- a/lib/isccfg/include/isccfg/grammar.h +++ b/lib/isccfg/include/isccfg/grammar.h @@ -173,6 +173,7 @@ struct cfg_duration { */ uint32_t parts[7]; bool iso8601; + bool unlimited; }; /*% @@ -344,6 +345,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_unsupported; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_fixedpoint; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_percentage; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_duration; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_duration_or_unlimited; /*@}*/ isc_result_t @@ -535,6 +537,13 @@ cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, void cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj); +isc_result_t +cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret); + +void +cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj); + isc_result_t cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 036761bdb0..8001c122e7 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -78,7 +78,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp) if (config == NULL) { /* We are creating a key reference for the default kasp. */ key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK; - key->lifetime = 0; + key->lifetime = 0; /* unlimited */ key->algorithm = DNS_KEYALG_ECDSA256; key->length = -1; } else { @@ -94,10 +94,16 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp) key->role |= DNS_KASP_KEY_ROLE_KSK; key->role |= DNS_KASP_KEY_ROLE_ZSK; } - key->lifetime = cfg_obj_asduration( - cfg_tuple_get(config, "lifetime")); - key->algorithm = cfg_obj_asuint32( - cfg_tuple_get(config, "algorithm")); + + key->lifetime = 0; /* unlimited */ + obj = cfg_tuple_get(config, "lifetime"); + if (cfg_obj_isduration(obj)) { + key->lifetime = cfg_obj_asduration(obj); + } + + obj = cfg_tuple_get(config, "algorithm"); + key->algorithm = cfg_obj_asuint32(obj); + obj = cfg_tuple_get(config, "length"); if (cfg_obj_isuint32(obj)) { key->length = cfg_obj_asuint32(obj); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index e8812e9e30..0bfb11a25c 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -96,7 +96,7 @@ static cfg_type_t cfg_type_logseverity; static cfg_type_t cfg_type_logsuffix; static cfg_type_t cfg_type_logversions; static cfg_type_t cfg_type_masterselement; -static cfg_type_t cfg_type_maxttl; +static cfg_type_t cfg_type_maxduration; static cfg_type_t cfg_type_minimal; static cfg_type_t cfg_type_nameportiplist; static cfg_type_t cfg_type_notifytype; @@ -439,6 +439,24 @@ static cfg_type_t cfg_type_category = { &cfg_rep_tuple, category_fields }; +static isc_result_t +parse_maxduration(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret)); +} + +static void +doc_maxduration(cfg_printer_t *pctx, const cfg_type_t *type) { + cfg_doc_enum_or_other(pctx, type, &cfg_type_duration); +} + +/*% + * A duration or "unlimited", but not "default". + */ +static const char *maxduration_enums[] = { "unlimited", NULL }; +static cfg_type_t cfg_type_maxduration = { + "maxduration_no_default", parse_maxduration, cfg_print_ustring, + doc_maxduration, &cfg_rep_duration, maxduration_enums +}; /*% * A dnssec key, as used in the "trusted-keys" statement. @@ -509,7 +527,8 @@ static cfg_type_t cfg_type_algorithm = { doc_keyvalue, &cfg_rep_uint32, &algorithm_kw }; -static keyword_type_t lifetime_kw = { "lifetime", &cfg_type_duration }; +static keyword_type_t lifetime_kw = { "lifetime", + &cfg_type_duration_or_unlimited }; static cfg_type_t cfg_type_lifetime = { "lifetime", parse_keyvalue, print_keyvalue, doc_keyvalue, &cfg_rep_duration, &lifetime_kw @@ -2227,7 +2246,7 @@ zone_clauses[] = { { "max-transfer-time-out", &cfg_type_uint32, CFG_ZONE_MASTER | CFG_ZONE_MIRROR | CFG_ZONE_SLAVE }, - { "max-zone-ttl", &cfg_type_maxttl, + { "max-zone-ttl", &cfg_type_maxduration, CFG_ZONE_MASTER | CFG_ZONE_REDIRECT }, { "min-refresh-time", &cfg_type_uint32, @@ -3867,25 +3886,6 @@ static cfg_type_t cfg_type_masterselement = { doc_masterselement, NULL, NULL }; -static isc_result_t -parse_maxttl(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { - return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret)); -} - -static void -doc_maxttl(cfg_printer_t *pctx, const cfg_type_t *type) { - cfg_doc_enum_or_other(pctx, type, &cfg_type_duration); -} - -/*% - * A size or "unlimited", but not "default". - */ -static const char *maxttl_enums[] = { "unlimited", NULL }; -static cfg_type_t cfg_type_maxttl = { - "maxttl_no_default", parse_maxttl, cfg_print_ustring, doc_maxttl, - &cfg_rep_string, maxttl_enums -}; - static int cmp_clause(const void *ap, const void *bp) { const cfg_clausedef_t *a = (const cfg_clausedef_t *)ap; const cfg_clausedef_t *b = (const cfg_clausedef_t *)bp; diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c index 97647ca58e..5ae6b6bd88 100644 --- a/lib/isccfg/parser.c +++ b/lib/isccfg/parser.c @@ -1088,6 +1088,22 @@ cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) { cfg_print_chars(pctx, buf, strlen(buf)); } +void +cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj) { + cfg_duration_t duration; + + REQUIRE(pctx != NULL); + REQUIRE(obj != NULL); + + duration = obj->value.duration; + + if (duration.unlimited) { + cfg_print_cstr(pctx, "unlimited"); + } else { + cfg_print_duration(pctx, obj); + } +} + bool cfg_obj_isduration(const cfg_obj_t *obj) { REQUIRE(obj != NULL); @@ -1250,21 +1266,14 @@ duration_fromtext(isc_textregion_t *source, cfg_duration_t *duration) { return (ISC_R_SUCCESS); } -isc_result_t -cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, - cfg_obj_t **ret) +static isc_result_t +cfg__parse_duration(cfg_parser_t *pctx, cfg_obj_t **ret) { isc_result_t result; cfg_obj_t *obj = NULL; cfg_duration_t duration; - UNUSED(type); - - CHECK(cfg_gettoken(pctx, 0)); - if (pctx->token.type != isc_tokentype_string) { - result = ISC_R_UNEXPECTEDTOKEN; - goto cleanup; - } + duration.unlimited = false; if (TOKEN_STRING(pctx)[0] == 'P') { result = duration_fromtext(&pctx->token.value.as_textregion, @@ -1278,12 +1287,9 @@ cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, * With dns_ttl_fromtext() the information on optional units. * is lost, and is treated as seconds from now on. */ - duration.parts[0] = 0; - duration.parts[1] = 0; - duration.parts[2] = 0; - duration.parts[3] = 0; - duration.parts[4] = 0; - duration.parts[5] = 0; + for (int i = 0; i < 6; i++) { + duration.parts[i] = 0; + } duration.parts[6] = ttl; duration.iso8601 = false; } @@ -1305,6 +1311,66 @@ cleanup: return (result); } +isc_result_t +cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + return cfg__parse_duration(pctx, ret); + +cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected ISO 8601 duration or TTL value"); + return (result); +} + +isc_result_t +cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + cfg_duration_t duration; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + if (strcmp(TOKEN_STRING(pctx), "unlimited") == 0) { + for (int i = 0; i < 7; i++) { + duration.parts[i] = 0; + } + duration.iso8601 = false; + duration.unlimited = true; + + CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj)); + obj->value.duration = duration; + *ret = obj; + return (ISC_R_SUCCESS); + } + + return cfg__parse_duration(pctx, ret); + +cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected ISO 8601 duration, TTL value, or unlimited"); + return (result); +} + + /*% * A duration as defined by ISO 8601 (P[n]Y[n]M[n]DT[n]H[n]M[n]S). * - P is the duration indicator ("period") placed at the start. @@ -1324,6 +1390,11 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration = { "duration", cfg_parse_duration, cfg_print_duration, cfg_doc_terminal, &cfg_rep_duration, NULL }; +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration_or_unlimited = { + "duration_or_unlimited", cfg_parse_duration_or_unlimited, + cfg_print_duration_or_unlimited, cfg_doc_terminal, + &cfg_rep_duration, NULL +}; /* * qstring (quoted string), ustring (unquoted string), astring diff --git a/lib/isccfg/win32/libisccfg.def b/lib/isccfg/win32/libisccfg.def index 1c40b3c34b..eba897ec30 100644 --- a/lib/isccfg/win32/libisccfg.def +++ b/lib/isccfg/win32/libisccfg.def @@ -72,6 +72,7 @@ cfg_parse_bracketed_list cfg_parse_buffer cfg_parse_dscp cfg_parse_duration +cfg_parse_duration_or_unlimited cfg_parse_enum cfg_parse_enum_or_other cfg_parse_file @@ -112,6 +113,7 @@ cfg_print_chars cfg_print_clauseflags cfg_print_cstr cfg_print_duration +cfg_print_duration_or_unlimited cfg_print_fixedpoint cfg_print_grammar cfg_print_indent