diff --git a/bin/check/named-checkconf.c b/bin/check/named-checkconf.c index a0dea04667..c8b12911bb 100644 --- a/bin/check/named-checkconf.c +++ b/bin/check/named-checkconf.c @@ -421,7 +421,7 @@ configure_zone(const char *vclass, const char *view, obj = NULL; if (get_maps(maps, "max-zone-ttl", &obj)) { - maxttl = cfg_obj_asuint32(obj); + maxttl = cfg_obj_asduration(obj); zone_options |= DNS_ZONEOPT_CHECKTTL; } diff --git a/bin/dnssec/Makefile.in b/bin/dnssec/Makefile.in index 32d79f4292..7d3b15f5d3 100644 --- a/bin/dnssec/Makefile.in +++ b/bin/dnssec/Makefile.in @@ -15,24 +15,26 @@ VERSION=@BIND9_VERSION@ @BIND9_MAKE_INCLUDES@ -CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} \ +CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES} \ ${OPENSSL_CFLAGS} -CDEFINES = -DVERSION=\"${VERSION}\" +CDEFINES = -DVERSION=\"${VERSION}\" -DNAMED_CONFFILE=\"${sysconfdir}/named.conf\" CWARNINGS = DNSLIBS = ../../lib/dns/libdns.@A@ ${MAXMINDDB_LIBS} @DNS_CRYPTO_LIBS@ +ISCCFGLIBS = ../../lib/isccfg/libisccfg.@A@ ISCLIBS = ../../lib/isc/libisc.@A@ ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBXML2_LIBS} ISCNOSYMLIBS = ../../lib/isc/libisc-nosymtbl.@A@ ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBXML2_LIBS} DNSDEPLIBS = ../../lib/dns/libdns.@A@ ISCDEPLIBS = ../../lib/isc/libisc.@A@ +ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@ -DEPLIBS = ${DNSDEPLIBS} ${ISCDEPLIBS} +DEPLIBS = ${DNSDEPLIBS} ${ISCCFGDEPLIBS} ${ISCDEPLIBS} -LIBS = ${DNSLIBS} ${ISCLIBS} @LIBS@ +LIBS = ${DNSLIBS} ${ISCCFGLIBS} ${ISCLIBS} @LIBS@ -NOSYMLIBS = ${DNSLIBS} ${ISCNOSYMLIBS} @LIBS@ +NOSYMLIBS = ${DNSLIBS} ${ISCCFGLIBS} ${ISCNOSYMLIBS} @LIBS@ # Alphabetically TARGETS = dnssec-cds@EXEEXT@ dnssec-dsfromkey@EXEEXT@ \ @@ -48,7 +50,7 @@ SRCS = dnssec-cds.c dnssec-dsfromkey.c dnssec-importkey.c \ dnssec-settime.c dnssec-signzone.c dnssec-verify.c \ dnssectool.c -MANPAGES = dnssec-cds.8 dnssec-dsfromkey.8 dnssec-importkey.8 \ +MANPAGES = dnssec-cds.8 dnssec-dsfromkey.8 dnssec-importkey.8 \ dnssec-keyfromlabel.8 dnssec-keygen.8 dnssec-revoke.8 \ dnssec-settime.8 dnssec-signzone.8 dnssec-verify.8 diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 85362db2a1..40cffe2168 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -39,10 +39,16 @@ #include #include +#include +#include +#include +#include + #include #include #include +#include #include #include #include @@ -67,6 +73,62 @@ usage(void) ISC_PLATFORM_NORETURN_POST; static void progress(int p); +struct keygen_ctx { + const char *predecessor; + const char *policy; + const char *configfile; + const char *directory; + char *algname; + char *nametype; + char *type; + int generator; + int protocol; + int size; + int signatory; + dns_rdataclass_t rdclass; + int options; + int dbits; + dns_ttl_t ttl; + uint16_t kskflag; + uint16_t revflag; + dns_secalg_t alg; + /* timing data */ + int prepub; + isc_stdtime_t now; + isc_stdtime_t publish; + isc_stdtime_t activate; + isc_stdtime_t inactive; + isc_stdtime_t revokekey; + isc_stdtime_t deltime; + isc_stdtime_t syncadd; + isc_stdtime_t syncdel; + bool setpub; + bool setact; + bool setinact; + bool setrev; + bool setdel; + bool setsyncadd; + bool setsyncdel; + bool unsetpub; + bool unsetact; + bool unsetinact; + bool unsetrev; + bool unsetdel; + /* how to generate the key */ + bool setttl; + bool use_nsec3; + bool genonly; + bool showprogress; + bool quiet; + bool oldstyle; + /* state */ + time_t lifetime; + bool ksk; + bool zsk; +}; + +typedef struct keygen_ctx keygen_ctx_t; + static void usage(void) { fprintf(stderr, "Usage:\n"); @@ -75,6 +137,9 @@ usage(void) { fprintf(stderr, " name: owner of the key\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -K : write keys into directory\n"); + fprintf(stderr, " -k : generate keys for dnssec-policy\n"); + fprintf(stderr, " -l : configuration file with dnssec-policy " + "statement\n"); fprintf(stderr, " -a :\n"); fprintf(stderr, " RSASHA1 | NSEC3RSASHA1 |\n"); fprintf(stderr, " RSASHA256 | RSASHA512 |\n"); @@ -177,55 +242,588 @@ progress(int p) (void) fflush(stderr); } +static void +kasp_from_conf(cfg_obj_t* config, isc_mem_t* mctx, const char* name, + dns_kasp_t** kaspp) +{ + const cfg_listelt_t *element; + const cfg_obj_t *kasps = NULL; + dns_kasp_t *kasp = NULL, *kasp_next; + isc_result_t result = ISC_R_NOTFOUND; + dns_kasplist_t kasplist; + + ISC_LIST_INIT(kasplist); + + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + kasp = NULL; + if (strcmp(cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + name) != 0) + { + continue; + } + + result = cfg_kasp_fromconfig(kconfig, mctx, &kasplist, &kasp); + if (result != ISC_R_SUCCESS) { + fatal("failed to configure dnssec-policy '%s': %s", + cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + isc_result_totext(result)); + } + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + break; + } + + *kaspp = kasp; + + /* + * Same cleanup for kasp list. + */ + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; kasp = kasp_next) { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } +} + +static void +keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) +{ + char filename[255]; + char algstr[DNS_SECALG_FORMATSIZE]; + uint16_t flags = 0; + int param = 0; + bool null_key = false; + bool conflict = false; + bool show_progress = false; + isc_buffer_t buf; + dns_name_t *name; + dns_fixedname_t fname; + isc_result_t ret; + dst_key_t* key = NULL; + dst_key_t* prevkey = NULL; + + UNUSED(argc); + + dns_secalg_format(ctx->alg, algstr, sizeof(algstr)); + + if (ctx->predecessor == NULL) { + if (ctx->prepub == -1) + ctx->prepub = 0; + + name = dns_fixedname_initname(&fname); + isc_buffer_init(&buf, argv[isc_commandline_index], + strlen(argv[isc_commandline_index])); + isc_buffer_add(&buf, strlen(argv[isc_commandline_index])); + ret = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); + if (ret != ISC_R_SUCCESS) { + fatal("invalid key name %s: %s", + argv[isc_commandline_index], + isc_result_totext(ret)); + } + + if (!dst_algorithm_supported(ctx->alg)) { + fatal("unsupported algorithm: %s", algstr); + } + + if (ctx->alg == DST_ALG_DH) { + ctx->options |= DST_TYPE_KEY; + } + + if (ctx->use_nsec3) { + switch (ctx->alg) { + case DST_ALG_RSASHA1: + ctx->alg = DST_ALG_NSEC3RSASHA1; + break; + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + break; + default: + fatal("algorithm %s is incompatible with NSEC3" + ", do not use the -3 option", algstr); + } + } + + if (ctx->type != NULL && (ctx->options & DST_TYPE_KEY) != 0) { + if (strcasecmp(ctx->type, "NOAUTH") == 0) { + flags |= DNS_KEYTYPE_NOAUTH; + } else if (strcasecmp(ctx->type, "NOCONF") == 0) { + flags |= DNS_KEYTYPE_NOCONF; + } else if (strcasecmp(ctx->type, "NOAUTHCONF") == 0) { + flags |= (DNS_KEYTYPE_NOAUTH | + DNS_KEYTYPE_NOCONF); + if (ctx->size < 0) + ctx->size = 0; + } else if (strcasecmp(ctx->type, "AUTHCONF") == 0) { + /* nothing */; + } else { + fatal("invalid type %s", ctx->type); + } + } + + if (ctx->size < 0) { + switch (ctx->alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + ctx->size = 2048; + if (verbose > 0) { + fprintf(stderr, "key size not " + "specified; defaulting" + " to %d\n", ctx->size); + } + break; + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + break; + default: + fatal("key size not specified (-b option)"); + } + } + + if (!ctx->oldstyle && ctx->prepub > 0) { + if (ctx->setpub && ctx->setact && + (ctx->activate - ctx->prepub) < ctx->publish) + { + fatal("Activation and publication dates " + "are closer together than the\n\t" + "prepublication interval."); + } + + if (!ctx->setpub && !ctx->setact) { + ctx->setpub = ctx->setact = true; + ctx->publish = ctx->now; + ctx->activate = ctx->now + ctx->prepub; + } else if (ctx->setpub && !ctx->setact) { + ctx->setact = true; + ctx->activate = ctx->publish + ctx->prepub; + } else if (ctx->setact && !ctx->setpub) { + ctx->setpub = true; + ctx->publish = ctx->activate - ctx->prepub; + } + + if ((ctx->activate - ctx->prepub) < ctx->now) + fatal("Time until activation is shorter " + "than the\n\tprepublication interval."); + } + } else { + char keystr[DST_KEY_FORMATSIZE]; + isc_stdtime_t when; + int major, minor; + + if (ctx->prepub == -1) + ctx->prepub = (30 * 86400); + + if (ctx->alg != 0) + fatal("-S and -a cannot be used together"); + if (ctx->size >= 0) + fatal("-S and -b cannot be used together"); + if (ctx->nametype != NULL) + fatal("-S and -n cannot be used together"); + if (ctx->type != NULL) + fatal("-S and -t cannot be used together"); + if (ctx->setpub || ctx->unsetpub) + fatal("-S and -P cannot be used together"); + if (ctx->setact || ctx->unsetact) + fatal("-S and -A cannot be used together"); + if (ctx->use_nsec3) + fatal("-S and -3 cannot be used together"); + if (ctx->oldstyle) + fatal("-S and -C cannot be used together"); + if (ctx->genonly) + fatal("-S and -G cannot be used together"); + + ret = dst_key_fromnamedfile(ctx->predecessor, ctx->directory, + (DST_TYPE_PUBLIC| + DST_TYPE_PRIVATE|DST_TYPE_STATE), + mctx, &prevkey); + if (ret != ISC_R_SUCCESS) + fatal("Invalid keyfile %s: %s", + ctx->predecessor, isc_result_totext(ret)); + if (!dst_key_isprivate(prevkey)) + fatal("%s is not a private key", ctx->predecessor); + + name = dst_key_name(prevkey); + ctx->alg = dst_key_alg(prevkey); + ctx->size = dst_key_size(prevkey); + flags = dst_key_flags(prevkey); + + dst_key_format(prevkey, keystr, sizeof(keystr)); + dst_key_getprivateformat(prevkey, &major, &minor); + if (major != DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) + fatal("Key %s has incompatible format version %d.%d\n\t" + "It is not possible to generate a successor key.", + keystr, major, minor); + + ret = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &when); + if (ret != ISC_R_SUCCESS) + fatal("Key %s has no activation date.\n\t" + "You must use dnssec-settime -A to set one " + "before generating a successor.", keystr); + + ret = dst_key_gettime(prevkey, DST_TIME_INACTIVE, + &ctx->activate); + if (ret != ISC_R_SUCCESS) + fatal("Key %s has no inactivation date.\n\t" + "You must use dnssec-settime -I to set one " + "before generating a successor.", keystr); + + ctx->publish = ctx->activate - ctx->prepub; + if (ctx->publish < ctx->now) + fatal("Key %s becomes inactive\n\t" + "sooner than the prepublication period " + "for the new key ends.\n\t" + "Either change the inactivation date with " + "dnssec-settime -I,\n\t" + "or use the -i option to set a shorter " + "prepublication interval.", keystr); + + ret = dst_key_gettime(prevkey, DST_TIME_DELETE, &when); + if (ret != ISC_R_SUCCESS) + fprintf(stderr, "%s: WARNING: Key %s has no removal " + "date;\n\t it will remain in the zone " + "indefinitely after rollover.\n\t " + "You can use dnssec-settime -D to " + "change this.\n", program, keystr); + + ctx->setpub = ctx->setact = true; + } + + switch (ctx->alg) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) + fatal("RSA key size %d out of range", ctx->size); + break; + case DNS_KEYALG_RSASHA512: + if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) + fatal("RSA key size %d out of range", ctx->size); + break; + case DNS_KEYALG_DH: + if (ctx->size != 0 && (ctx->size < 128 || ctx->size > 4096)) + fatal("DH key size %d out of range", ctx->size); + break; + case DST_ALG_ECDSA256: + ctx->size = 256; + break; + case DST_ALG_ECDSA384: + ctx->size = 384; + break; + case DST_ALG_ED25519: + ctx->size = 256; + break; + case DST_ALG_ED448: + ctx->size = 456; + break; + } + + if (ctx->alg != DNS_KEYALG_DH && ctx->generator != 0) + fatal("specified DH generator for a non-DH key"); + + if (ctx->nametype == NULL) { + if ((ctx->options & DST_TYPE_KEY) != 0) /* KEY */ + fatal("no nametype specified"); + flags |= DNS_KEYOWNER_ZONE; /* DNSKEY */ + } else if (strcasecmp(ctx->nametype, "zone") == 0) + flags |= DNS_KEYOWNER_ZONE; + else if ((ctx->options & DST_TYPE_KEY) != 0) { /* KEY */ + if (strcasecmp(ctx->nametype, "host") == 0 || + strcasecmp(ctx->nametype, "entity") == 0) + flags |= DNS_KEYOWNER_ENTITY; + else if (strcasecmp(ctx->nametype, "user") == 0) + flags |= DNS_KEYOWNER_USER; + else + fatal("invalid KEY nametype %s", ctx->nametype); + } else if (strcasecmp(ctx->nametype, "other") != 0) /* DNSKEY */ + fatal("invalid DNSKEY nametype %s", ctx->nametype); + + if (ctx->directory == NULL) + ctx->directory = "."; + + if ((ctx->options & DST_TYPE_KEY) != 0) /* KEY */ + flags |= ctx->signatory; + else if ((flags & DNS_KEYOWNER_ZONE) != 0) { /* DNSKEY */ + flags |= ctx->kskflag; + flags |= ctx->revflag; + } + + if (ctx->protocol == -1) + ctx->protocol = DNS_KEYPROTO_DNSSEC; + else if ((ctx->options & DST_TYPE_KEY) == 0 && + ctx->protocol != DNS_KEYPROTO_DNSSEC) + fatal("invalid DNSKEY protocol: %d", ctx->protocol); + + if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { + if (ctx->size > 0) + fatal("specified null key with non-zero size"); + if ((flags & DNS_KEYFLAG_SIGNATORYMASK) != 0) + fatal("specified null key with signing authority"); + } + + if ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE && + ctx->alg == DNS_KEYALG_DH) + { + fatal("a key with algorithm %s cannot be a zone key", algstr); + } + + switch(ctx->alg) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + case DNS_KEYALG_RSASHA512: + show_progress = true; + break; + + case DNS_KEYALG_DH: + param = ctx->generator; + break; + + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + show_progress = true; + break; + } + + if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) + null_key = true; + + isc_buffer_init(&buf, filename, sizeof(filename) - 1); + + do { + conflict = false; + + if (!ctx->quiet && show_progress) { + fprintf(stderr, "Generating key pair."); + ret = dst_key_generate(name, ctx->alg, ctx->size, + param, flags, ctx->protocol, + ctx->rdclass, mctx, &key, + &progress); + putc('\n', stderr); + fflush(stderr); + } else { + ret = dst_key_generate(name, ctx->alg, ctx->size, + param, flags, ctx->protocol, + ctx->rdclass, mctx, &key, NULL); + } + + if (ret != ISC_R_SUCCESS) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namestr, sizeof(namestr)); + fatal("failed to generate key %s/%s: %s\n", + namestr, algstr, isc_result_totext(ret)); + } + + dst_key_setbits(key, ctx->dbits); + + /* + * Set key timing metadata (unless using -C) + * + * Creation date is always set to "now". + * + * For a new key without an explicit predecessor, publish + * and activation dates are set to "now" by default, but + * can both be overridden. + * + * For a successor key, activation is set to match the + * predecessor's inactivation date. Publish is set to 30 + * days earlier than that (XXX: this should be configurable). + * If either of the resulting dates are in the past, that's + * an error; the inactivation date of the predecessor key + * must be updated before a successor key can be created. + */ + if (!ctx->oldstyle) { + dst_key_settime(key, DST_TIME_CREATED, ctx->now); + + if (ctx->genonly && (ctx->setpub || ctx->setact)) + fatal("cannot use -G together with " + "-P or -A options"); + + if (ctx->setpub) + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->publish); + else if (ctx->setact && !ctx->unsetpub) + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->activate - ctx->prepub); + else if (!ctx->genonly && !ctx->unsetpub) + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->now); + + if (ctx->setact) + dst_key_settime(key, DST_TIME_ACTIVATE, + ctx->activate); + else if (!ctx->genonly && !ctx->unsetact) + dst_key_settime(key, DST_TIME_ACTIVATE, + ctx->now); + + if (ctx->setrev) { + if (ctx->kskflag == 0) + fprintf(stderr, "%s: warning: Key is " + "not flagged as a KSK, but -R " + "was used. Revoking a ZSK is " + "legal, but undefined.\n", + program); + dst_key_settime(key, DST_TIME_REVOKE, + ctx->revokekey); + } + + if (ctx->setinact) + dst_key_settime(key, DST_TIME_INACTIVE, + ctx->inactive); + + if (ctx->setdel) { + if (ctx->setinact && + ctx->deltime < ctx->inactive) + { + fprintf(stderr, "%s: warning: Key is " + "scheduled to be deleted " + "before it is scheduled to be " + "made inactive.\n", + program); + } + dst_key_settime(key, DST_TIME_DELETE, + ctx->deltime); + } + + if (ctx->setsyncadd) + dst_key_settime(key, DST_TIME_SYNCPUBLISH, + ctx->syncadd); + + if (ctx->setsyncdel) + dst_key_settime(key, DST_TIME_SYNCDELETE, + ctx->syncdel); + } else { + if (ctx->setpub || ctx->setact || ctx->setrev || + ctx->setinact || ctx->setdel || ctx->unsetpub || + ctx->unsetact || ctx->unsetrev || + ctx->unsetinact || ctx->unsetdel || ctx->genonly || + ctx->setsyncadd || ctx->setsyncdel) + { + fatal("cannot use -C together with " + "-P, -A, -R, -I, -D, or -G options"); + } + /* + * Compatibility mode: Private-key-format + * should be set to 1.2. + */ + dst_key_setprivateformat(key, 1, 2); + } + + /* Set the default key TTL */ + if (ctx->setttl) + dst_key_setttl(key, ctx->ttl); + + /* Set dnssec-policy related metadata */ + if (ctx->policy) { + dst_key_setnum(key, DST_NUM_LIFETIME, ctx->lifetime); + dst_key_setbool(key, DST_BOOL_KSK, ctx->ksk); + dst_key_setbool(key, DST_BOOL_ZSK, ctx->zsk); + } + + /* + * Do not overwrite an existing key, or create a key + * if there is a risk of ID collision due to this key + * or another key being revoked. + */ + if (key_collision(key, name, ctx->directory, mctx, NULL)) { + conflict = true; + if (null_key) { + dst_key_free(&key); + break; + } + + if (verbose > 0) { + isc_buffer_clear(&buf); + ret = dst_key_buildfilename(key, 0, + ctx->directory, + &buf); + if (ret == ISC_R_SUCCESS) + fprintf(stderr, + "%s: %s already exists, or " + "might collide with another " + "key upon revokation. " + "Generating a new key\n", + program, filename); + } + + dst_key_free(&key); + } + } while (conflict == true); + + if (conflict) + fatal("cannot generate a null key due to possible key ID " + "collision"); + + if (ctx->predecessor != NULL && prevkey != NULL) { + dst_key_setnum(prevkey, DST_NUM_SUCCESSOR, dst_key_id(key)); + dst_key_setnum(key, DST_NUM_PREDECESSOR, dst_key_id(prevkey)); + + ret = dst_key_tofile(prevkey, ctx->options, ctx->directory); + if (ret != ISC_R_SUCCESS) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(prevkey, keystr, sizeof(keystr)); + fatal("failed to update predecessor %s: %s\n", keystr, + isc_result_totext(ret)); + } + } + + ret = dst_key_tofile(key, ctx->options, ctx->directory); + if (ret != ISC_R_SUCCESS) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key, keystr, sizeof(keystr)); + fatal("failed to write key %s: %s\n", keystr, + isc_result_totext(ret)); + } + + isc_buffer_clear(&buf); + ret = dst_key_buildfilename(key, 0, NULL, &buf); + if (ret != ISC_R_SUCCESS) + fatal("dst_key_buildfilename returned: %s\n", + isc_result_totext(ret)); + printf("%s\n", filename); + + dst_key_free(&key); + if (prevkey != NULL) { + dst_key_free(&prevkey); + } +} + int main(int argc, char **argv) { char *algname = NULL, *freeit = NULL; - char *nametype = NULL, *type = NULL; char *classname = NULL; char *endp; - dst_key_t *key = NULL; - dns_fixedname_t fname; - dns_name_t *name; - uint16_t flags = 0, kskflag = 0, revflag = 0; - dns_secalg_t alg; - bool conflict = false, null_key = false; - bool oldstyle = false; isc_mem_t *mctx = NULL; - int ch, generator = 0, param = 0; - int protocol = -1, size = -1, signatory = 0; isc_result_t ret; isc_textregion_t r; - char filename[255]; - const char *directory = NULL; - const char *predecessor = NULL; - dst_key_t *prevkey = NULL; - isc_buffer_t buf; isc_log_t *log = NULL; const char *engine = NULL; - dns_rdataclass_t rdclass; - int options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC; - int dbits = 0; - dns_ttl_t ttl = 0; - bool use_nsec3 = false; - isc_stdtime_t publish = 0, activate = 0, revokekey = 0; - isc_stdtime_t inactive = 0, deltime = 0; - isc_stdtime_t now; - int prepub = -1; - bool setpub = false, setact = false; - bool setrev = false, setinact = false; - bool setdel = false, setttl = false; - bool unsetpub = false, unsetact = false; - bool unsetrev = false, unsetinact = false; - bool unsetdel = false; - bool genonly = false; - bool show_progress = false; unsigned char c; - isc_stdtime_t syncadd = 0, syncdel = 0; - bool setsyncadd = false; - bool setsyncdel = false; + int ch; - if (argc == 1) + keygen_ctx_t ctx = { + .options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC, + .prepub = -1, + .protocol = -1, + .size = -1, + }; + + if (argc == 1) { usage(); + } #if USE_PKCS11 pk11_result_register(); @@ -237,8 +835,8 @@ main(int argc, char **argv) { /* * Process memory debugging argument first. */ -#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:L:m:n:P:p:qR:r:S:s:T:t:" \ - "v:V" +#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:k:L:l:m:n:P:p:qR:r:S:s:" \ + "T:t:v:V" while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case 'm': @@ -260,31 +858,30 @@ main(int argc, char **argv) { isc_commandline_reset = true; isc_mem_create(&mctx); - - isc_stdtime_get(&now); + isc_stdtime_get(&ctx.now); while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case '3': - use_nsec3 = true; + ctx.use_nsec3 = true; break; case 'a': algname = isc_commandline_argument; break; case 'b': - size = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || size < 0) + ctx.size = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.size < 0) fatal("-b requires a non-negative number"); break; case 'C': - oldstyle = true; + ctx.oldstyle = true; break; case 'c': classname = isc_commandline_argument; break; case 'd': - dbits = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || dbits < 0) + ctx.dbits = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.dbits < 0) fatal("-d requires a non-negative number"); break; case 'E': @@ -298,58 +895,71 @@ main(int argc, char **argv) { case 'f': c = (unsigned char)(isc_commandline_argument[0]); if (toupper(c) == 'K') - kskflag = DNS_KEYFLAG_KSK; + ctx.kskflag = DNS_KEYFLAG_KSK; else if (toupper(c) == 'R') - revflag = DNS_KEYFLAG_REVOKE; + ctx.revflag = DNS_KEYFLAG_REVOKE; else fatal("unknown flag '%s'", isc_commandline_argument); break; case 'g': - generator = strtol(isc_commandline_argument, + ctx.generator = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || generator <= 0) + if (*endp != '\0' || ctx.generator <= 0) fatal("-g requires a positive number"); break; case 'K': - directory = isc_commandline_argument; - ret = try_dir(directory); + ctx.directory = isc_commandline_argument; + ret = try_dir(ctx.directory); if (ret != ISC_R_SUCCESS) fatal("cannot open directory %s: %s", - directory, isc_result_totext(ret)); + ctx.directory, isc_result_totext(ret)); + break; + case 'k': + ctx.policy = isc_commandline_argument; break; case 'L': - ttl = strtottl(isc_commandline_argument); - setttl = true; + ctx.ttl = strtottl(isc_commandline_argument); + ctx.setttl = true; + break; + case 'l': + ctx.configfile = isc_commandline_argument; break; case 'n': - nametype = isc_commandline_argument; + ctx.nametype = isc_commandline_argument; break; case 'm': break; case 'p': - protocol = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || protocol < 0 || protocol > 255) + ctx.protocol = strtol(isc_commandline_argument, &endp, + 10); + if (*endp != '\0' || ctx.protocol < 0 || + ctx.protocol > 255) + { fatal("-p must be followed by a number " "[0..255]"); + } break; case 'q': - quiet = true; + ctx.quiet = true; break; case 'r': fatal("The -r option has been deprecated.\n" "System random data is always used.\n"); break; case 's': - signatory = strtol(isc_commandline_argument, - &endp, 10); - if (*endp != '\0' || signatory < 0 || signatory > 15) + ctx.signatory = strtol(isc_commandline_argument, + &endp, 10); + if (*endp != '\0' || ctx.signatory < 0 || + ctx.signatory > 15) + { fatal("-s must be followed by a number " "[0..15]"); + } break; case 'T': if (strcasecmp(isc_commandline_argument, "KEY") == 0) - options |= DST_TYPE_KEY; + ctx.options |= DST_TYPE_KEY; else if (strcasecmp(isc_commandline_argument, "DNSKEY") == 0) /* default behavior */ @@ -359,7 +969,7 @@ main(int argc, char **argv) { isc_commandline_argument); break; case 't': - type = isc_commandline_argument; + ctx.type = isc_commandline_argument; break; case 'v': endp = NULL; @@ -367,79 +977,80 @@ main(int argc, char **argv) { if (*endp != '\0') fatal("-v must be followed by a number"); break; - case 'z': - /* already the default */ - break; case 'G': - genonly = true; + ctx.genonly = true; break; case 'P': /* -Psync ? */ if (isoptarg("sync", argv, usage)) { - if (setsyncadd) + if (ctx.setsyncadd) fatal("-P sync specified more than " "once"); - syncadd = strtotime(isc_commandline_argument, - now, now, &setsyncadd); + ctx.syncadd = strtotime( + isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setsyncadd); break; } (void)isoptarg("dnskey", argv, usage); - if (setpub || unsetpub) + if (ctx.setpub || ctx.unsetpub) fatal("-P specified more than once"); - publish = strtotime(isc_commandline_argument, - now, now, &setpub); - unsetpub = !setpub; + ctx.publish = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setpub); + ctx.unsetpub = !ctx.setpub; break; case 'A': - if (setact || unsetact) + if (ctx.setact || ctx.unsetact) fatal("-A specified more than once"); - activate = strtotime(isc_commandline_argument, - now, now, &setact); - unsetact = !setact; + ctx.activate = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setact); + ctx.unsetact = !ctx.setact; break; case 'R': - if (setrev || unsetrev) + if (ctx.setrev || ctx.unsetrev) fatal("-R specified more than once"); - revokekey = strtotime(isc_commandline_argument, - now, now, &setrev); - unsetrev = !setrev; + ctx.revokekey = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setrev); + ctx.unsetrev = !ctx.setrev; break; case 'I': - if (setinact || unsetinact) + if (ctx.setinact || ctx.unsetinact) fatal("-I specified more than once"); - inactive = strtotime(isc_commandline_argument, - now, now, &setinact); - unsetinact = !setinact; + ctx.inactive = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setinact); + ctx.unsetinact = !ctx.setinact; break; case 'D': /* -Dsync ? */ if (isoptarg("sync", argv, usage)) { - if (setsyncdel) + if (ctx.setsyncdel) fatal("-D sync specified more than " "once"); - syncdel = strtotime(isc_commandline_argument, - now, now, &setsyncdel); + ctx.syncdel = strtotime( + isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setsyncdel); break; } (void)isoptarg("dnskey", argv, usage); - if (setdel || unsetdel) + if (ctx.setdel || ctx.unsetdel) fatal("-D specified more than once"); - deltime = strtotime(isc_commandline_argument, - now, now, &setdel); - unsetdel = !setdel; + ctx.deltime = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setdel); + ctx.unsetdel = !ctx.setdel; break; case 'S': - predecessor = isc_commandline_argument; + ctx.predecessor = isc_commandline_argument; break; case 'i': - prepub = strtottl(isc_commandline_argument); + ctx.prepub = strtottl(isc_commandline_argument); break; case 'F': /* Reserved for FIPS mode */ @@ -465,7 +1076,7 @@ main(int argc, char **argv) { } if (!isatty(0)) - quiet = true; + ctx.quiet = true; ret = dst_lib_init(mctx, engine); if (ret != ISC_R_SUCCESS) @@ -474,478 +1085,141 @@ main(int argc, char **argv) { setup_logging(mctx, &log); - if (predecessor == NULL) { - if (prepub == -1) - prepub = 0; + ctx.rdclass = strtoclass(classname); + if (ctx.configfile == NULL || ctx.configfile[0] == '\0') { + ctx.configfile = NAMED_CONFFILE; + } + + if (ctx.predecessor == NULL) { if (argc < isc_commandline_index + 1) fatal("the key name was not specified"); if (argc > isc_commandline_index + 1) fatal("extraneous arguments"); + } - name = dns_fixedname_initname(&fname); - isc_buffer_init(&buf, argv[isc_commandline_index], - strlen(argv[isc_commandline_index])); - isc_buffer_add(&buf, strlen(argv[isc_commandline_index])); - ret = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); - if (ret != ISC_R_SUCCESS) - fatal("invalid key name %s: %s", - argv[isc_commandline_index], - isc_result_totext(ret)); - + if (ctx.predecessor == NULL && ctx.policy == NULL) { if (algname == NULL) { fatal("no algorithm specified"); } - r.base = algname; r.length = strlen(algname); - ret = dns_secalg_fromtext(&alg, &r); + ret = dns_secalg_fromtext(&ctx.alg, &r); if (ret != ISC_R_SUCCESS) { fatal("unknown algorithm %s", algname); } - if (alg == DST_ALG_DH) { - options |= DST_TYPE_KEY; + if (!dst_algorithm_supported(ctx.alg)) { + fatal("unsupported algorithm: %s", algname); + } + } + + if (ctx.policy != NULL) { + if (ctx.nametype != NULL) { + fatal("-k and -n cannot be used together"); + } + if (ctx.predecessor != NULL) { + fatal("-k and -S cannot be used together"); + } + if (ctx.oldstyle) { + fatal("-k and -C cannot be used together"); + } + if (ctx.setttl) { + fatal("-k and -L cannot be used together"); + } + if (ctx.prepub > 0) { + fatal("-k and -i cannot be used together"); + } + if (ctx.size != -1) { + fatal("-k and -b cannot be used together"); + } + if (ctx.kskflag || ctx.revflag) { + fatal("-k and -f cannot be used together"); + } + if (ctx.options & DST_TYPE_KEY) { + fatal("-k and -T KEY cannot be used together"); + } + if (ctx.use_nsec3) { + fatal("-k and -3 cannot be used together"); } - if (!dst_algorithm_supported(alg)) { - fatal("unsupported algorithm: %d", alg); + if (ctx.setpub || ctx.setact || ctx.setrev || ctx.setinact || + ctx.setdel || ctx.unsetpub || ctx.unsetact || + ctx.unsetrev || ctx.unsetinact || ctx.unsetdel || + ctx.setsyncadd || ctx.setsyncdel) + { + fatal("cannot use -k together with " + "-P, -A, -R, -I, or -D options " + "(use dnssec-settime on keys afterwards)"); } - if (use_nsec3) { - switch (alg) { - case DST_ALG_RSASHA1: - alg = DST_ALG_NSEC3RSASHA1; - break; - case DST_ALG_NSEC3RSASHA1: - case DST_ALG_RSASHA256: - case DST_ALG_RSASHA512: - case DST_ALG_ECDSA256: - case DST_ALG_ECDSA384: - case DST_ALG_ED25519: - case DST_ALG_ED448: - break; - default: - fatal("%s is incompatible with NSEC3; " - "do not use the -3 option", algname); - } - } + ctx.options |= DST_TYPE_STATE; + ctx.genonly = true; - if (type != NULL && (options & DST_TYPE_KEY) != 0) { - if (strcasecmp(type, "NOAUTH") == 0) { - flags |= DNS_KEYTYPE_NOAUTH; - } else if (strcasecmp(type, "NOCONF") == 0) { - flags |= DNS_KEYTYPE_NOCONF; - } else if (strcasecmp(type, "NOAUTHCONF") == 0) { - flags |= (DNS_KEYTYPE_NOAUTH | - DNS_KEYTYPE_NOCONF); - if (size < 0) - size = 0; - } else if (strcasecmp(type, "AUTHCONF") == 0) { - /* nothing */; - } else { - fatal("invalid type %s", type); - } - } + if (strcmp(ctx.policy, "default") == 0) { + ctx.use_nsec3 = false; + ctx.alg = DST_ALG_ECDSA256; + ctx.size = 0; + ctx.kskflag = DNS_KEYFLAG_KSK; + ctx.ttl = 3600; + ctx.setttl = true; + ctx.ksk = true; + ctx.zsk = true; + ctx.lifetime = 0; - if (size < 0) { - switch (alg) { - case DST_ALG_RSASHA1: - case DST_ALG_NSEC3RSASHA1: - case DST_ALG_RSASHA256: - case DST_ALG_RSASHA512: - size = 2048; - if (verbose > 0) { - fprintf(stderr, "key size not " - "specified; defaulting" - " to %d\n", size); - } - break; - case DST_ALG_ECDSA256: - case DST_ALG_ECDSA384: - case DST_ALG_ED25519: - case DST_ALG_ED448: - break; - default: - fatal("key size not specified (-b option)"); - } - } + keygen(&ctx, mctx, argc, argv); + } else { + cfg_parser_t *parser = NULL; + cfg_obj_t *config = NULL; + dns_kasp_t* kasp = NULL; + dns_kasp_key_t* kaspkey = NULL; - if (!oldstyle && prepub > 0) { - if (setpub && setact && (activate - prepub) < publish) - fatal("Activation and publication dates " - "are closer together than the\n\t" - "prepublication interval."); - - if (!setpub && !setact) { - setpub = setact = true; - publish = now; - activate = now + prepub; - } else if (setpub && !setact) { - setact = true; - activate = publish + prepub; - } else if (setact && !setpub) { - setpub = true; - publish = activate - prepub; + RUNTIME_CHECK(cfg_parser_create(mctx, log, &parser) + == ISC_R_SUCCESS); + if (cfg_parse_file(parser, ctx.configfile, + &cfg_type_namedconf, &config) != ISC_R_SUCCESS) + { + fatal("unable to load dnssec-policy '%s' from " + "'%s'", ctx.policy, ctx.configfile); } - if ((activate - prepub) < now) - fatal("Time until activation is shorter " - "than the\n\tprepublication interval."); + kasp_from_conf(config, mctx, ctx.policy, &kasp); + if (kasp == NULL) { + fatal("failed to load dnssec-policy '%s'", + ctx.policy); + } + if (ISC_LIST_EMPTY(dns_kasp_keys(kasp))) { + fatal("dnssec-policy '%s' has no keys " + "configured", ctx.policy); + } + + ctx.ttl = dns_kasp_dnskeyttl(kasp); + ctx.setttl = true; + + kaspkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); + + while (kaspkey != NULL) { + ctx.use_nsec3 = false; + ctx.alg = dns_kasp_key_algorithm(kaspkey); + ctx.size = dns_kasp_key_size(kaspkey); + ctx.kskflag = dns_kasp_key_ksk(kaspkey) ? + DNS_KEYFLAG_KSK : 0; + ctx.ksk = dns_kasp_key_ksk(kaspkey); + ctx.zsk = dns_kasp_key_zsk(kaspkey); + ctx.lifetime = dns_kasp_key_lifetime(kaspkey); + + keygen(&ctx, mctx, argc, argv); + + kaspkey = ISC_LIST_NEXT(kaspkey, link); + } + + dns_kasp_detach(&kasp); + cfg_obj_destroy(parser, &config); + cfg_parser_destroy(&parser); } } else { - char keystr[DST_KEY_FORMATSIZE]; - isc_stdtime_t when; - int major, minor; - - if (prepub == -1) - prepub = (30 * 86400); - - if (algname != NULL) - fatal("-S and -a cannot be used together"); - if (size >= 0) - fatal("-S and -b cannot be used together"); - if (nametype != NULL) - fatal("-S and -n cannot be used together"); - if (type != NULL) - fatal("-S and -t cannot be used together"); - if (setpub || unsetpub) - fatal("-S and -P cannot be used together"); - if (setact || unsetact) - fatal("-S and -A cannot be used together"); - if (use_nsec3) - fatal("-S and -3 cannot be used together"); - if (oldstyle) - fatal("-S and -C cannot be used together"); - if (genonly) - fatal("-S and -G cannot be used together"); - - ret = dst_key_fromnamedfile(predecessor, directory, - DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, - mctx, &prevkey); - if (ret != ISC_R_SUCCESS) - fatal("Invalid keyfile %s: %s", - predecessor, isc_result_totext(ret)); - if (!dst_key_isprivate(prevkey)) - fatal("%s is not a private key", predecessor); - - name = dst_key_name(prevkey); - alg = dst_key_alg(prevkey); - size = dst_key_size(prevkey); - flags = dst_key_flags(prevkey); - - dst_key_format(prevkey, keystr, sizeof(keystr)); - dst_key_getprivateformat(prevkey, &major, &minor); - if (major != DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) - fatal("Key %s has incompatible format version %d.%d\n\t" - "It is not possible to generate a successor key.", - keystr, major, minor); - - ret = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &when); - if (ret != ISC_R_SUCCESS) - fatal("Key %s has no activation date.\n\t" - "You must use dnssec-settime -A to set one " - "before generating a successor.", keystr); - - ret = dst_key_gettime(prevkey, DST_TIME_INACTIVE, &activate); - if (ret != ISC_R_SUCCESS) - fatal("Key %s has no inactivation date.\n\t" - "You must use dnssec-settime -I to set one " - "before generating a successor.", keystr); - - publish = activate - prepub; - if (publish < now) - fatal("Key %s becomes inactive\n\t" - "sooner than the prepublication period " - "for the new key ends.\n\t" - "Either change the inactivation date with " - "dnssec-settime -I,\n\t" - "or use the -i option to set a shorter " - "prepublication interval.", keystr); - - ret = dst_key_gettime(prevkey, DST_TIME_DELETE, &when); - if (ret != ISC_R_SUCCESS) - fprintf(stderr, "%s: WARNING: Key %s has no removal " - "date;\n\t it will remain in the zone " - "indefinitely after rollover.\n\t " - "You can use dnssec-settime -D to " - "change this.\n", program, keystr); - - setpub = setact = true; + keygen(&ctx, mctx, argc, argv); } - switch (alg) { - case DNS_KEYALG_RSASHA1: - case DNS_KEYALG_NSEC3RSASHA1: - case DNS_KEYALG_RSASHA256: - if (size != 0 && (size < 1024 || size > MAX_RSA)) - fatal("RSA key size %d out of range", size); - break; - case DNS_KEYALG_RSASHA512: - if (size != 0 && (size < 1024 || size > MAX_RSA)) - fatal("RSA key size %d out of range", size); - break; - case DNS_KEYALG_DH: - if (size != 0 && (size < 128 || size > 4096)) - fatal("DH key size %d out of range", size); - break; - case DST_ALG_ECDSA256: - size = 256; - break; - case DST_ALG_ECDSA384: - size = 384; - break; - case DST_ALG_ED25519: - size = 256; - break; - case DST_ALG_ED448: - size = 456; - break; - } - - if (alg != DNS_KEYALG_DH && generator != 0) - fatal("specified DH generator for a non-DH key"); - - if (nametype == NULL) { - if ((options & DST_TYPE_KEY) != 0) /* KEY */ - fatal("no nametype specified"); - flags |= DNS_KEYOWNER_ZONE; /* DNSKEY */ - } else if (strcasecmp(nametype, "zone") == 0) - flags |= DNS_KEYOWNER_ZONE; - else if ((options & DST_TYPE_KEY) != 0) { /* KEY */ - if (strcasecmp(nametype, "host") == 0 || - strcasecmp(nametype, "entity") == 0) - flags |= DNS_KEYOWNER_ENTITY; - else if (strcasecmp(nametype, "user") == 0) - flags |= DNS_KEYOWNER_USER; - else - fatal("invalid KEY nametype %s", nametype); - } else if (strcasecmp(nametype, "other") != 0) /* DNSKEY */ - fatal("invalid DNSKEY nametype %s", nametype); - - rdclass = strtoclass(classname); - - if (directory == NULL) - directory = "."; - - if ((options & DST_TYPE_KEY) != 0) /* KEY */ - flags |= signatory; - else if ((flags & DNS_KEYOWNER_ZONE) != 0) { /* DNSKEY */ - flags |= kskflag; - flags |= revflag; - } - - if (protocol == -1) - protocol = DNS_KEYPROTO_DNSSEC; - else if ((options & DST_TYPE_KEY) == 0 && - protocol != DNS_KEYPROTO_DNSSEC) - fatal("invalid DNSKEY protocol: %d", protocol); - - if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { - if (size > 0) - fatal("specified null key with non-zero size"); - if ((flags & DNS_KEYFLAG_SIGNATORYMASK) != 0) - fatal("specified null key with signing authority"); - } - - if ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE && - alg == DNS_KEYALG_DH) - { - fatal("a key with algorithm '%s' cannot be a zone key", - algname); - } - - switch(alg) { - case DNS_KEYALG_RSASHA1: - case DNS_KEYALG_NSEC3RSASHA1: - case DNS_KEYALG_RSASHA256: - case DNS_KEYALG_RSASHA512: - show_progress = true; - break; - - case DNS_KEYALG_DH: - param = generator; - break; - - case DST_ALG_ECDSA256: - case DST_ALG_ECDSA384: - case DST_ALG_ED25519: - case DST_ALG_ED448: - show_progress = true; - break; - } - - if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) - null_key = true; - - isc_buffer_init(&buf, filename, sizeof(filename) - 1); - - do { - conflict = false; - - if (!quiet && show_progress) { - fprintf(stderr, "Generating key pair."); - ret = dst_key_generate(name, alg, size, param, flags, - protocol, rdclass, mctx, &key, - &progress); - putc('\n', stderr); - fflush(stderr); - } else { - ret = dst_key_generate(name, alg, size, param, flags, - protocol, rdclass, mctx, &key, - NULL); - } - - if (ret != ISC_R_SUCCESS) { - char namestr[DNS_NAME_FORMATSIZE]; - char algstr[DNS_SECALG_FORMATSIZE]; - dns_name_format(name, namestr, sizeof(namestr)); - dns_secalg_format(alg, algstr, sizeof(algstr)); - fatal("failed to generate key %s/%s: %s\n", - namestr, algstr, isc_result_totext(ret)); - /* NOTREACHED */ - exit(-1); - } - - dst_key_setbits(key, dbits); - - /* - * Set key timing metadata (unless using -C) - * - * Creation date is always set to "now". - * - * For a new key without an explicit predecessor, publish - * and activation dates are set to "now" by default, but - * can both be overridden. - * - * For a successor key, activation is set to match the - * predecessor's inactivation date. Publish is set to 30 - * days earlier than that (XXX: this should be configurable). - * If either of the resulting dates are in the past, that's - * an error; the inactivation date of the predecessor key - * must be updated before a successor key can be created. - */ - if (!oldstyle) { - dst_key_settime(key, DST_TIME_CREATED, now); - - if (genonly && (setpub || setact)) - fatal("cannot use -G together with " - "-P or -A options"); - - if (setpub) - dst_key_settime(key, DST_TIME_PUBLISH, publish); - else if (setact && !unsetpub) - dst_key_settime(key, DST_TIME_PUBLISH, - activate - prepub); - else if (!genonly && !unsetpub) - dst_key_settime(key, DST_TIME_PUBLISH, now); - - if (setact) - dst_key_settime(key, DST_TIME_ACTIVATE, - activate); - else if (!genonly && !unsetact) - dst_key_settime(key, DST_TIME_ACTIVATE, now); - - if (setrev) { - if (kskflag == 0) - fprintf(stderr, "%s: warning: Key is " - "not flagged as a KSK, but -R " - "was used. Revoking a ZSK is " - "legal, but undefined.\n", - program); - dst_key_settime(key, DST_TIME_REVOKE, revokekey); - } - - if (setinact) - dst_key_settime(key, DST_TIME_INACTIVE, - inactive); - - if (setdel) { - if (setinact && deltime < inactive) - fprintf(stderr, "%s: warning: Key is " - "scheduled to be deleted " - "before it is scheduled to be " - "made inactive.\n", - program); - dst_key_settime(key, DST_TIME_DELETE, deltime); - } - - if (setsyncadd) - dst_key_settime(key, DST_TIME_SYNCPUBLISH, - syncadd); - - if (setsyncdel) - dst_key_settime(key, DST_TIME_SYNCDELETE, - syncdel); - - } else { - if (setpub || setact || setrev || setinact || - setdel || unsetpub || unsetact || - unsetrev || unsetinact || unsetdel || genonly || - setsyncadd || setsyncdel) - fatal("cannot use -C together with " - "-P, -A, -R, -I, -D, or -G options"); - /* - * Compatibility mode: Private-key-format - * should be set to 1.2. - */ - dst_key_setprivateformat(key, 1, 2); - } - - /* Set the default key TTL */ - if (setttl) - dst_key_setttl(key, ttl); - - /* - * Do not overwrite an existing key, or create a key - * if there is a risk of ID collision due to this key - * or another key being revoked. - */ - if (key_collision(key, name, directory, mctx, NULL)) { - conflict = true; - if (null_key) { - dst_key_free(&key); - break; - } - - if (verbose > 0) { - isc_buffer_clear(&buf); - ret = dst_key_buildfilename(key, 0, - directory, &buf); - if (ret == ISC_R_SUCCESS) - fprintf(stderr, - "%s: %s already exists, or " - "might collide with another " - "key upon revokation. " - "Generating a new key\n", - program, filename); - } - - dst_key_free(&key); - } - } while (conflict == true); - - if (conflict) - fatal("cannot generate a null key due to possible key ID " - "collision"); - - ret = dst_key_tofile(key, options, directory); - if (ret != ISC_R_SUCCESS) { - char keystr[DST_KEY_FORMATSIZE]; - dst_key_format(key, keystr, sizeof(keystr)); - fatal("failed to write key %s: %s\n", keystr, - isc_result_totext(ret)); - } - - isc_buffer_clear(&buf); - ret = dst_key_buildfilename(key, 0, NULL, &buf); - if (ret != ISC_R_SUCCESS) - fatal("dst_key_buildfilename returned: %s\n", - isc_result_totext(ret)); - printf("%s\n", filename); - dst_key_free(&key); - if (prevkey != NULL) - dst_key_free(&prevkey); - cleanup_logging(&log); dst_lib_destroy(); dns_name_destroy(); diff --git a/bin/dnssec/dnssec-keygen.docbook b/bin/dnssec/dnssec-keygen.docbook index 5833b79841..46ebec3dcd 100644 --- a/bin/dnssec/dnssec-keygen.docbook +++ b/bin/dnssec/dnssec-keygen.docbook @@ -66,6 +66,7 @@ + @@ -74,8 +75,9 @@ - + + @@ -84,6 +86,7 @@ + @@ -207,6 +210,18 @@ + + -d bits + + + Key size in bits. For the algorithms RSASHA1, NSEC3RSASA1, + RSASHA256 and RSASHA512 the key size must be in range 1024-4096. + DH size is between 128 and 4096. This option is ignored for + algorithms ECDSAP256SHA256, ECDSAP384SHA384, ED25519 and ED448. + + + + -E engine @@ -275,6 +290,24 @@ + + -k policy + + + Create keys for a specific dnssec-policy. If a policy uses + multiple keys, dnssec-keygen will generate + multiple keys. This will also create a ".state" file to keep + track of the key state. + + + This option creates keys according to the dnssec-policy + configuration, hence it cannot be used together with many of + the other options that dnssec-keygen + provides. + + + + -L ttl @@ -291,6 +324,16 @@ + + -l file + + + Provide a configuration file that contains a dnssec-policy + statement (matching the policy set with -k). + + + + -n nametype diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c index bcf32c72b7..1cc12e5190 100644 --- a/bin/dnssec/dnssec-settime.c +++ b/bin/dnssec/dnssec-settime.c @@ -88,6 +88,15 @@ usage(void) { fprintf(stderr, " -i : prepublication interval for " "successor key " "(default: 30 days)\n"); + fprintf(stderr, "Key state options:\n"); + fprintf(stderr, " -s: update key state file (default no)\n"); + fprintf(stderr, " -g state: set the goal state for this key\n"); + fprintf(stderr, " -d state date/[+-]offset: set the DS state\n"); + fprintf(stderr, " -k state date/[+-]offset: set the DNSKEY state\n"); + fprintf(stderr, " -r state date/[+-]offset: set the RRSIG (KSK) " + "state\n"); + fprintf(stderr, " -z state date/[+-]offset: set the RRSIG (ZSK) " + "state\n"); fprintf(stderr, "Printing options:\n"); fprintf(stderr, " -p C/P/Psync/A/R/I/D/Dsync/all: print a " "particular time value or values\n"); @@ -123,29 +132,87 @@ printtime(dst_key_t *key, int type, const char *tag, bool epoch, } } +static void +writekey(dst_key_t *key, const char *directory, bool write_state) +{ + char newname[1024]; + char keystr[DST_KEY_FORMATSIZE]; + isc_buffer_t buf; + isc_result_t result; + int options = DST_TYPE_PUBLIC|DST_TYPE_PRIVATE; + + if (write_state) { + options |= DST_TYPE_STATE; + } + + isc_buffer_init(&buf, newname, sizeof(newname)); + result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build public key filename: %s", + isc_result_totext(result)); + } + + result = dst_key_tofile(key, options, directory); + if (result != ISC_R_SUCCESS) { + dst_key_format(key, keystr, sizeof(keystr)); + fatal("Failed to write key %s: %s", keystr, + isc_result_totext(result)); + } + printf("%s\n", newname); + + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build private key filename: %s", + isc_result_totext(result)); + } + printf("%s\n", newname); + + if (write_state) { + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_STATE, directory, + &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build key state filename: %s", + isc_result_totext(result)); + } + printf("%s\n", newname); + } +} + int main(int argc, char **argv) { isc_result_t result; const char *engine = NULL; const char *filename = NULL; char *directory = NULL; - char newname[1024]; char keystr[DST_KEY_FORMATSIZE]; char *endp, *p; int ch; const char *predecessor = NULL; dst_key_t *prevkey = NULL; dst_key_t *key = NULL; - isc_buffer_t buf; dns_name_t *name = NULL; dns_secalg_t alg = 0; unsigned int size = 0; uint16_t flags = 0; int prepub = -1; + int options; dns_ttl_t ttl = 0; isc_stdtime_t now; + isc_stdtime_t dstime = 0, dnskeytime = 0; + isc_stdtime_t krrsigtime = 0, zrrsigtime = 0; isc_stdtime_t pub = 0, act = 0, rev = 0, inact = 0, del = 0; isc_stdtime_t prevact = 0, previnact = 0, prevdel = 0; + dst_key_state_t goal = DST_KEY_STATE_NA; + dst_key_state_t ds = DST_KEY_STATE_NA; + dst_key_state_t dnskey = DST_KEY_STATE_NA; + dst_key_state_t krrsig = DST_KEY_STATE_NA; + dst_key_state_t zrrsig = DST_KEY_STATE_NA; + bool setgoal = false, setds = false, setdnskey = false; + bool setkrrsig = false, setzrrsig = false; + bool setdstime = false, setdnskeytime = false; + bool setkrrsigtime = false, setzrrsigtime = false; bool setpub = false, setact = false; bool setrev = false, setinact = false; bool setdel = false, setttl = false; @@ -156,14 +223,17 @@ main(int argc, char **argv) { bool printact = false, printrev = false; bool printinact = false, printdel = false; bool force = false; - bool epoch = false; - bool changed = false; + bool epoch = false; + bool changed = false; + bool write_state = false; isc_log_t *log = NULL; isc_stdtime_t syncadd = 0, syncdel = 0; bool unsetsyncadd = false, setsyncadd = false; bool unsetsyncdel = false, setsyncdel = false; bool printsyncadd = false, printsyncdel = false; + options = DST_TYPE_PUBLIC|DST_TYPE_PRIVATE|DST_TYPE_STATE; + if (argc == 1) usage(); @@ -180,7 +250,7 @@ main(int argc, char **argv) { isc_stdtime_get(&now); -#define CMDLINE_FLAGS "A:D:E:fhI:i:K:L:P:p:R:S:uv:V" +#define CMDLINE_FLAGS "A:D:d:E:fg:hI:i:K:k:L:P:p:R:r:S:suv:Vz:" while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case 'E': @@ -339,6 +409,70 @@ main(int argc, char **argv) { case 'i': prepub = strtottl(isc_commandline_argument); break; + case 's': + write_state = true; + break; + case 'g': + if (setgoal) { + fatal("-g specified more than once"); + } + + goal = strtokeystate(isc_commandline_argument); + if (goal != DST_KEY_STATE_NA && + goal != DST_KEY_STATE_HIDDEN && + goal != DST_KEY_STATE_OMNIPRESENT) { + fatal("-g must be either none, hidden, or " + "omnipresent"); + } + setgoal = true; + break; + case 'd': + if (setds) { + fatal("-d specified more than once"); + } + + ds = strtokeystate(isc_commandline_argument); + setds = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + dstime = strtotime(isc_commandline_argument, + now, now, &setdstime); + break; + case 'k': + if (setdnskey) { + fatal("-k specified more than once"); + } + + dnskey = strtokeystate(isc_commandline_argument); + setdnskey = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + dnskeytime = strtotime(isc_commandline_argument, + now, now, &setdnskeytime); + break; + case 'r': + if (setkrrsig) { + fatal("-r specified more than once"); + } + + krrsig = strtokeystate(isc_commandline_argument); + setkrrsig = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + krrsigtime = strtotime(isc_commandline_argument, + now, now, &setkrrsigtime); + break; + case 'z': + if (setzrrsig) { + fatal("-z specified more than once"); + } + + zrrsig = strtokeystate(isc_commandline_argument); + setzrrsig = true; + (void)isoptarg(isc_commandline_argument, argv, usage); + zrrsigtime = strtotime(isc_commandline_argument, + now, now, &setzrrsigtime); + break; case '?': if (isc_commandline_option != '?') fprintf(stderr, "%s: invalid argument -%c\n", @@ -365,6 +499,12 @@ main(int argc, char **argv) { if (argc > isc_commandline_index + 1) fatal("Extraneous arguments"); + if ((setgoal || setds || setdnskey || setkrrsig || setzrrsig) && + !write_state) + { + fatal("Options -g, -d, -k, -r and -z require -s to be set"); + } + result = dst_lib_init(mctx, engine); if (result != ISC_R_SUCCESS) fatal("Could not initialize dst: %s", @@ -381,9 +521,7 @@ main(int argc, char **argv) { if (setact || unsetact) fatal("-S and -A cannot be used together"); - result = dst_key_fromnamedfile(predecessor, directory, - DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE, + result = dst_key_fromnamedfile(predecessor, directory, options, mctx, &prevkey); if (result != ISC_R_SUCCESS) fatal("Invalid keyfile %s: %s", @@ -475,9 +613,8 @@ main(int argc, char **argv) { isc_result_totext(result)); } - result = dst_key_fromnamedfile(filename, directory, - DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, - mctx, &key); + result = dst_key_fromnamedfile(filename, directory, options, mctx, + &key); if (result != ISC_R_SUCCESS) fatal("Invalid keyfile %s: %s", filename, isc_result_totext(result)); @@ -578,6 +715,11 @@ main(int argc, char **argv) { if (setttl) dst_key_setttl(key, ttl); + if (predecessor != NULL && prevkey != NULL) { + dst_key_setnum(prevkey, DST_NUM_SUCCESSOR, dst_key_id(key)); + dst_key_setnum(key, DST_NUM_PREDECESSOR, dst_key_id(prevkey)); + } + /* * No metadata changes were made but we're forcing an upgrade * to the new format anyway: use "-P now -A now" as the default @@ -588,6 +730,63 @@ main(int argc, char **argv) { changed = true; } + /* + * Make sure the key state goals are written. + */ + if (write_state) { + if (setgoal) { + if (goal == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_GOAL); + } else { + dst_key_setstate(key, DST_KEY_GOAL, goal); + } + changed = true; + } + if (setds) { + if (ds == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_DS); + dst_key_unsettime(key, DST_TIME_DS); + } else { + dst_key_setstate(key, DST_KEY_DS, ds); + dst_key_settime(key, DST_TIME_DS, dstime); + } + changed = true; + } + if (setdnskey) { + if (dnskey == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_DNSKEY); + dst_key_unsettime(key, DST_TIME_DNSKEY); + } else { + dst_key_setstate(key, DST_KEY_DNSKEY, dnskey); + dst_key_settime(key, DST_TIME_DNSKEY, + dnskeytime); + } + changed = true; + } + if (setkrrsig) { + if (krrsig == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_KRRSIG); + dst_key_unsettime(key, DST_TIME_KRRSIG); + } else { + dst_key_setstate(key, DST_KEY_KRRSIG, krrsig); + dst_key_settime(key, DST_TIME_KRRSIG, + krrsigtime); + } + changed = true; + } + if (setzrrsig) { + if (zrrsig == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_ZRRSIG); + dst_key_unsettime(key, DST_TIME_ZRRSIG); + } else { + dst_key_setstate(key, DST_KEY_ZRRSIG, zrrsig); + dst_key_settime(key, DST_TIME_ZRRSIG, + zrrsigtime); + } + changed = true; + } + } + if (!changed && setttl) changed = true; @@ -621,32 +820,10 @@ main(int argc, char **argv) { epoch, stdout); if (changed) { - isc_buffer_init(&buf, newname, sizeof(newname)); - result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, - &buf); - if (result != ISC_R_SUCCESS) { - fatal("Failed to build public key filename: %s", - isc_result_totext(result)); + writekey(key, directory, write_state); + if (predecessor != NULL && prevkey != NULL) { + writekey(prevkey, directory, write_state); } - - result = dst_key_tofile(key, DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, - directory); - if (result != ISC_R_SUCCESS) { - dst_key_format(key, keystr, sizeof(keystr)); - fatal("Failed to write key %s: %s", keystr, - isc_result_totext(result)); - } - - printf("%s\n", newname); - - isc_buffer_clear(&buf); - result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, - &buf); - if (result != ISC_R_SUCCESS) { - fatal("Failed to build private key filename: %s", - isc_result_totext(result)); - } - printf("%s\n", newname); } if (prevkey != NULL) diff --git a/bin/dnssec/dnssec-settime.docbook b/bin/dnssec/dnssec-settime.docbook index bf432bd44e..905d72af8f 100644 --- a/bin/dnssec/dnssec-settime.docbook +++ b/bin/dnssec/dnssec-settime.docbook @@ -64,6 +64,12 @@ + + + + + + keyfile @@ -88,11 +94,30 @@ When key metadata fields are changed, both files of a key pair (Knnnn.+aaa+iiiii.key and Knnnn.+aaa+iiiii.private) are regenerated. + + Metadata fields are stored in the private file. A human-readable description of the metadata is also placed in comments in the key file. The private file's permissions are always set to be inaccessible to anyone other than the owner (mode 0600). + + When working with state files, it is possible to update the timing + metadata in those files as well with . If this + option is used you can also update key states with + (DS), (DNSKEY), (RRSIG of KSK), + or (RRSIG of ZSK). Allowed states are HIDDEN, + RUMOURED, OMNIPRESENT, and UNRETENTIVE. + + + You can also set the goal state of the key with . + This should be either HIDDEN or OMNIPRESENT (representing whether the + key should be removed from the zone, or published). + + + It is NOT RECOMMENDED to manipulate state files manually except for + testing purposes. + OPTIONS @@ -319,6 +344,74 @@ + KEY STATE OPTIONS + + + Known key states are HIDDEN, RUMOURED, OMNIPRESENT and UNRETENTIVE. + These should not be set manually except for testing purposes. + + + + + + -s + + + When setting key timing data, also update the state file. + + + + + + -g + + + Set the goal state for this key. Must be HIDDEN or OMNIPRESENT. + + + + + + -d + + + Set the DS state for this key, and when it was last changed. + + + + + + -k + + + Set the DNSKEY state for this key, and when it was last changed. + + + + + + -r + + + Set the RRSIG (KSK) state for this key, and when it was last + changed. + + + + + + -z + + + Set the RRSIG (ZSK) state for this key, and when it was last + changed. + + + + + + + PRINTING OPTIONS diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 47b7c257c0..5703dde634 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -2717,7 +2717,7 @@ build_final_keylist(void) { * Update keylist with information from from the key repository. */ dns_dnssec_updatekeys(&keylist, &matchkeys, NULL, gorigin, keyttl, - &diff, ignore_kskflag, mctx, report); + &diff, mctx, report); /* * Update keylist with sync records. diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index d409965fed..aadd2ee39c 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -57,6 +57,11 @@ #include "dnssectool.h" +#define KEYSTATES_NVALUES 4 +static const char *keystates[KEYSTATES_NVALUES] = { + "hidden", "rumoured", "omnipresent", "unretentive", +}; + int verbose = 0; bool quiet = false; uint8_t dtype[8]; @@ -244,6 +249,21 @@ strtottl(const char *str) { return (ttl); } +dst_key_state_t +strtokeystate(const char *str) { + if (isnone(str)) { + return (DST_KEY_STATE_NA); + } + + for (int i = 0; i < KEYSTATES_NVALUES; i++) { + if (keystates[i] != NULL && + strcasecmp(str, keystates[i]) == 0) { + return (dst_key_state_t) i; + } + } + fatal("unknown key state"); +} + isc_stdtime_t strtotime(const char *str, int64_t now, int64_t base, bool *setp) diff --git a/bin/dnssec/dnssectool.h b/bin/dnssec/dnssectool.h index cddfb2f902..3e88e2959e 100644 --- a/bin/dnssec/dnssectool.h +++ b/bin/dnssec/dnssectool.h @@ -71,6 +71,8 @@ cleanup_logging(isc_log_t **logp); dns_ttl_t strtottl(const char *str); +dst_key_state_t strtokeystate(const char *str); + isc_stdtime_t strtotime(const char *str, int64_t now, int64_t base, bool *setp); diff --git a/bin/dnssec/win32/keygen.vcxproj.in b/bin/dnssec/win32/keygen.vcxproj.in index 72d1d9c99f..ddef528081 100644 --- a/bin/dnssec/win32/keygen.vcxproj.in +++ b/bin/dnssec/win32/keygen.vcxproj.in @@ -66,15 +66,15 @@ $(OutDir)$(TargetName).pdb true ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\isccfg\win32;..\..\..\lib\isccfg\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) CompileAsC Console true ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) - @OPENSSL_LIB@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) - $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIB@dnssectool.lib;libisc.lib;libisccfg.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\isccfg\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) @@ -94,7 +94,7 @@ .\$(Configuration)\ $(OutDir)$(TargetName).pdb ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\isccfg\win32;..\..\..\lib\isccfg\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) CompileAsC @@ -104,8 +104,8 @@ true ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) Default - @OPENSSL_LIB@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) - $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIB@dnssectool.lib;libisc.lib;libisccfg.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\isccfg\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) diff --git a/bin/named/config.c b/bin/named/config.c index 6ea56d8881..aeabf49057 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -58,6 +58,7 @@ options {\n\ "\ # deallocate-on-exit ;\n\ # directory \n\ + dnssec-policy \"none\";\n\ dump-file \"named_dump.db\";\n\ edns-udp-size 4096;\n\ # fake-iquery ;\n" diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 8443201908..97e0082ee3 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -64,6 +64,7 @@ struct named_server { dns_loadmgr_t * loadmgr; dns_zonemgr_t * zonemgr; dns_viewlist_t viewlist; + dns_kasplist_t kasplist; ns_interfacemgr_t * interfacemgr; dns_db_t * in_roothints; diff --git a/bin/named/include/named/zoneconf.h b/bin/named/include/named/zoneconf.h index 23c1f65f53..88633b24f4 100644 --- a/bin/named/include/named/zoneconf.h +++ b/bin/named/include/named/zoneconf.h @@ -27,19 +27,18 @@ ISC_LANG_BEGINDECLS isc_result_t named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, - dns_zone_t *zone, dns_zone_t *raw); + dns_kasplist_t* kasplist, dns_zone_t *zone, + dns_zone_t *raw); /*%< * Configure or reconfigure a zone according to the named.conf - * data in 'cctx' and 'czone'. + * data. * * The zone origin is not configured, it is assumed to have been set * at zone creation time. * * Require: - * \li 'lctx' to be initialized or NULL. - * \li 'cctx' to be initialized or NULL. * \li 'ac' to point to an initialized cfg_aclconfctx_t. - * \li 'czone' to be initialized. + * \li 'kasplist' to be initialized. * \li 'zone' to be initialized. */ diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index a013873e18..61016b6094 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -208,7 +208,7 @@ options { [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -290,18 +290,18 @@ options { fstrm-set-output-notify-threshold integer; fstrm-set-output-queue-model ( mpsc | spsc ); fstrm-set-output-queue-size integer; - fstrm-set-reopen-interval ttlval; + fstrm-set-reopen-interval duration; geoip-directory ( quoted_string | none ); glue-cache boolean; heartbeat-interval integer; hostname ( quoted_string | none ); inline-signing boolean; - interface-interval ttlval; + interface-interval duration; ixfr-from-differences ( primary | master | secondary | slave | boolean ); keep-response-order { address_match_element; ... }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; listen-on [ port integer ] [ dscp integer ] { address_match_element; ... }; @@ -315,28 +315,28 @@ options { masterfile-style ( full | relative ); match-mapped-addresses boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; max-rsa-exponent-size integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); memstatistics boolean; memstatistics-file quoted_string; message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -353,8 +353,8 @@ options { notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; pid-file ( quoted_string | none ); port integer; @@ -401,13 +401,13 @@ options { response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -421,7 +421,7 @@ options { serial-query-rate integer; serial-update-method ( date | increment | unixtime ); server-id ( quoted_string | none | hostname ); - servfail-ttl ttlval; + servfail-ttl duration; session-keyalg string; session-keyfile ( quoted_string | none ); session-keyname string; @@ -432,7 +432,7 @@ options { sortlist { address_match_element; ... }; stacksize ( default | unlimited | sizeval ); stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; startup-notify-rate integer; statistics-file quoted_string; synth-from-dnssec boolean; @@ -564,7 +564,7 @@ view string [ class ] { [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -642,7 +642,7 @@ view string [ class ] { secret string; }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; lmdb-mapsize sizeval; managed-keys { string ( static-key | initial-key @@ -655,25 +655,25 @@ view string [ class ] { match-destinations { address_match_element; ... }; match-recursive-only boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -689,8 +689,8 @@ view string [ class ] { notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; plugin ( query ) string [ { unspecified-text } ]; @@ -732,13 +732,13 @@ view string [ class ] { response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -783,14 +783,14 @@ view string [ class ] { integer | * ) ] [ dscp integer ]; transfers integer; }; - servfail-ttl ttlval; + servfail-ttl duration; sig-signing-nodes integer; sig-signing-signatures integer; sig-signing-type integer; sig-validity-interval integer [ integer ]; sortlist { address_match_element; ... }; stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; synth-from-dnssec boolean; transfer-format ( many-answers | one-answer ); transfer-source ( ipv4_address | * ) [ port ( integer | * ) ] [ @@ -842,6 +842,7 @@ view string [ class ] { dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-secure-to-insecure boolean; dnssec-update-mode ( maintain | no-resign ); file quoted_string; @@ -867,7 +868,7 @@ view string [ class ] { max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); min-refresh-time integer; min-retry-time integer; multi-master boolean; @@ -943,6 +944,7 @@ zone string [ class ] { dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-secure-to-insecure boolean; dnssec-update-mode ( maintain | no-resign ); file quoted_string; @@ -967,7 +969,7 @@ zone string [ class ] { max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); min-refresh-time integer; min-retry-time integer; multi-master boolean; @@ -1008,6 +1010,26 @@ zone string [ class ] { + DNSSEC-POLICY + + +dnssec-policy string { + dnskey-ttl ttlval; + keys { ( csk | ksk | zsk ) key-directory lifetime duration algorithm integer [ integer ] ; ... }; + parent-ds-ttl duration; + parent-propagation-delay duration; + parent-registration-delay duration; + publish-safety duration; + retire-safety duration; + signatures-refresh duration; + signatures-validity duration; + signatures-validity-dnskey duration; + zone-max-ttl duration; + zone-propagation-delay duration; +}; + + + FILES /etc/named.conf diff --git a/bin/named/server.c b/bin/named/server.c index e7f87e349e..ca216599ff 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -50,6 +50,7 @@ #include #include +#include #include #include @@ -68,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -459,8 +461,8 @@ configure_alternates(const cfg_obj_t *config, dns_view_t *view, static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - dns_viewlist_t *viewlist, cfg_aclconfctx_t *aclconf, - bool added, bool old_rpz_ok, + dns_viewlist_t *viewlist, dns_kasplist_t* kasplist, + cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, bool modify); static isc_result_t @@ -2039,7 +2041,13 @@ conf_dnsrps_num(const cfg_obj_t *obj, const char *name, return; } - conf_dnsrps_sadd(ctx, " %s %d", name, cfg_obj_asuint32(sub_obj)); + if (cfg_obj_isduration(sub_obj)) { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asduration(sub_obj)); + } else { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asuint32(sub_obj)); + } } /* @@ -2221,15 +2229,15 @@ configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, } obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); - if (cfg_obj_isuint32(obj)) { - zone->max_policy_ttl = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + zone->max_policy_ttl = cfg_obj_asduration(obj); } else { zone->max_policy_ttl = ttl_default; } obj = cfg_tuple_get(rpz_obj, "min-update-interval"); - if (cfg_obj_isuint32(obj)) { - zone->min_update_interval = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + zone->min_update_interval = cfg_obj_asduration(obj); } else { zone->min_update_interval = minupdateinterval_default; } @@ -2448,14 +2456,14 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps, } sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); - if (cfg_obj_isuint32(sub_obj)) - ttl_default = cfg_obj_asuint32(sub_obj); + if (cfg_obj_isduration(sub_obj)) + ttl_default = cfg_obj_asduration(sub_obj); else ttl_default = DNS_RPZ_MAX_TTL_DEFAULT; sub_obj = cfg_tuple_get(rpz_obj, "min-update-interval"); - if (cfg_obj_isuint32(sub_obj)) - minupdateinterval_default = cfg_obj_asuint32(sub_obj); + if (cfg_obj_isduration(sub_obj)) + minupdateinterval_default = cfg_obj_asduration(sub_obj); else minupdateinterval_default = DNS_RPZ_MINUPDATEINTERVAL_DEFAULT; @@ -2679,7 +2687,8 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { dns_view_thaw(ev->view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, ev->cbd->server->mctx, ev->view, - &ev->cbd->server->viewlist, cfg->actx, + &ev->cbd->server->viewlist, + &ev->cbd->server->kasplist, cfg->actx, true, false, ev->mod); dns_view_freeze(ev->view); isc_task_endexclusive(task); @@ -2992,8 +3001,8 @@ configure_catz_zone(dns_view_t *view, const cfg_obj_t *config, } obj = cfg_tuple_get(catz_obj, "min-update-interval"); - if (obj != NULL && cfg_obj_isuint32(obj)) - opts->min_update_interval = cfg_obj_asuint32(obj); + if (obj != NULL && cfg_obj_isduration(obj)) + opts->min_update_interval = cfg_obj_asduration(obj); cleanup: if (pview != NULL) @@ -3641,7 +3650,7 @@ configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) { result = named_config_get(maps, "fstrm-set-reopen-interval", &obj); if (result == ISC_R_SUCCESS) { - i = cfg_obj_asuint32(obj); + i = cfg_obj_asduration(obj); fstrm_iothr_options_set_reopen_interval(fopt, i); } @@ -3764,11 +3773,10 @@ register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj, * global defaults in 'config' used exclusively. */ static isc_result_t -configure_view(dns_view_t *view, dns_viewlist_t *viewlist, - cfg_obj_t *config, cfg_obj_t *vconfig, - named_cachelist_t *cachelist, const cfg_obj_t *bindkeys, - isc_mem_t *mctx, cfg_aclconfctx_t *actx, - bool need_hints) +configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, + cfg_obj_t *vconfig, named_cachelist_t *cachelist, + dns_kasplist_t *kasplist, const cfg_obj_t *bindkeys, + isc_mem_t *mctx, cfg_aclconfctx_t *actx, bool need_hints) { const cfg_obj_t *maps[4]; const cfg_obj_t *cfgmaps[3]; @@ -3895,8 +3903,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, { const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, mctx, view, - viewlist, actx, false, old_rpz_ok, - false)); + viewlist, kasplist, actx, false, + old_rpz_ok, false)); } /* @@ -4217,22 +4225,22 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "max-cache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->maxcachettl = cfg_obj_asuint32(obj); + view->maxcachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "max-ncache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->maxncachettl = cfg_obj_asuint32(obj); + view->maxncachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "min-cache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->mincachettl = cfg_obj_asuint32(obj); + view->mincachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "min-ncache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->minncachettl = cfg_obj_asuint32(obj); + view->minncachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "synth-from-dnssec", &obj); @@ -4242,7 +4250,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "max-stale-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - max_stale_ttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + max_stale_ttl = ISC_MAX(cfg_obj_asduration(obj), 1); obj = NULL; result = named_config_get(maps, "stale-answer-enable", &obj); @@ -4392,7 +4400,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "stale-answer-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->staleanswerttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + view->staleanswerttl = ISC_MAX(cfg_obj_asduration(obj), 1); /* * Resolver. @@ -4512,7 +4520,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "lame-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - lame_ttl = cfg_obj_asuint32(obj); + lame_ttl = cfg_obj_asduration(obj); if (lame_ttl > 1800) lame_ttl = 1800; dns_resolver_setlamettl(view->resolver, lame_ttl); @@ -5216,12 +5224,12 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "nta-recheck", &obj); INSIST(result == ISC_R_SUCCESS); - view->nta_recheck = cfg_obj_asuint32(obj); + view->nta_recheck = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "nta-lifetime", &obj); INSIST(result == ISC_R_SUCCESS); - view->nta_lifetime = cfg_obj_asuint32(obj); + view->nta_lifetime = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "preferred-glue", &obj); @@ -5464,7 +5472,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "servfail-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - fail_ttl = cfg_obj_asuint32(obj); + fail_ttl = cfg_obj_asduration(obj); if (fail_ttl > 30) fail_ttl = 30; dns_view_setfailttl(view, fail_ttl); @@ -5893,8 +5901,8 @@ create_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - dns_viewlist_t *viewlist, cfg_aclconfctx_t *aclconf, - bool added, bool old_rpz_ok, + dns_viewlist_t *viewlist, dns_kasplist_t *kasplist, + cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, bool modify) { dns_view_t *pview = NULL; /* Production view */ @@ -6111,8 +6119,8 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, zone)); dns_zone_setstats(zone, named_g_server->zonestats); } - CHECK(named_zone_configure(config, vconfig, zconfig, - aclconf, zone, NULL)); + CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, + kasplist, zone, NULL)); dns_zone_attach(zone, &view->redirect); goto cleanup; } @@ -6249,8 +6257,11 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, strcasecmp(ztypestr, "master") == 0 || strcasecmp(ztypestr, "secondary") == 0 || strcasecmp(ztypestr, "slave") == 0) && - cfg_map_get(zoptions, "inline-signing", &signing) == ISC_R_SUCCESS && - cfg_obj_asboolean(signing)) + ((cfg_map_get(zoptions, "inline-signing", &signing) == + ISC_R_SUCCESS && cfg_obj_asboolean(signing)) || + (cfg_map_get(zoptions, "dnssec-policy", &signing) == + ISC_R_SUCCESS && signing != NULL && + strcmp(cfg_obj_asstring(signing), "none") != 0))) { dns_zone_getraw(zone, &raw); if (raw == NULL) { @@ -6274,8 +6285,8 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, /* * Configure the zone. */ - CHECK(named_zone_configure(config, vconfig, zconfig, - aclconf, zone, raw)); + CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, kasplist, + zone, raw)); /* * Add the zone to its view in the new view list. @@ -7567,9 +7578,10 @@ configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, element = cfg_list_next(element)) { const cfg_obj_t *zconfig = cfg_listelt_value(element); - CHECK(configure_zone(config, zconfig, vconfig, mctx, - view, &named_g_server->viewlist, actx, - true, false, false)); + CHECK(configure_zone(config, zconfig, vconfig, mctx, view, + &named_g_server->viewlist, + &named_g_server->kasplist, actx, true, + false, false)); } result = ISC_R_SUCCESS; @@ -7753,8 +7765,9 @@ configure_newzone(const cfg_obj_t *zconfig, cfg_obj_t *config, cfg_aclconfctx_t *actx) { return (configure_zone(config, zconfig, vconfig, mctx, view, - &named_g_server->viewlist, actx, true, - false, false)); + &named_g_server->viewlist, + &named_g_server->kasplist, actx, true, false, + false)); } /*% @@ -7989,9 +8002,13 @@ load_configuration(const char *filename, named_server_t *server, const cfg_obj_t *obj; const cfg_obj_t *options; const cfg_obj_t *usev4ports, *avoidv4ports, *usev6ports, *avoidv6ports; + const cfg_obj_t *kasps; + dns_kasp_t *kasp = NULL; + dns_kasp_t *kasp_next = NULL; + dns_kasplist_t tmpkasplist, kasplist; const cfg_obj_t *views; dns_view_t *view = NULL; - dns_view_t *view_next; + dns_view_t *view_next = NULL; dns_viewlist_t tmpviewlist; dns_viewlist_t viewlist, builtin_viewlist; in_port_t listen_port, udpport_low, udpport_high; @@ -8020,6 +8037,7 @@ load_configuration(const char *filename, named_server_t *server, dns_aclenv_t *env = ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + ISC_LIST_INIT(kasplist); ISC_LIST_INIT(viewlist); ISC_LIST_INIT(builtin_viewlist); ISC_LIST_INIT(cachelist); @@ -8560,7 +8578,7 @@ load_configuration(const char *filename, named_server_t *server, obj = NULL; result = named_config_get(maps, "interface-interval", &obj); INSIST(result == ISC_R_SUCCESS); - interface_interval = cfg_obj_asuint32(obj) * 60; + interface_interval = cfg_obj_asduration(obj); if (interface_interval == 0) { CHECK(isc_timer_reset(server->interface_timer, isc_timertype_inactive, @@ -8634,6 +8652,39 @@ load_configuration(const char *filename, named_server_t *server, */ (void)configure_session_key(maps, server, named_g_mctx); + /* + * Create the DNSSEC key and signing policies (KASP). + */ + kasps = NULL; + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + kasp = NULL; + CHECK(cfg_kasp_fromconfig(kconfig, named_g_mctx, &kasplist, + &kasp)); + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + dns_kasp_detach(&kasp); + } + /* + * Create the default kasp. + */ + kasp = NULL; + CHECK(cfg_kasp_fromconfig(NULL, named_g_mctx, &kasplist, &kasp)); + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + dns_kasp_detach(&kasp); + + tmpkasplist = server->kasplist; + server->kasplist = kasplist; + kasplist = tmpkasplist; + + /* + * Configure the views. + */ views = NULL; (void)cfg_map_get(config, "view", &views); @@ -8712,8 +8763,8 @@ load_configuration(const char *filename, named_server_t *server, view = NULL; CHECK(find_view(vconfig, &viewlist, &view)); CHECK(configure_view(view, &viewlist, config, vconfig, - &cachelist, bindkeys, named_g_mctx, - named_g_aclconfctx, true)); + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, true)); dns_view_freeze(view); dns_view_detach(&view); } @@ -8726,9 +8777,8 @@ load_configuration(const char *filename, named_server_t *server, view = NULL; CHECK(find_view(NULL, &viewlist, &view)); CHECK(configure_view(view, &viewlist, config, NULL, - &cachelist, bindkeys, - named_g_mctx, named_g_aclconfctx, - true)); + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, true)); dns_view_freeze(view); dns_view_detach(&view); } @@ -8747,9 +8797,8 @@ load_configuration(const char *filename, named_server_t *server, CHECK(create_view(vconfig, &builtin_viewlist, &view)); CHECK(configure_view(view, &viewlist, config, vconfig, - &cachelist, bindkeys, - named_g_mctx, named_g_aclconfctx, - false)); + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, false)); dns_view_freeze(view); dns_view_detach(&view); view = NULL; @@ -9185,6 +9234,10 @@ load_configuration(const char *filename, named_server_t *server, dns_view_detach(&view); } + if (kasp != NULL) { + dns_kasp_detach(&kasp); + } + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); /* @@ -9207,6 +9260,15 @@ load_configuration(const char *filename, named_server_t *server, dns_view_detach(&view); } + /* + * Same cleanup for kasp list. + */ + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; kasp = kasp_next) { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + /* Same cleanup for cache list. */ while ((nsc = ISC_LIST_HEAD(cachelist)) != NULL) { ISC_LIST_UNLINK(cachelist, nsc, link); @@ -9454,7 +9516,8 @@ named_server_flushonshutdown(named_server_t *server, bool flush) { static void shutdown_server(isc_task_t *task, isc_event_t *event) { isc_result_t result; - dns_view_t *view, *view_next; + dns_view_t *view, *view_next = NULL; + dns_kasp_t *kasp, *kasp_next = NULL; named_server_t *server = (named_server_t *)event->ev_arg; bool flush = server->flushonshutdown; named_cache_t *nsc; @@ -9484,9 +9547,17 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { (void) named_server_saventa(server); - for (view = ISC_LIST_HEAD(server->viewlist); - view != NULL; - view = view_next) { + for (kasp = ISC_LIST_HEAD(server->kasplist); kasp != NULL; + kasp = kasp_next) + { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(server->kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = view_next) + { view_next = ISC_LIST_NEXT(view, link); ISC_LIST_UNLINK(server->viewlist, view, link); if (flush) @@ -9604,6 +9675,7 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) { /* Initialize server data structures. */ server->interfacemgr = NULL; + ISC_LIST_INIT(server->kasplist); ISC_LIST_INIT(server->viewlist); server->in_roothints = NULL; @@ -9791,6 +9863,7 @@ named_server_destroy(named_server_t **serverp) { isc_event_free(&server->reload_event); + INSIST(ISC_LIST_EMPTY(server->kasplist)); INSIST(ISC_LIST_EMPTY(server->viewlist)); INSIST(ISC_LIST_EMPTY(server->cachelist)); @@ -11760,7 +11833,10 @@ named_server_rekey(named_server_t *server, isc_lex_t *lex, keyopts = dns_zone_getkeyopts(zone); - /* "rndc loadkeys" requires "auto-dnssec maintain". */ + /* + * "rndc loadkeys" requires "auto-dnssec maintain" + * or a "dnssec-policy". + */ if ((keyopts & DNS_ZONEKEY_ALLOW) == 0) result = ISC_R_NOPERM; else if ((keyopts & DNS_ZONEKEY_MAINTAIN) == 0 && !fullsign) @@ -12925,7 +13001,8 @@ do_addzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, dns_view_thaw(view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, server->mctx, view, &server->viewlist, - cfg->actx, true, false, false); + &server->kasplist, cfg->actx, true, false, + false); dns_view_freeze(view); isc_task_endexclusive(server->task); @@ -13103,7 +13180,8 @@ do_modzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, dns_view_thaw(view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, server->mctx, view, &server->viewlist, - cfg->actx, true, false, true); + &server->kasplist, cfg->actx, true, false, + true); dns_view_freeze(view); exclusive = false; diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index aebea7aa1a..cb0ec51fcf 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -840,8 +841,9 @@ process_notifytype(dns_notifytype_t ntype, dns_zonetype_t ztype, isc_result_t named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, - const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, - dns_zone_t *zone, dns_zone_t *raw) + const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, + dns_kasplist_t *kasplist, dns_zone_t *zone, + dns_zone_t *raw) { isc_result_t result; const char *zname; @@ -853,6 +855,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *options = NULL; const cfg_obj_t *obj; const char *filename = NULL; + const char *kaspname = NULL; const char *dupcheck; dns_notifytype_t notifytype = dns_notifytype_yes; uint32_t count; @@ -868,7 +871,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, int32_t journal_size; bool multi; bool alt; - dns_view_t *view; + dns_view_t *view = NULL; + dns_kasp_t *kasp = NULL; bool check = false, fail = false; bool warn = false, ignore = false; bool ixfrdiff; @@ -1045,8 +1049,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } else if (result == ISC_R_SUCCESS) { dns_ttl_t maxttl = 0; /* unlimited */ - if (cfg_obj_isuint32(obj)) - maxttl = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) + maxttl = cfg_obj_asduration(obj); dns_zone_setmaxttl(zone, maxttl); if (raw != NULL) dns_zone_setmaxttl(raw, maxttl); @@ -1192,6 +1196,24 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, */ if (ztype != dns_zone_stub && ztype != dns_zone_staticstub && ztype != dns_zone_redirect) { + obj = NULL; + result = named_config_get(maps, "dnssec-policy", &obj); + if (result == ISC_R_SUCCESS) { + kaspname = cfg_obj_asstring(obj); + if (strcmp(kaspname, "none") != 0) { + result = dns_kasplist_find(kasplist, kaspname, + &kasp); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, + ISC_LOG_ERROR, + "'dnssec-policy '%s' not " + "found ", kaspname); + RETERR(result); + } + dns_zone_setkasp(zone, kasp); + } + } + obj = NULL; result = named_config_get(maps, "notify", &obj); INSIST(result == ISC_R_SUCCESS && obj != NULL); @@ -1481,38 +1503,52 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, bool allow = false, maint = false; bool sigvalinsecs; - obj = NULL; - result = named_config_get(maps, "dnskey-sig-validity", &obj); - INSIST(result == ISC_R_SUCCESS && obj != NULL); - seconds = cfg_obj_asuint32(obj) * 86400; + if (kasp) { + seconds = (uint32_t) dns_kasp_sigvalidity_dnskey(kasp); + } else { + obj = NULL; + result = named_config_get(maps, "dnskey-sig-validity", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + seconds = cfg_obj_asuint32(obj) * 86400; + } dns_zone_setkeyvalidityinterval(zone, seconds); - obj = NULL; - result = named_config_get(maps, "sig-validity-interval", &obj); - INSIST(result == ISC_R_SUCCESS && obj != NULL); - - sigvalinsecs = ns_server_getoption(named_g_server->sctx, - NS_SERVER_SIGVALINSECS); - validity = cfg_tuple_get(obj, "validity"); - seconds = cfg_obj_asuint32(validity); - if (!sigvalinsecs) { - seconds *= 86400; - } - dns_zone_setsigvalidityinterval(zone, seconds); - - resign = cfg_tuple_get(obj, "re-sign"); - if (cfg_obj_isvoid(resign)) { - seconds /= 4; - } else if (!sigvalinsecs) { - if (seconds > 7 * 86400) { - seconds = cfg_obj_asuint32(resign) * 86400; - } else { - seconds = cfg_obj_asuint32(resign) * 3600; - } + if (kasp) { + seconds = (uint32_t) dns_kasp_sigvalidity(kasp); + dns_zone_setsigvalidityinterval(zone, seconds); + seconds = (uint32_t) dns_kasp_sigrefresh(kasp); + dns_zone_setsigresigninginterval(zone, seconds); } else { - seconds = cfg_obj_asuint32(resign); + obj = NULL; + result = named_config_get(maps, "sig-validity-interval", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + + sigvalinsecs = ns_server_getoption(named_g_server->sctx, + NS_SERVER_SIGVALINSECS); + validity = cfg_tuple_get(obj, "validity"); + seconds = cfg_obj_asuint32(validity); + if (!sigvalinsecs) { + seconds *= 86400; + } + dns_zone_setsigvalidityinterval(zone, seconds); + + resign = cfg_tuple_get(obj, "re-sign"); + if (cfg_obj_isvoid(resign)) { + seconds /= 4; + } else if (!sigvalinsecs) { + seconds = cfg_obj_asuint32(resign); + if (seconds > 7 * 86400) { + seconds *= 86400; + } else { + seconds *= 3600; + } + } else { + seconds = cfg_obj_asuint32(resign); + } + dns_zone_setsigresigninginterval(zone, seconds); } - dns_zone_setsigresigninginterval(zone, seconds); obj = NULL; result = named_config_get(maps, "key-directory", &obj); @@ -1541,12 +1577,20 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, INSIST(result == ISC_R_SUCCESS && obj != NULL); dns_zone_setoption(zone, DNS_ZONEOPT_UPDATECHECKKSK, cfg_obj_asboolean(obj)); + /* + * This setting will be ignored if dnssec-policy is used. + * named-checkconf will error if both are configured. + */ obj = NULL; result = named_config_get(maps, "dnssec-dnskey-kskonly", &obj); INSIST(result == ISC_R_SUCCESS && obj != NULL); dns_zone_setoption(zone, DNS_ZONEOPT_DNSKEYKSKONLY, cfg_obj_asboolean(obj)); + /* + * This setting will be ignored if dnssec-policy is used. + * named-checkconf will error if both are configured. + */ obj = NULL; result = named_config_get(maps, "dnssec-loadkeys-interval", @@ -1557,7 +1601,11 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, obj = NULL; result = cfg_map_get(zoptions, "auto-dnssec", &obj); - if (result == ISC_R_SUCCESS) { + if (dns_zone_getkasp(zone) != NULL) { + dns_zone_setkeyopt(zone, DNS_ZONEKEY_ALLOW, true); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_CREATE, true); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_MAINTAIN, true); + } else if (result == ISC_R_SUCCESS) { const char *arg = cfg_obj_asstring(obj); if (strcasecmp(arg, "allow") == 0) { allow = true; @@ -1570,6 +1618,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, ISC_UNREACHABLE(); } dns_zone_setkeyopt(zone, DNS_ZONEKEY_ALLOW, allow); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_CREATE, false); dns_zone_setkeyopt(zone, DNS_ZONEKEY_MAINTAIN, maint); } } diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook index c5c1c4e0a2..83b7eb291b 100644 --- a/bin/rndc/rndc.docbook +++ b/bin/rndc/rndc.docbook @@ -443,7 +443,8 @@ allowed to incrementally re-sign over time. - This command requires that the + This command requires that the zone is configured with a + dnssec-policy, or that the auto-dnssec zone option be set to maintain, and also requires the zone to be configured to @@ -849,7 +850,8 @@ re-signed with the new key set. - This command requires that the + This command requires that the zone is configured with a + dnssec-policy, or that the auto-dnssec zone option be set to allow or maintain, diff --git a/bin/tests/system/checkconf/bad-kasp1.conf b/bin/tests/system/checkconf/bad-kasp1.conf new file mode 100644 index 0000000000..686160f983 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp1.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Using the keyword 'default' is not allowed. +dnssec-policy "default" { + signatures-refresh P5D; +}; + +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "default"; +}; + diff --git a/bin/tests/system/checkconf/bad-kasp2.conf b/bin/tests/system/checkconf/bad-kasp2.conf new file mode 100644 index 0000000000..a7b44ab6d0 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp2.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "good-kasp.conf"; + +// Bad zone configuration because this has dnssec-policy and other DNSSEC sign +// configuration options (auto-dnssec). +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "test"; + auto-dnssec maintain; + allow-update { any; }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp3.conf b/bin/tests/system/checkconf/bad-kasp3.conf new file mode 100644 index 0000000000..104100dc59 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp3.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "good-kasp.conf"; + +// Bad zone configuration because this has dnssec-policy with no matching +// dnssec-policy configuration (good-kasp.conf has "test", zone refers to +// "nosuchpolicy". +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "nosuchpolicy"; +}; + diff --git a/bin/tests/system/checkconf/bad-kasp4.conf b/bin/tests/system/checkconf/bad-kasp4.conf new file mode 100644 index 0000000000..efb2cbefa8 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp4.conf @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Bad kasp configuration because this has an invalid duration for +// signatures-refresh. +dnssec-policy "badduration" { + signatures-refresh PT20Sabcd; +}; + +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "badduration"; +}; + diff --git a/bin/tests/system/checkconf/bad-kasp5.conf b/bin/tests/system/checkconf/bad-kasp5.conf new file mode 100644 index 0000000000..a399079db5 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp5.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Using the keyword 'none' is not allowed. +dnssec-policy "none" { + signatures-refresh P5D; +}; + +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "none"; +}; + diff --git a/bin/tests/system/checkconf/clean.sh b/bin/tests/system/checkconf/clean.sh index 9ac839b09d..989daff341 100644 --- a/bin/tests/system/checkconf/clean.sh +++ b/bin/tests/system/checkconf/clean.sh @@ -10,6 +10,7 @@ # information regarding copyright ownership. rm -f good.conf.in good.conf.out badzero.conf *.out +rm -f good-kasp.conf.in rm -rf test.keydir rm -f checkconf.out* rm -f diff.out* diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf new file mode 100644 index 0000000000..35abe1e6ca --- /dev/null +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -0,0 +1,56 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * This is just a random selection of DNSSEC configuration options. + */ + +/* cut here */ +dnssec-policy "test" { + dnskey-ttl 3600; + keys { + ksk key-directory lifetime P1Y algorithm 13 256; + zsk key-directory lifetime P30D algorithm 13; + csk key-directory lifetime P30D algorithm 8 2048; + }; + publish-safety PT3600S; + retire-safety PT3600S; + signatures-refresh P3D; + signatures-validity P2W; + signatures-validity-dnskey P14D; + zone-max-ttl 86400; + zone-propagation-delay PT5M; + parent-ds-ttl 7200; + parent-propagation-delay PT1H; + parent-registration-delay P1D; +}; +options { + dnssec-policy "default"; +}; +zone "example1" { + type master; + file "example1.db"; +}; +zone "example2" { + type master; + file "example2.db"; + dnssec-policy "test"; +}; +zone "example3" { + type master; + file "example3.db"; + dnssec-policy "default"; +}; +zone "example4" { + type master; + file "example4.db"; + dnssec-policy "none"; +}; diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf index b6136d6f3b..37d3de6504 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -14,6 +14,24 @@ */ /* cut here */ +dnssec-policy "test" { + dnskey-ttl 3600; + keys { + ksk key-directory lifetime P1Y algorithm 13 256; + zsk key-directory lifetime P30D algorithm 13; + csk key-directory lifetime P30D algorithm 8 2048; + }; + publish-safety PT3600S; + retire-safety PT3600S; + signatures-refresh P3D; + signatures-validity P2W; + signatures-validity-dnskey P14D; + zone-max-ttl 86400; + zone-propagation-delay PT5M; + parent-ds-ttl 7200; + parent-propagation-delay PT1H; + parent-registration-delay P1D; +}; options { avoid-v4-udp-ports { 100; @@ -60,6 +78,7 @@ options { validate-except { "corp"; }; + dnssec-policy "test"; transfer-source 0.0.0.0 dscp 63; zone-statistics none; }; @@ -140,6 +159,28 @@ view "third" { }; }; }; +view "fourth" { + zone "dnssec-test" { + type master; + file "dnssec-test.db"; + dnssec-policy "test"; + }; + zone "dnssec-default" { + type master; + file "dnssec-default.db"; + dnssec-policy "default"; + }; + zone "dnssec-inherit" { + type master; + file "dnssec-inherit.db"; + }; + zone "dnssec-none" { + type master; + file "dnssec-none.db"; + dnssec-policy "none"; + }; + dnssec-policy "default"; +}; view "chaos" chaos { zone "hostname.bind" chaos { type master; diff --git a/bin/tests/system/checkconf/good.zonelist b/bin/tests/system/checkconf/good.zonelist index e4504fc672..dff4d170ca 100644 --- a/bin/tests/system/checkconf/good.zonelist +++ b/bin/tests/system/checkconf/good.zonelist @@ -8,4 +8,8 @@ clone IN third in-view first dnssec IN third master p IN third primary s IN third secondary +dnssec-test IN fourth master +dnssec-default IN fourth master +dnssec-inherit IN fourth master +dnssec-none IN fourth master hostname.bind chaos chaos master diff --git a/bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf b/bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf new file mode 100644 index 0000000000..d7c1bf8123 --- /dev/null +++ b/bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf @@ -0,0 +1,28 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "good-kasp.conf"; + +zone "nsec3.net" { + type master; + file "nsec3.db"; + dnssec-policy "test"; + auto-dnssec maintain; + dnskey-sig-validity 3600; + dnssec-dnskey-kskonly yes; + dnssec-secure-to-insecure yes; + dnssec-update-mode maintain; + inline-signing yes; + sig-validity-interval 3600; + update-check-ksk yes; + allow-update { any; }; +}; + diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index 6146754320..45e6cefb42 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -466,5 +466,38 @@ grep "'geoip-use-ecs' is obsolete" < checkconf.out$n > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; ret=1; fi status=`expr $status + $ret` +n=`expr $n + 1` +echo_i "checking named-checkconf kasp warnings ($n)" +ret=0 +$CHECKCONF kasp-and-other-dnssec-options.conf > checkconf.out$n 2>&1 +grep "'auto-dnssec maintain;' cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnskey-sig-validity: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnssec-dnskey-kskonly: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnssec-secure-to-insecure: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnssec-update-mode: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "inline-signing: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "sig-validity-interval: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "update-check-ksk: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo_i "check that a good 'kasp' configuration is accepted ($n)" +ret=0 +$CHECKCONF good-kasp.conf > checkconf.out$n 2>/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo_i "checking that named-checkconf prints a known good kasp config ($n)" +ret=0 +awk 'BEGIN { ok = 0; } /cut here/ { ok = 1; getline } ok == 1 { print }' good-kasp.conf > good-kasp.conf.in +[ -s good-kasp.conf.in ] || ret=1 +$CHECKCONF -p good-kasp.conf.in | grep -v '^good-kasp.conf.in:' > good-kasp.conf.out 2>&1 || ret=1 +cmp good-kasp.conf.in good-kasp.conf.out || ret=1 + +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 3ca6f92475..6d9ba5a646 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1485,7 +1485,7 @@ n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) -echo_i "checking that dnsssec-signzone updates originalttl on ttl changes ($n)" +echo_i "checking that dnssec-signzone updates originalttl on ttl changes ($n)" ret=0 zone=example key1=$($KEYGEN -K signer -q -a RSASHA1 -b 1024 -n zone $zone) diff --git a/bin/tests/system/kasp/README b/bin/tests/system/kasp/README new file mode 100644 index 0000000000..ceafd19772 --- /dev/null +++ b/bin/tests/system/kasp/README @@ -0,0 +1,13 @@ +Copyright (C) Internet Systems Consortium, Inc. ("ISC") + +See COPYRIGHT in the source root or http://isc.org/copyright.html for terms. + +The test setup for the KASP tests. + +ns1 is reserved for the root server. + +ns2 is running primary service for ns3. + +ns3 is an authoritative server for the various test domains. + +ns4 and ns5 are authoritative servers for various test domains related to views. diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh new file mode 100644 index 0000000000..803dd703cd --- /dev/null +++ b/bin/tests/system/kasp/clean.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +set -e + +rm -f ./keygen.* +rm -f ./K*.private ./K*.key ./K*.state ./K*.cmp +rm -rf ./keys/ +rm -f dig.out* rrsig.out.* keyevent.out.* +rm -f ns*/named.conf ns*/named.memstats ns*/named.run* +rm -f ns*/*.jnl ns*/*.jbk +rm -f ns*/K*.private ns*/K*.key ns*/K*.state +rm -f ns*/dsset-* ns*/*.db ns*/*.db.signed +rm -f ns*/keygen.out.* ns*/settime.out.* ns*/signer.out.* +rm -f ns*/managed-keys.bind +rm -f ns*/*.mkeys +# NS3 specific +rm -f ns3/zones ns3/*.db.infile diff --git a/bin/tests/system/kasp/kasp.conf b/bin/tests/system/kasp/kasp.conf new file mode 100644 index 0000000000..5b09682fcf --- /dev/null +++ b/bin/tests/system/kasp/kasp.conf @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * This is just a random selection of configuration options. + */ + +dnssec-policy "kasp" { + dnskey-ttl 200; + + keys { + csk key-directory lifetime P1Y algorithm 13; + ksk key-directory lifetime P1Y algorithm 8; + zsk key-directory lifetime P30D algorithm 8 1024; + zsk key-directory lifetime P6M algorithm 8 2000; + }; +}; diff --git a/bin/tests/system/kasp/ns2/named.conf.in b/bin/tests/system/kasp/ns2/named.conf.in new file mode 100644 index 0000000000..cad71da5b4 --- /dev/null +++ b/bin/tests/system/kasp/ns2/named.conf.in @@ -0,0 +1,58 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + dnssec-policy "none"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +/* Inherit dnssec-policy (which is none) */ + +zone "unsigned.tld" { + type master; + file "unsigned.tld.db"; +}; + +/* Override dnssec-policy */ + +zone "signed.tld" { + type master; + dnssec-policy "default"; + file "signed.tld.db"; +}; + +/* Primary service for ns3 */ + +zone "secondary.kasp" { + type master; + file "secondary.kasp.db"; + allow-transfer { 10.53.0.3; }; + notify yes; +}; diff --git a/bin/tests/system/kasp/ns2/secondary.kasp.db.in b/bin/tests/system/kasp/ns2/secondary.kasp.db.in new file mode 100644 index 0000000000..12c678e8b3 --- /dev/null +++ b/bin/tests/system/kasp/ns2/secondary.kasp.db.in @@ -0,0 +1,27 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA secondary.kasp. hostmaster.kasp. ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 + NS ns3 +ns2 A 10.53.0.2 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns2/secondary.kasp.db.in2 b/bin/tests/system/kasp/ns2/secondary.kasp.db.in2 new file mode 100644 index 0000000000..6fb00a9c30 --- /dev/null +++ b/bin/tests/system/kasp/ns2/secondary.kasp.db.in2 @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA secondary.kasp. hostmaster.kasp. ( + 2 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 + NS ns3 +ns2 A 10.53.0.2 +ns3 A 10.53.0.3 + +a A 10.0.0.11 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 + diff --git a/bin/tests/system/kasp/ns2/setup.sh b/bin/tests/system/kasp/ns2/setup.sh new file mode 100644 index 0000000000..588735d0a6 --- /dev/null +++ b/bin/tests/system/kasp/ns2/setup.sh @@ -0,0 +1,33 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns2/setup.sh" + +zone="secondary.kasp" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="${zonefile}.in" +cp $infile $zonefile + +zone="signed.tld" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="template.tld.db.in" +cp $infile $zonefile + +zone="unsigned.tld" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="template.tld.db.in" +cp $infile $zonefile diff --git a/bin/tests/system/kasp/ns2/template.tld.db.in b/bin/tests/system/kasp/ns2/template.tld.db.in new file mode 100644 index 0000000000..7d8b924f64 --- /dev/null +++ b/bin/tests/system/kasp/ns2/template.tld.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA secondary.kasp. hostmaster.kasp. ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in new file mode 100644 index 0000000000..c9ae05894b --- /dev/null +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -0,0 +1,319 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS3 + +include "policies/kasp.conf"; +include "policies/autosign.conf"; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + dnssec-policy "rsasha1"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +/* Zones that are getting initially signed */ + +/* The default case: No keys created, using default policy. */ +zone "default.kasp" { + type master; + file "default.kasp.db"; + dnssec-policy "default"; +}; + +/* A master zone with dnssec-policy, no keys created. */ +zone "rsasha1.kasp" { + type master; + file "rsasha1.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* A zone that inherits dnssec-policy. */ +zone "inherit.kasp" { + type master; + file "inherit.kasp.db"; +}; + +/* A zone that overrides dnssec-policy. */ +zone "unsigned.kasp" { + type master; + file "unsigned.kasp.db"; + dnssec-policy "none"; +}; + +/* A master zone with dnssec-policy but keys already created. */ +zone "dnssec-keygen.kasp" { + type master; + file "dnssec-keygen.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* A secondary zone with dnssec-policy. */ +zone "secondary.kasp" { + type secondary; + masters { 10.53.0.2; }; + file "secondary.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * A configured dnssec-policy but some keys already created. + */ +zone "some-keys.kasp" { + type master; + file "some-keys.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * A configured dnssec-policy but some keys already in use. + */ +zone "legacy-keys.kasp" { + type master; + file "legacy-keys.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * A configured dnssec-policy with (too) many keys pregenerated. + */ +zone "pregenerated.kasp" { + type master; + file "pregenerated.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * Different algorithms. + */ +zone "rsasha1-nsec3.kasp" { + type master; + file "rsasha1-nsec3.kasp.db"; + dnssec-policy "rsasha1-nsec3"; +}; +zone "rsasha256.kasp" { + type master; + file "rsasha256.kasp.db"; + dnssec-policy "rsasha256"; +}; +zone "rsasha512.kasp" { + type master; + file "rsasha512.kasp.db"; + dnssec-policy "rsasha512"; +}; +zone "ecdsa256.kasp" { + type master; + file "ecdsa256.kasp.db"; + dnssec-policy "ecdsa256"; +}; +zone "ecdsa384.kasp" { + type master; + file "ecdsa384.kasp.db"; + dnssec-policy "ecdsa384"; +}; + +/* + * Zones in different signing states. + */ + +/* + * Zone that has expired signatures. + */ +zone "expired-sigs.autosign" { + type master; + file "expired-sigs.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has valid, fresh signatures. + */ +zone "fresh-sigs.autosign" { + type master; + file "fresh-sigs.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has unfresh signatures. + */ +zone "unfresh-sigs.autosign" { + type master; + file "unfresh-sigs.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has missing private ZSK. + */ +zone "zsk-missing.autosign" { + type master; + file "zsk-missing.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has inactive ZSK. + */ +zone "zsk-retired.autosign" { + type master; + file "zsk-retired.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zones for testing ZSK Pre-Publication steps. + */ +zone "step1.zsk-prepub.autosign" { + type master; + file "step1.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step2.zsk-prepub.autosign" { + type master; + file "step2.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step3.zsk-prepub.autosign" { + type master; + file "step3.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step4.zsk-prepub.autosign" { + type master; + file "step4.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step5.zsk-prepub.autosign" { + type master; + file "step5.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; + +/* + * Zones for testing KSK Double-KSK steps. + */ +zone "step1.ksk-doubleksk.autosign" { + type master; + file "step1.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step2.ksk-doubleksk.autosign" { + type master; + file "step2.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step3.ksk-doubleksk.autosign" { + type master; + file "step3.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step4.ksk-doubleksk.autosign" { + type master; + file "step4.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step5.ksk-doubleksk.autosign" { + type master; + file "step5.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step6.ksk-doubleksk.autosign" { + type master; + file "step6.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; + +/* + * Zones for testing CSK rollover steps. + */ +zone "step1.csk-roll.autosign" { + type master; + file "step1.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step2.csk-roll.autosign" { + type master; + file "step2.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step3.csk-roll.autosign" { + type master; + file "step3.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step4.csk-roll.autosign" { + type master; + file "step4.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step5.csk-roll.autosign" { + type master; + file "step5.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step6.csk-roll.autosign" { + type master; + file "step6.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step7.csk-roll.autosign" { + type master; + file "step7.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; + +zone "step1.csk-roll2.autosign" { + type master; + file "step1.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step2.csk-roll2.autosign" { + type master; + file "step2.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step3.csk-roll2.autosign" { + type master; + file "step3.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step4.csk-roll2.autosign" { + type master; + file "step4.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step5.csk-roll2.autosign" { + type master; + file "step5.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step6.csk-roll2.autosign" { + type master; + file "step6.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; diff --git a/bin/tests/system/kasp/ns3/policies/autosign.conf b/bin/tests/system/kasp/ns3/policies/autosign.conf new file mode 100644 index 0000000000..664693a445 --- /dev/null +++ b/bin/tests/system/kasp/ns3/policies/autosign.conf @@ -0,0 +1,110 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "autosign" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 300; + + keys { + ksk key-directory lifetime P2Y algorithm 13; + zsk key-directory lifetime P1Y algorithm 13; + }; +}; + +dnssec-policy "zsk-prepub" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 3600; + publish-safety P1D; + retire-safety P2D; + + keys { + ksk key-directory lifetime P2Y algorithm 13; + zsk key-directory lifetime P30D algorithm 13; + }; + + zone-propagation-delay PT1H; + zone-max-ttl 1d; +}; + +dnssec-policy "ksk-doubleksk" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 2h; + publish-safety P1D; + retire-safety P2D; + + keys { + ksk key-directory lifetime P60D algorithm 13; + zsk key-directory lifetime P1Y algorithm 13; + }; + + zone-propagation-delay PT1H; + zone-max-ttl 1d; + + parent-ds-ttl 3600; + parent-registration-delay P1D; + parent-propagation-delay PT1H; +}; + +dnssec-policy "csk-roll" { + + signatures-refresh P5D; + signatures-validity 30d; + signatures-validity-dnskey 30d; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 2h; + + keys { + csk key-directory lifetime P6M algorithm 13; + }; + + zone-propagation-delay 1h; + zone-max-ttl P1D; + + parent-ds-ttl 1h; + parent-registration-delay 1d; + parent-propagation-delay 1h; +}; + +dnssec-policy "csk-roll2" { + + signatures-refresh 12h; + signatures-validity P1D; + signatures-validity-dnskey P1D; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 1h; + + keys { + csk key-directory lifetime P6M algorithm 13; + }; + + zone-propagation-delay PT1H; + zone-max-ttl 1d; + + parent-ds-ttl PT1H; + parent-registration-delay P1W; + parent-propagation-delay PT1H; +}; diff --git a/bin/tests/system/kasp/ns3/policies/kasp.conf b/bin/tests/system/kasp/ns3/policies/kasp.conf new file mode 100644 index 0000000000..fa60476e65 --- /dev/null +++ b/bin/tests/system/kasp/ns3/policies/kasp.conf @@ -0,0 +1,70 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "rsasha1" { + dnskey-ttl 1234; + + keys { + ksk key-directory lifetime P10Y algorithm 5; + zsk key-directory lifetime P5Y algorithm 5; + zsk key-directory lifetime P1Y algorithm 5 2000; + }; +}; + +dnssec-policy "rsasha1-nsec3" { + dnskey-ttl 1234; + + keys { + ksk key-directory lifetime P10Y algorithm 7; + zsk key-directory lifetime P5Y algorithm 7; + zsk key-directory lifetime P1Y algorithm 7 2000; + }; +}; + +dnssec-policy "rsasha256" { + dnskey-ttl 1234; + + keys { + ksk key-directory lifetime P10Y algorithm 8; + zsk key-directory lifetime P5Y algorithm 8; + zsk key-directory lifetime P1Y algorithm 8 2000; + }; +}; + +dnssec-policy "rsasha512" { + dnskey-ttl 1234; + + keys { + ksk key-directory lifetime P10Y algorithm 10; + zsk key-directory lifetime P5Y algorithm 10; + zsk key-directory lifetime P1Y algorithm 10 2000; + }; +}; + +dnssec-policy "ecdsa256" { + dnskey-ttl 1234; + + keys { + ksk key-directory lifetime P10Y algorithm 13; + zsk key-directory lifetime P5Y algorithm 13; + zsk key-directory lifetime P1Y algorithm 13 256; + }; +}; + +dnssec-policy "ecdsa384" { + dnskey-ttl 1234; + + keys { + ksk key-directory lifetime P10Y algorithm 14; + zsk key-directory lifetime P5Y algorithm 14; + zsk key-directory lifetime P1Y algorithm 14 384; + }; +}; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh new file mode 100644 index 0000000000..5a4b44bca5 --- /dev/null +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -0,0 +1,654 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns3/setup.sh" + +setup() { + zone="$1" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + infile="${zone}.db.infile" + echo $zone >> zones +} + +private_type_record() { + _zone=$1 + _algorithm=$2 + _keyfile=$3 + + _id=$(keyfile_to_key_id "$_keyfile") + + printf "%s. 0 IN TYPE65534 \# 5 %02x%04x0000\n" $_zone $_algorithm $_id +} + + +# Make lines shorter by storing key states in environment variables. +H="HIDDEN" +R="RUMOURED" +O="OMNIPRESENT" +U="UNRETENTIVE" + +# +# Set up zones that will be initially signed. +# +for zn in default rsasha1 dnssec-keygen some-keys legacy-keys pregenerated \ + rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 inherit +do + setup "${zn}.kasp" + cp template.db.in $zonefile +done + +# Set up zone that stays unsigned. +zone="unsigned.kasp" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="${zone}.db.infile" +cp template.db.in $zonefile + +# Some of these zones already have keys. +zone="dnssec-keygen.kasp" +$KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.1 2>&1 + +zone="some-keys.kasp" +$KEYGEN -P none -A none -a RSASHA1 -b 2000 -L 1234 $zone > keygen.out.$zone.1 2>&1 +$KEYGEN -P none -A none -a RSASHA1 -f KSK -L 1234 $zone > keygen.out.$zone.2 2>&1 + +zone="legacy.kasp" +$KEYGEN -a RSASHA1 -b 2000 -L 1234 $zone > keygen.out.$zone.1 2>&1 +$KEYGEN -a RSASHA1 -f KSK -L 1234 $zone > keygen.out.$zone.2 2>&1 + +zone="pregenerated.kasp" +$KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.1 2>&1 +$KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.2 2>&1 + +# +# Set up zones that are already signed. +# + +# These signatures are set to expire long in the past, update immediately. +setup expired-sigs.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -PS -x -s now-2mo -e now-1mo -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# These signatures are still good, and can be reused. +setup fresh-sigs.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# These signatures are still good, but not fresh enough, update immediately. +setup unfresh-sigs.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1w -e now+1w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# These signatures are already expired, and the private ZSK is missing. +setup zsk-missing.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 +rm -f ${ZSK}.private + +# These signatures are already expired, and the private ZSK is retired. +setup zsk-retired.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 +$SETTIME -s -I now -g HIDDEN $ZSK > settime.out.$zone.3 2>&1 + +# +# The zones at zsk-prepub.autosign represent the various steps of a ZSK +# Pre-Publication rollover. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to pre-publish the successor ZSK. +setup step2.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +# According to RFC 7583: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# Also: Ipub = Dprp + TTLkey (+publish-safety) +# so: Tact(N) = Tpub(N+1) + Ipub - Lzsk = now + (1d2h) - 30d = +# now + 26h - 30d = now − 694h +TactN="now-694h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# After the publication interval has passed the DNSKEY of the successor ZSK +# is OMNIPRESENT and the zone can thus be signed with the successor ZSK. +setup step3.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK1=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +ZSK2=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# Also: Tret(N) = Tact(N+1) = Tact(N) + Lzsk +# so: Tact(N) = Tact(N+1) - Lzsk = now - 30d +# and: Tpub(N+1) = Tact(N+1) - Ipub = now - 26h +# and: Tret(N+1) = Tact(N+1) + Lzsk +TactN="now-30d" +TpubN1="now-26h" +TretN1="now+30d" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -z $O $TactN $ZSK1 > settime.out.$zone.2 2>&1 +$SETTIME -s -S $ZSK1 -i 0 $ZSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A now -I $TretN1 -g $O -k $R $TpubN1 -z $H $TpubN1 $ZSK2 > settime.out.$zone.4 2>&1 +cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK1 >> "$infile" +private_type_record $zone 13 $ZSK2 >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# After the retire interval has passed the predecessor DNSKEY can be +# removed from the zone. +setup step4.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK1=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +ZSK2=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tret(N) = Tact(N) + Lzsk +# Also: Tdea(N) = Tret(N) + Iret +# Also: Iret = Dsgn + Dprp + TTLsig (+retire-safety) +# so: Tact(N) = Tdea(N) - Iret - Lzsk = now - (1w1h1d2d) - 30d = +# now - (10d1h) - 30d = now - 961h +# and: Tret(N) = Tdea(N) - Iret = now - (10d1h) = now - 241h +# and: Tpub(N+1) = Tdea(N) - Iret - Ipub = now - (10d1h) - 26h = +# now - 267h +# and: Tact(N+1) = Tdea(N) - Iret = Tret(N) +# and: Tret(N+1) = Tdea(N) - Iret + Lzsk = now - (10d1h) + 30d = +# now + 479h +TactN="now-961h" +TretN="now-241h" +TpubN1="now-267h" +TactN1="${TretN}" +TretN1="now+479h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -z $U $TretN $ZSK1 > settime.out.$zone.2 2>&1 +$SETTIME -s -S $ZSK1 -i 0 $ZSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TactN1 -z $R $TactN1 $ZSK2 > settime.out.$zone.4 2>&1 +cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" > "$infile" +$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# The predecessor DNSKEY is removed long enough that is has become HIDDEN. +setup step5.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK1=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +ZSK2=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.3` +# Substract DNSKEY TTL from all the times (1h). +TactN="now-962h" +TretN="now-242h" +TpubN1="now-268h" +TactN1="${TretN}" +TretN1="now+478h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I $TretN -D now -g $H -k $U $TretN -z $U $TretN $ZSK1 > settime.out.$zone.2 2>&1 +$SETTIME -s -S $ZSK1 -i 0 $ZSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TactN1 -z $R $TactN1 $ZSK2 > settime.out.$zone.4 2>&1 +cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK1 >> "$infile" +private_type_record $zone 13 $ZSK2 >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at ksk-doubleksk.autosign represent the various steps of a KSK +# Double-KSK rollover. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.ksk-doubleksk.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.2` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to submit the introduce the new KSK. +setup step2.ksk-doubleksk.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.2` +# According to RFC 7583: Tpub(N+1) <= Tact(N) + Lksk - Dreg - IpubC +# Also: IpubC = DprpC + TTLkey (+publish-safety) +# so: Tact(N) = Tpub(N+1) - Lksk + Dreg + IpubC = now - 60d + (1d3h) +# now - 1440h + 27h = now - 1413h +TactN="now-1413h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# It is time to submit the DS. +setup step3.ksk-doubleksk.autosign +KSK1=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +KSK2=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.2` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tsbm(N+1) >= Trdy(N+1) +# Also: Tact(N+1) = Tsbm(N+1) + Dreg +# so: Tact(N) = Tsbm(N+1) + Dreg - Lksk = now + 1d - 60d = now - 59d +# and: Tret(N) = Tsbm(N+1) + Dreg = now + 1d +# and: Tpub(N+1) <= Tsbm(N+1) - IpubC = now + 27h +# and: Tret(N+1) = Tsbm(N+1) + Dreg + Lksk = 1d + 60d +TactN="now-59d" +TretN="now+1d" +TpubN1="now-27h" +TretN1="now+61d" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $O $TactN $KSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $KSK1 -i 0 $KSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TretN -I $TretN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 $KSK2 > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK1 >> "$infile" +private_type_record $zone 13 $KSK2 >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# The DS should be swapped now. +setup step4.ksk-doubleksk.autosign +KSK1=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +KSK2=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.2` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Tret(N) = Tsbm(N+1) + Dreg +# Also: Tact(N+1) = Tret(N) +# Also: Iret = DprpP + TTLds (+retire-safety) +# so: Tact(N) = Tdea(N) - Lksk - Iret = now - 60d - 2d2h = now - 1490h +# and: Tret(N) = Tdea(N) - Iret = now - 2d2h = 50h +# and: Tpub(N+1) = Tdea(N) - Iret - Dreg - IpubC = now - 50h - 1d - 1d3h = now - 101h +# and: Tsbm(N+1) = Tdea(N) - Iret - Dreg = now - 50h - 1d = now - 74h +# and: Tact(N+1) = Tret(N) +# and: Tret(N+1) = Tdea(N) + Lksk - Iret = now + 60d - 2d2h = now + 1390h +TactN="now-1490h" +TretN="now-50h" +TpubN1="now-101h" +TsbmN1="now-74h" +TactN1="${TretN}" +TretN1="now+1390h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TsbmN1 $KSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $KSK1 -i 0 $KSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $R $TsbmN1 $KSK2 > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK1 >> "$infile" +private_type_record $zone 13 $KSK2 >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# The predecessor DNSKEY is removed long enough that is has become HIDDEN. +setup step5.ksk-doubleksk.autosign +KSK1=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +KSK2=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.2` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.3` +# Substract DNSKEY TTL from all the times (2h). +TactN="now-1492h" +TretN="now-52h" +TpubN1="now-102h" +TsbmN1="now-75h" +TactN1="${TretN}" +TretN1="now+1388h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $U $TretN -r $U $TretN -d $H $TretN $KSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $KSK1 -i 0 $KSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TactN1 -r $O $TactN1 -d $O $TactN1 $KSK2 > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK1 >> "$infile" +private_type_record $zone 13 $KSK2 >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at csk-roll.autosign represent the various steps of a CSK rollover +# (which is essentially a ZSK Pre-Publication / KSK Double-KSK rollover). +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.csk-roll.autosign +CSK=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to introduce the new CSK. +setup step2.csk-roll.autosign +CSK=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: ZSK: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# According to RFC 7583: KSK: Tpub(N+1) <= Tact(N) + Lksk - Dreg - IpubC +# Also: Ipub = Dprp + TTLkey (+publish-safety) +# Also: IpubC = DprpC + TTLkey (+publish-safety) +# Both sums are almost the same, but the KSK case has Dreg in the equation. +# so: Tact(N) = Tpub(N+1) - Lcsk + Dreg + IpubC = now - 6mo + 1d + 3h = +# now - 4464h + 24h + 3h = now - 4437h +TactN="now-4437h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# It is time to submit the DS and to roll signatures. +setup step3.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tsbm(N+1) >= Trdy(N+1) +# Also: Tact(N+1) = Tsbm(N+1) + Dreg +# so: Tact(N) = Tsbm(N+1) + Dreg - Lksk = now + 1d - 6mo = now - 185d +# and: Tret(N) = Tsbm(N+1) + Dreg = now + 1d +# and: Tpub(N+1) <= Tsbm(N+1) - IpubC = now - 3h +# and: Tret(N+1) = Tsbm(N+1) + Dreg + Lksk = now + 1d + 6mo = now + 187d +TactN="now-185d" +TretN="now+1d" +TpubN1="now-3h" +TretN1="now+187d" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TretN -I $TretN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 -z $H $TpubN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# Some time later all the ZRRSIG records should be from the new CSK, and the +# DS should be swapped. The ZRRSIG records are all replaced after Iret +# which is Dsgn + Dprp + TTLsig + retire-safety (25d + 1h + 1d + 2h = 26d3h). +# The DS is swapped after Dreg + DprpP + TTLds + retire-safety +# (1d + 1h + 1h + 2h = 1d4h). In other words, the DS is swapped before all +# zone signatures are replaced. +setup step4.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Iret = 1h + 1h + 2h = 4h +# Also: Tact(N+1) = Tret(N) +# so: Tact(N) = Tdea(N) - Lksk - Iret = now - 6mo - 4h = now - 4468h +# and: Tret(N) = Tdea(N) - Iret = now - 4h = now - 4h +# and: Tpub(N+1) = Tdea(N) - Iret - Dreg - IpubC = now - 4h - 1d - 3h = now - 31h +# and: Tsbm(N+1) = Tdea(N) - Iret - Dreg = now - 4h - 1d = now - 28h +# and: Tact(N+1) = Tret(N) +# and: Tret(N+1) = Tdea(N) + Lksk - Iret = now + 6mo - 4h = now + 4460h +TactN="now-4468h" +TretN="now-4h" +TpubN1="now-31h" +TsbmN1="now-28h" +TactN1="${TretN}" +TretN1="now+4460h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TsbmN1 -z $U $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $R $TsbmN1 -z $R $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# After the DS is swapped in step 4, also the KRRSIG records can be removed. +# At this time these have all become hidden. +setup step5.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract DNSKEY TTL plus zone propagation delay from all the times (2h). +TactN="now-4470h" +TretN="now-6h" +TdeaN="now-2h" +TpubN1="now-33h" +TsbmN1="now-30h" +TactN1="${TretN}" +TretN1="now+4458h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $U $TdeaN -d $H $TdeaN -z $U $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $O $TdeaN -z $R $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 6: +# After the retire interval has passed the predecessor DNSKEY can be +# removed from the zone. +setup step6.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Tret(N) = Tact(N) + Lzsk +# Also: Iret = Dsgn + Dprp + TTLsig (+retire-safety) +# so: Tact(N) = Tdea(N) - Iret - Lzsk = now - 25d1h1d2h - 6mo = +# now - 26d3h - 6mo = now - 627h - 4464h = now - 5091h +# and: Tret(N) = Tdea(N) - Iret = now - 627h +# and: Tpub(N+1) = Tdea(N) - Iret - Ipub = now - 627h - 3h = now - 630h +# and: Tact(N+1) = Tdea(N) - Iret = Tret(N) +# and: Tret(N+1) = Tdea(N) - Iret + Lzsk = now - 627h + 6mo = now + 3837h +TactN="now-5091h" +TretN="now-627h" +TdeaN="now-623h" +TpubN1="now-630h" +TsbmN1="now-627h" +TactN1="${TretN}" +TretN1="now+3837h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $H $TdeaN -d $H $TdeaN -z $U $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $O $TdeaN -z $R $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 7: +# Some time later the predecessor DNSKEY enters the HIDDEN state. +setup step7.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract DNSKEY TTL plus zone propagation delay from all the times (2h). +TactN="now-5093h" +TretN="now-629h" +TdeaN="now-625h" +TpubN1="now-632h" +TsbmN1="now-629h" +TactN1="${TretN}" +TretN1="now+3835h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $U now-2h -r $H $TdeaN -d $H $TdeaN -z $H $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $O $TdeaN -z $O $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at csk-roll2.autosign represent the various steps of a CSK rollover +# (which is essentially a ZSK Pre-Publication / KSK Double-KSK rollover). +# This scenario differs from the above one because the zone signatures (ZRRSIG) +# are replaced with the new key sooner than the DS is swapped. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.csk-roll2.autosign +CSK=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to introduce the new CSK. +setup step2.csk-roll2.autosign +CSK=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: ZSK: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# According to RFC 7583: KSK: Tpub(N+1) <= Tact(N) + Lksk - Dreg - IpubC +# Also: Ipub = Dprp + TTLkey (+publish-safety) +# Also: IpubC = DprpC + TTLkey (+publish-safety) +# Both sums are almost the same, but the KSK case has Dreg in the equation. +# so: Tact(N) = Tpub(N+1) - Lcsk + Dreg + IpubC = now - 6mo + 1w + 3h = +# now - 4464h + 168h + 3h = now - 4635h +TactN="now-4635h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# It is time to submit the DS and to roll signatures. +setup step3.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tsbm(N+1) >= Trdy(N+1) +# Also: Tact(N+1) = Tsbm(N+1) + Dreg +# so: Tact(N) = Tsbm(N+1) + Dreg - Lksk = now + 1w - 6mo = now - 179d +# and: Tret(N) = Tsbm(N+1) + Dreg = now + 1w +# and: Tpub(N+1) <= Tsbm(N+1) - IpubC = now - 3h +# and: Tret(N+1) = Tsbm(N+1) + Dreg + Lksk = now + 1w + 6mo = now + 193d +TactN="now-179d" +TretN="now+1w" +TpubN1="now-3h" +TretN1="now+193d" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TretN -I $TretN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 -z $H $TpubN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# Some time later all the ZRRSIG records should be from the new CSK, and the +# DS should be swapped. The ZRRSIG records are all replaced after Iret +# which is Dsgn + Dprp + TTLsig + retire-safety (12h + 1h + 1d + 2h = 38h). +# The DS is swapped after Dreg + DprpP + TTLds + retire-safety +# (1w + 1h + 1h + 1h = 1w3h). In other words, the zone signatures are +# replaced before the DS is swapped. +setup step4.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Tret(N) = Tact(N) + Lzsk +# Also: Iret = Dsgn + Dprp + TTLsig (+retire-safety) +# so: Tact(N) = Tdea(N) - Iret - Lzsk = now - 38h - 6mo = now - 4502h +# and: Tret(N) = Tdea(N) - Iret = now - 38h +# and: Tpub(N+1) = Tdea(N) - Iret - Ipub = now - 41h +# and: Tact(N+1) = Tdea(N) - Iret = Tret(N) +# and: Tret(N+1) = Tdea(N) - Iret + Lzsk = now - 38h + 6mo = now + 4426h +TactN="now-4502h" +TretN="now-38h" +TpubN1="now-41h" +TactN1="${TretN}" +TretN1="now+4426" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TretN -z $U $TretN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TretN -r $O $TretN -d $R $TretN -z $R $TretN $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# Some time later the DS can be swapped and the old DNSKEY can be removed from +# the zone. +setup step5.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract Dreg + Iret (174h). +TactN="now-4676h" +TretN="now-212h" +TpubN1="now-215h" +TactN1="${TretN}" +TretN1="now+4252h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TretN -z $H $TretN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TretN -r $O $TretN -d $R $TretN -z $O $TretN $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 6: +# Some time later the predecessor DNSKEY enters the HIDDEN state. +setup step6.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract DNSKEY TTL plus zone propagation delay (2h). +TactN="now-4678h" +TretN="now-214h" +TdeaN="now-2h" +TpubN1="now-217h" +TactN1="${TretN}" +TretN1="now+4250h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $U $TdeaN -r $U $TdeaN -d $H $TretN -z $H $TretN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TretN -r $O $TretN -d $O $TretN -z $O $TretN $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/kasp/ns3/template.db.in b/bin/tests/system/kasp/ns3/template.db.in new file mode 100644 index 0000000000..051a312891 --- /dev/null +++ b/bin/tests/system/kasp/ns3/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns3/template2.db.in b/bin/tests/system/kasp/ns3/template2.db.in new file mode 100644 index 0000000000..3fe69f34c3 --- /dev/null +++ b/bin/tests/system/kasp/ns3/template2.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 2 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.11 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 diff --git a/bin/tests/system/kasp/ns4/named.conf.in b/bin/tests/system/kasp/ns4/named.conf.in new file mode 100644 index 0000000000..c8d4094f85 --- /dev/null +++ b/bin/tests/system/kasp/ns4/named.conf.in @@ -0,0 +1,117 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS4 + +key "sha1" { + algorithm "hmac-sha1"; + secret "FrSt77yPTFx6hTs4i2tKLB9LmE0="; +}; + +key "sha224" { + algorithm "hmac-sha224"; + secret "hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="; +}; + +key "sha256" { + algorithm "hmac-sha256"; + secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="; +}; + +dnssec-policy "test" { + keys { + csk key-directory lifetime 0 algorithm 14; + }; +}; + +options { + query-source address 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion no; + dnssec-policy "test"; +}; + +view "inherit" { + match-clients { key "sha1"; }; + + /* Inherit dnssec-policy 'test' */ + zone "inherit.inherit.signed" { + type master; + file "inherit.inherit.signed.db"; + }; + + /* Override dnssec-policy */ + zone "override.inherit.signed" { + type master; + dnssec-policy "default"; + file "override.inherit.signed.db"; + }; + + /* Unset dnssec-policy */ + zone "none.inherit.signed" { + type master; + dnssec-policy "none"; + file "none.inherit.signed.db"; + }; +}; + +view "override" { + match-clients { key "sha224"; }; + dnssec-policy "default"; + + /* Inherit dnssec-policy 'test' */ + zone "inherit.override.signed" { + type master; + file "inherit.override.signed.db"; + }; + + /* Override dnssec-policy */ + zone "override.override.signed" { + type master; + dnssec-policy "test"; + file "override.override.signed.db"; + }; + + /* Unset dnssec-policy */ + zone "none.override.signed" { + type master; + dnssec-policy "none"; + file "none.override.signed.db"; + }; +}; + +view "none" { + match-clients { key "sha256"; }; + dnssec-policy "none"; + + /* Inherit dnssec-policy 'none' */ + zone "inherit.none.signed" { + type master; + file "inherit.none.signed.db"; + }; + + /* Override dnssec-policy */ + zone "override.none.signed" { + type master; + dnssec-policy "test"; + file "override.none.signed.db"; + }; + + /* Unset dnssec-policy */ + zone "none.none.signed" { + type master; + dnssec-policy "none"; + file "none.none.signed.db"; + }; +}; diff --git a/bin/tests/system/kasp/ns4/setup.sh b/bin/tests/system/kasp/ns4/setup.sh new file mode 100644 index 0000000000..ca830dd028 --- /dev/null +++ b/bin/tests/system/kasp/ns4/setup.sh @@ -0,0 +1,28 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns4/setup.sh" + +# +# Set up zones that potentially will be initially signed. +# +for zn in inherit.inherit override.inherit none.inherit \ + inherit.override override.override none.override \ + inherit.none override.none none.none +do + zone="$zn.signed" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + cp template.db.in $zonefile +done diff --git a/bin/tests/system/kasp/ns4/template.db.in b/bin/tests/system/kasp/ns4/template.db.in new file mode 100644 index 0000000000..59946e07ba --- /dev/null +++ b/bin/tests/system/kasp/ns4/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns4 +ns4 A 10.53.0.4 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns5/named.conf.in b/bin/tests/system/kasp/ns5/named.conf.in new file mode 100644 index 0000000000..2c9c8f6214 --- /dev/null +++ b/bin/tests/system/kasp/ns5/named.conf.in @@ -0,0 +1,117 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS5 + +key "sha1" { + algorithm "hmac-sha1"; + secret "FrSt77yPTFx6hTs4i2tKLB9LmE0="; +}; + +key "sha224" { + algorithm "hmac-sha224"; + secret "hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="; +}; + +key "sha256" { + algorithm "hmac-sha256"; + secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="; +}; + +dnssec-policy "test" { + keys { + csk key-directory lifetime 0 algorithm 14; + }; +}; + +options { + query-source address 10.53.0.5; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + recursion no; + dnssec-policy "none"; +}; + +view "inherit" { + match-clients { key "sha1"; }; + + /* Inherit dnssec-policy 'none' */ + zone "inherit.inherit.unsigned" { + type master; + file "inherit.inherit.unsigned.db"; + }; + + /* Override dnssec-policy */ + zone "override.inherit.unsigned" { + type master; + dnssec-policy "default"; + file "override.inherit.unsigned.db"; + }; + + /* Unset dnssec-policy */ + zone "none.inherit.unsigned" { + type master; + dnssec-policy "none"; + file "none.inherit.unsigned.db"; + }; +}; + +view "override" { + match-clients { key "sha224"; }; + dnssec-policy "default"; + + /* Inherit dnssec-policy 'default' */ + zone "inherit.override.unsigned" { + type master; + file "inherit.override.unsigned.db"; + }; + + /* Override dnssec-policy */ + zone "override.override.unsigned" { + type master; + dnssec-policy "test"; + file "override.override.unsigned.db"; + }; + + /* Unset dnssec-policy */ + zone "none.override.unsigned" { + type master; + dnssec-policy "none"; + file "none.override.unsigned.db"; + }; +}; + +view "none" { + match-clients { key "sha256"; }; + dnssec-policy "none"; + + /* Inherit dnssec-policy 'none' */ + zone "inherit.none.unsigned" { + type master; + file "inherit.none.unsigned.db"; + }; + + /* Override dnssec-policy */ + zone "override.none.unsigned" { + type master; + dnssec-policy "test"; + file "override.none.unsigned.db"; + }; + + /* Unset dnssec-policy */ + zone "none.none.unsigned" { + type master; + dnssec-policy "none"; + file "none.none.unsigned.db"; + }; +}; diff --git a/bin/tests/system/kasp/ns5/setup.sh b/bin/tests/system/kasp/ns5/setup.sh new file mode 100644 index 0000000000..b6f274e6a7 --- /dev/null +++ b/bin/tests/system/kasp/ns5/setup.sh @@ -0,0 +1,28 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns5/setup.sh" + +# +# Set up zones that potentially will be initially signed. +# +for zn in inherit.inherit override.inherit none.inherit \ + inherit.override override.override none.override \ + inherit.none override.none none.none +do + zone="$zn.unsigned" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + cp template.db.in $zonefile +done diff --git a/bin/tests/system/kasp/ns5/template.db.in b/bin/tests/system/kasp/ns5/template.db.in new file mode 100644 index 0000000000..2f73182e72 --- /dev/null +++ b/bin/tests/system/kasp/ns5/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns5 +ns5 A 10.53.0.5 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/setup.sh b/bin/tests/system/kasp/setup.sh new file mode 100644 index 0000000000..0d93046ae1 --- /dev/null +++ b/bin/tests/system/kasp/setup.sh @@ -0,0 +1,42 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +set -e + +$SHELL clean.sh + +mkdir keys + +copy_setports ns2/named.conf.in ns2/named.conf +copy_setports ns3/named.conf.in ns3/named.conf +copy_setports ns4/named.conf.in ns4/named.conf +copy_setports ns5/named.conf.in ns5/named.conf + +# Setup zones +( + cd ns2 + $SHELL setup.sh +) +( + cd ns3 + $SHELL setup.sh +) +( + cd ns4 + $SHELL setup.sh +) +( + cd ns5 + $SHELL setup.sh +) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh new file mode 100644 index 0000000000..5fa2178e99 --- /dev/null +++ b/bin/tests/system/kasp/tests.sh @@ -0,0 +1,2080 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +SYSTEMTESTTOP=.. +. "$SYSTEMTESTTOP/conf.sh" + +status=0 +n=0 + +############################################################################### +# Constants # +############################################################################### +DEFAULT_TTL=300 + +############################################################################### +# Query properties # +############################################################################### +TSIG="" +SHA1="FrSt77yPTFx6hTs4i2tKLB9LmE0=" +SHA224="hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA==" +SHA256="R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=" + +############################################################################### +# Key properties # +############################################################################### +ID=0 +EXPECT=1 +ROLE=2 +KSK=3 +ZSK=4 +LIFETIME=5 +ALG_NUM=6 +ALG_STR=7 +ALG_LEN=8 +PUBLISHED=9 +ACTIVE=10 +RETIRED=11 +REVOKED=12 +REMOVED=13 +GOAL=14 +STATE_DNSKEY=15 +STATE_ZRRSIG=16 +STATE_KRRSIG=17 +STATE_DS=18 +EXPECT_ZRRSIG=19 +EXPECT_KRRSIG=20 + +# Clear key state. +# +# This will update either the KEY1, KEY2, or KEY3 array. +key_clear() { + _key=( [$ID]="no" [$EXPECT]="no" \ + [$ROLE]="none" [$KSK]="no" [$ZSK]="no" \ + [$LIFETIME]="0" [$ALG_NUM]="0" \ + [$ALG_STR]="none" [$ALG_LEN]="0" \ + [$PUBLISHED]="none" [$ACTIVE]="none" \ + [$RETIRED]="none" [$REVOKED]="none" \ + [$REMOVED]="none" \ + [$GOAL]="none" [$STATE_DNSKEY]="none" \ + [$STATE_KRRSIG]="none" [$STATE_ZRRSIG]="none" \ + [$STATE_DS]="none" \ + [$EXPECT_ZRRSIG]="no" [$EXPECT_KRRSIG]="no") + + if [ $1 == "KEY1" ]; then + KEY1=(${_key[*]}) + elif [ $1 == "KEY2" ]; then + KEY2=(${_key[*]}) + elif [ $1 == "KEY3" ]; then + KEY3=(${_key[*]}) + fi +} + +# Start clear. +key_clear "KEY1" +key_clear "KEY2" +key_clear "KEY3" + +############################################################################### +# Utilities # +############################################################################### + +# Call dig with default options. +dig_with_opts() { + _tsig="" + if [ -n "$TSIG" ]; then + _tsig="-y $TSIG" + fi + + "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p $PORT $_tsig "$@" +} + +# RNDC. +rndccmd() { + "$RNDC" -c "$SYSTEMTESTTOP/common/rndc.conf" -p "$CONTROLPORT" -s "$@" +} + +# Print IDs of keys used for generating RRSIG records for RRsets of type $1 +# found in dig output file $2. +get_keys_which_signed() { + _qtype=$1 + _output=$2 + # The key ID is the 11th column of the RRSIG record line. + awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' < "$_output" +} + +# Get the key ids from key files for zone $2 in directory $1 +# that matches algorithm $3. +get_keyids() { + _dir=$1 + _zone=$2 + _algorithm=$(printf "%03d" $3) + _start="${_dir}/K${_zone}.+${_algorithm}+" + _end=".key" + + if [ $_algorithm -ne 0 ]; then + ls ${_start}*${_end} | sed "s/$_dir\/K${_zone}.+${_algorithm}+\([0-9]\{5\}\)${_end}/\1/" + fi +} + +# By default log errors and don't quit immediately. +_log=1 +log_error() { + test $_log -eq 1 && echo_i "error: $1" + ret=$((ret+1)) +} + +# Set zone properties for testing keys. +# $1: Key directory +# $2: Zone name +# $3: Policy name +# $4: DNSKEY TTL +# $5: Number of keys +# $6: Name server +# +# This will set the following environment variables for testing: +# DIR, ZONE, POLICY, DNSKEY_TTL, NUM_KEYS, SERVER +zone_properties() { + DIR=$1 + ZONE=$2 + POLICY=$3 + DNSKEY_TTL=$4 + NUM_KEYS=$5 + SERVER=$6 +} + +# Set key properties for testing keys. +# $1: Key to update +# $2: Role +# $3: Lifetime +# $4: Algorithm (number) +# $5: Algorithm (string-format) +# $6: Algorithm length +# $7: Is zone signing +# $8: Is key signing +# +# This will update either the KEY1, KEY2 or KEY3 array. +key_properties() { + if [ $1 == "KEY1" ]; then + KEY1[$EXPECT]="yes" + KEY1[$ROLE]=$2 + KEY1[$KSK]="no" + KEY1[$ZSK]="no" + test $2 == "ksk" && KEY1[$KSK]="yes" + test $2 == "zsk" && KEY1[$ZSK]="yes" + test $2 == "csk" && KEY1[$KSK]="yes" + test $2 == "csk" && KEY1[$ZSK]="yes" + KEY1[$LIFETIME]=$3 + KEY1[$ALG_NUM]=$4 + KEY1[$ALG_STR]=$5 + KEY1[$ALG_LEN]=$6 + KEY1[$EXPECT_ZRRSIG]=$7 + KEY1[$EXPECT_KRRSIG]=$8 + elif [ $1 == "KEY2" ]; then + KEY2[$EXPECT]="yes" + KEY2[$ROLE]=$2 + KEY2[$KSK]="no" + KEY2[$ZSK]="no" + test $2 == "ksk" && KEY2[$KSK]="yes" + test $2 == "zsk" && KEY2[$ZSK]="yes" + test $2 == "csk" && KEY2[$KSK]="yes" + test $2 == "csk" && KEY2[$ZSK]="yes" + KEY2[$LIFETIME]=$3 + KEY2[$ALG_NUM]=$4 + KEY2[$ALG_STR]=$5 + KEY2[$ALG_LEN]=$6 + KEY2[$EXPECT_ZRRSIG]=$7 + KEY2[$EXPECT_KRRSIG]=$8 + elif [ $1 == "KEY3" ]; then + KEY3[$EXPECT]="yes" + KEY3[$ROLE]=$2 + KEY3[$KSK]="no" + KEY3[$ZSK]="no" + test $2 == "ksk" && KEY3[$KSK]="yes" + test $2 == "zsk" && KEY3[$ZSK]="yes" + test $2 == "csk" && KEY3[$KSK]="yes" + test $2 == "csk" && KEY3[$ZSK]="yes" + KEY3[$LIFETIME]=$3 + KEY3[$ALG_NUM]=$4 + KEY3[$ALG_STR]=$5 + KEY3[$ALG_LEN]=$6 + KEY3[$EXPECT_ZRRSIG]=$7 + KEY3[$EXPECT_KRRSIG]=$8 + fi +} + +# Set key timing metadata. Set to "none" to unset. +# These times are hard to test, so it is just an indication that we expect the +# respective timing metadata in the key files. +# $1: Key to update +# $2: Published +# $3: Active +# $4: Retired +# $5: Revoked +# $6: Removed +# +# This will update either the KEY1, KEY2 or KEY3 array. +key_timings() { + if [ $1 == "KEY1" ]; then + KEY1[$EXPECT]="yes" + KEY1[$PUBLISHED]=$2 + KEY1[$ACTIVE]=$3 + KEY1[$RETIRED]=$4 + KEY1[$REVOKED]=$5 + KEY1[$REMOVED]=$6 + elif [ $1 == "KEY2" ]; then + KEY2[$EXPECT]="yes" + KEY2[$PUBLISHED]=$2 + KEY2[$ACTIVE]=$3 + KEY2[$RETIRED]=$4 + KEY2[$REVOKED]=$5 + KEY2[$REMOVED]=$6 + elif [ $1 == "KEY3" ]; then + KEY3[$EXPECT]="yes" + KEY3[$PUBLISHED]=$2 + KEY3[$ACTIVE]=$3 + KEY3[$RETIRED]=$4 + KEY3[$REVOKED]=$5 + KEY3[$REMOVED]=$6 + fi +} + +# Set key state metadata. Set to "none" to unset. +# $1: Key to update +# $2: Goal state +# $3: DNSKEY state +# $4: RRSIG state (zsk) +# $5: RRSIG state (ksk) +# $6: DS state +# +# This will update either the KEY1, KEY2, OR KEY3 array. +key_states() { + if [ $1 == "KEY1" ]; then + KEY1[$EXPECT]="yes" + KEY1[$GOAL]=$2 + KEY1[$STATE_DNSKEY]=$3 + KEY1[$STATE_ZRRSIG]=$4 + KEY1[$STATE_KRRSIG]=$5 + KEY1[$STATE_DS]=$6 + elif [ $1 == "KEY2" ]; then + KEY2[$EXPECT]="yes" + KEY2[$GOAL]=$2 + KEY2[$STATE_DNSKEY]=$3 + KEY2[$STATE_ZRRSIG]=$4 + KEY2[$STATE_KRRSIG]=$5 + KEY2[$STATE_DS]=$6 + elif [ $1 == "KEY3" ]; then + KEY3[$EXPECT]="yes" + KEY3[$GOAL]=$2 + KEY3[$STATE_DNSKEY]=$3 + KEY3[$STATE_ZRRSIG]=$4 + KEY3[$STATE_KRRSIG]=$5 + KEY3[$STATE_DS]=$6 + fi +} + +# Check the key $1 with id $2. +# This requires environment variables to be set with 'zone_properties', +# 'key_properties', 'key_timings', and 'key_states'. +# +# This will set the following environment variables for testing: +# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" +# KEY_FILE="${BASE_FILE}.key" +# PRIVATE_FILE="${BASE_FILE}.private" +# STATE_FILE="${BASE_FILE}.state" +# KEY_ID=$(echo $1 | sed 's/^0*//') +check_key() { + if [ $1 == "KEY1" ]; then + _key=(${KEY1[*]}) + elif [ $1 == "KEY2" ]; then + _key=(${KEY2[*]}) + elif [ $1 == "KEY3" ]; then + _key=(${KEY3[*]}) + fi + + _dir=$DIR + _zone=$ZONE + _role="${_key[$ROLE]}" + _key_idpad=$2 + _key_id=$(echo $_key_idpad | sed 's/^0*//') + _alg_num="${_key[$ALG_NUM]}" + _alg_numpad=$(printf "%03d" $_alg_num) + _alg_string="${_key[$ALG_STR]}" + _length="${_key[$ALG_LEN]}" + _dnskey_ttl=$DNSKEY_TTL + _lifetime="${_key[$LIFETIME]}" + + _published="${_key[$PUBLISHED]}" + _active="${_key[$ACTIVE]}" + _retired="${_key[$RETIRED]}" + _revoked="${_key[$REVOKED]}" + _removed="${_key[$REMOVED]}" + + _goal="${_key[$GOAL]}" + _state_dnskey="${_key[$STATE_DNSKEY]}" + _state_zrrsig="${_key[$STATE_ZRRSIG]}" + _state_krrsig="${_key[$STATE_KRRSIG]}" + _state_ds="${_key[$STATE_DS]}" + + _ksk="no" + _zsk="no" + if [ "$_role" == "ksk" ]; then + _role2="key-signing" + _ksk="yes" + _flags="257" + elif [ "$_role" == "zsk" ]; then + _role2="zone-signing" + _zsk="yes" + _flags="256" + elif [ "$_role" == "csk" ]; then + _role2="key-signing" + _zsk="yes" + _ksk="yes" + _flags="257" + fi + + BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" + KEY_FILE="${BASE_FILE}.key" + PRIVATE_FILE="${BASE_FILE}.private" + STATE_FILE="${BASE_FILE}.state" + KEY_ID="${_key_id}" + + test $_log -eq 1 && echo_i "check key $BASE_FILE" + + # Check the public key file. + grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." $KEY_FILE > /dev/null || log_error "mismatch top comment in $KEY_FILE" + grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" $KEY_FILE > /dev/null || log_error "mismatch DNSKEY record in $KEY_FILE" + # Now check the private key file. + grep "Private-key-format: v1.3" $PRIVATE_FILE > /dev/null || log_error "mismatch private key format in $PRIVATE_FILE" + grep "Algorithm: ${_alg_num} (${_alg_string})" $PRIVATE_FILE > /dev/null || log_error "mismatch algorithm in $PRIVATE_FILE" + # Now check the key state file. + grep "This is the state of key ${_key_id}, for ${_zone}." $STATE_FILE > /dev/null || log_error "mismatch top comment in $STATE_FILE" + grep "Lifetime: ${_lifetime}" $STATE_FILE > /dev/null || log_error "mismatch lifetime in $STATE_FILE" + grep "Algorithm: ${_alg_num}" $STATE_FILE > /dev/null || log_error "mismatch algorithm in $STATE_FILE" + grep "Length: ${_length}" $STATE_FILE > /dev/null || log_error "mismatch length in $STATE_FILE" + grep "KSK: ${_ksk}" $STATE_FILE > /dev/null || log_error "mismatch ksk in $STATE_FILE" + grep "ZSK: ${_zsk}" $STATE_FILE > /dev/null || log_error "mismatch zsk in $STATE_FILE" + + # Check key states. + if [ "$_goal" == "none" ]; then + grep "GoalState: " $STATE_FILE > /dev/null && log_error "unexpected goal state in $STATE_FILE" + else + grep "GoalState: ${_goal}" $STATE_FILE > /dev/null || log_error "mismatch goal state in $STATE_FILE" + fi + + if [ "$_state_dnskey" == "none" ]; then + grep "DNSKEYState: " $STATE_FILE > /dev/null && log_error "unexpected dnskey state in $STATE_FILE" + grep "DNSKEYChange: " $STATE_FILE > /dev/null && log_error "unexpected dnskey change in $STATE_FILE" + else + grep "DNSKEYState: ${_state_dnskey}" $STATE_FILE > /dev/null || log_error "mismatch dnskey state in $STATE_FILE" + grep "DNSKEYChange: " $STATE_FILE > /dev/null || log_error "mismatch dnskey change in $STATE_FILE" + fi + + if [ "$_state_zrrsig" == "none" ]; then + grep "ZRRSIGState: " $STATE_FILE > /dev/null && log_error "unexpected zrrsig state in $STATE_FILE" + grep "ZRRSIGChange: " $STATE_FILE > /dev/null && log_error "unexpected zrrsig change in $STATE_FILE" + else + grep "ZRRSIGState: ${_state_zrrsig}" $STATE_FILE > /dev/null || log_error "mismatch zrrsig state in $STATE_FILE" + grep "ZRRSIGChange: " $STATE_FILE > /dev/null || log_error "mismatch zrrsig change in $STATE_FILE" + fi + + if [ "$_state_krrsig" == "none" ]; then + grep "KRRSIGState: " $STATE_FILE > /dev/null && log_error "unexpected krrsig state in $STATE_FILE" + grep "KRRSIGChange: " $STATE_FILE > /dev/null && log_error "unexpected krrsig change in $STATE_FILE" + else + grep "KRRSIGState: ${_state_krrsig}" $STATE_FILE > /dev/null || log_error "mismatch krrsig state in $STATE_FILE" + grep "KRRSIGChange: " $STATE_FILE > /dev/null || log_error "mismatch krrsig change in $STATE_FILE" + fi + + if [ "$_state_ds" == "none" ]; then + grep "DSState: " $STATE_FILE > /dev/null && log_error "unexpected ds state in $STATE_FILE" + grep "DSChange: " $STATE_FILE > /dev/null && log_error "unexpected ds change in $STATE_FILE" + else + grep "DSState: ${_state_ds}" $STATE_FILE > /dev/null || log_error "mismatch ds state in $STATE_FILE" + grep "DSChange: " $STATE_FILE > /dev/null || log_error "mismatch ds change in $STATE_FILE" + fi + + # Check timing metadata. + if [ "$_published" == "none" ]; then + grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected publish comment in $KEY_FILE" + grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected publish in $PRIVATE_FILE" + grep "Published: " $STATE_FILE > /dev/null && log_error "unexpected publish in $STATE_FILE" + else + grep "; Publish:" $KEY_FILE > /dev/null || log_error "mismatch publish comment in $KEY_FILE ($KEY_PUBLISHED)" + grep "Publish:" $PRIVATE_FILE > /dev/null || log_error "mismatch publish in $PRIVATE_FILE ($KEY_PUBLISHED)" + grep "Published:" $STATE_FILE > /dev/null || log_error "mismatch publish in $STATE_FILE ($KEY_PUBLISHED)" + fi + + if [ "$_active" == "none" ]; then + grep "; Activate:" $KEY_FILE > /dev/null && log_error "unexpected active comment in $KEY_FILE" + grep "Activate:" $PRIVATE_FILE > /dev/null && log_error "unexpected active in $PRIVATE_FILE" + grep "Active: " $STATE_FILE > /dev/null && log_error "unexpected active in $STATE_FILE" + else + grep "; Activate:" $KEY_FILE > /dev/null || log_error "mismatch active comment in $KEY_FILE" + grep "Activate:" $PRIVATE_FILE > /dev/null || log_error "mismatch active in $PRIVATE_FILE" + grep "Active: " $STATE_FILE > /dev/null || log_error "mismatch active in $STATE_FILE" + fi + + if [ "$_retired" == "none" ]; then + grep "; Inactive:" $KEY_FILE > /dev/null && log_error "unexpected retired comment in $KEY_FILE" + grep "Inactive:" $PRIVATE_FILE > /dev/null && log_error "unexpected retired in $PRIVATE_FILE" + grep "Retired: " $STATE_FILE > /dev/null && log_error "unexpected retired in $STATE_FILE" + else + grep "; Inactive:" $KEY_FILE > /dev/null || log_error "mismatch retired comment in $KEY_FILE" + grep "Inactive:" $PRIVATE_FILE > /dev/null || log_error "mismatch retired in $PRIVATE_FILE" + grep "Retired: " $STATE_FILE > /dev/null || log_error "mismatch retired in $STATE_FILE" + fi + + if [ "$_revoked" == "none" ]; then + grep "; Revoke:" $KEY_FILE > /dev/null && log_error "unexpected revoked comment in $KEY_FILE" + grep "Revoke:" $PRIVATE_FILE > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE" + grep "Revoked: " $STATE_FILE > /dev/null && log_error "unexpected revoked in $STATE_FILE" + else + grep "; Revoke:" $KEY_FILE > /dev/null || log_error "mismatch revoked comment in $KEY_FILE" + grep "Revoke:" $PRIVATE_FILE > /dev/null || log_error "mismatch revoked in $PRIVATE_FILE" + grep "Revoked: " $STATE_FILE > /dev/null || log_error "mismatch revoked in $STATE_FILE" + fi + + if [ "$_removed" == "none" ]; then + grep "; Delete:" $KEY_FILE > /dev/null && log_error "unexpected removed comment in $KEY_FILE" + grep "Delete:" $PRIVATE_FILE > /dev/null && log_error "unexpected removed in $PRIVATE_FILE" + grep "Removed: " $STATE_FILE > /dev/null && log_error "unexpected removed in $STATE_FILE" + else + grep "; Delete:" $KEY_FILE > /dev/null || log_error "mismatch removed comment in $KEY_FILE" + grep "Delete:" $PRIVATE_FILE > /dev/null || log_error "mismatch removed in $PRIVATE_FILE" + grep "Removed: " $STATE_FILE > /dev/null || log_error "mismatch removed in $STATE_FILE" + fi + + grep "; Created:" $KEY_FILE > /dev/null || log_error "mismatch created comment in $KEY_FILE" + grep "Created:" $PRIVATE_FILE > /dev/null || log_error "mismatch created in $PRIVATE_FILE" + grep "Generated: " $STATE_FILE > /dev/null || log_error "mismatch generated in $STATE_FILE" +} + +# Check the key with key id $1 and see if it is unused. +# This requires environment variables to be set with 'zone_properties', +# and 'key_properties'. +# +# This will set the following environment variables for testing: +# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" +# KEY_FILE="${BASE_FILE}.key" +# PRIVATE_FILE="${BASE_FILE}.private" +# STATE_FILE="${BASE_FILE}.state" +# KEY_ID=$(echo $1 | sed 's/^0*//') +key_unused() { + _dir=$DIR + _zone=$ZONE + _key_idpad=$1 + _key_id=$(echo $_key_idpad | sed 's/^0*//') + _alg_num="${KEY1[$ALG_NUM]}" + _alg_numpad=$(printf "%03d" $_alg_num) + + BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" + KEY_FILE="${BASE_FILE}.key" + PRIVATE_FILE="${BASE_FILE}.private" + STATE_FILE="${BASE_FILE}.state" + KEY_ID="${_key_id}" + + test $_log -eq 1 && echo_i "key unused $KEY_ID?" + + # Check timing metadata. + grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected publish comment in $KEY_FILE" + grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected publish in $PRIVATE_FILE" + grep "Published: " $STATE_FILE > /dev/null && log_error "unexpected publish in $STATE_FILE" + grep "; Activate:" $KEY_FILE > /dev/null && log_error "unexpected active comment in $KEY_FILE" + grep "Activate:" $PRIVATE_FILE > /dev/null && log_error "unexpected active in $PRIVATE_FILE" + grep "Active: " $STATE_FILE > /dev/null && log_error "unexpected active in $STATE_FILE" + grep "; Inactive:" $KEY_FILE > /dev/null && log_error "unexpected retired comment in $KEY_FILE" + grep "Inactive:" $PRIVATE_FILE > /dev/null && log_error "unexpected retired in $PRIVATE_FILE" + grep "Retired: " $STATE_FILE > /dev/null && log_error "unexpected retired in $STATE_FILE" + grep "; Revoke:" $KEY_FILE > /dev/null && log_error "unexpected revoked comment in $KEY_FILE" + grep "Revoke:" $PRIVATE_FILE > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE" + grep "Revoked: " $STATE_FILE > /dev/null && log_error "unexpected revoked in $STATE_FILE" + grep "; Delete:" $KEY_FILE > /dev/null && log_error "unexpected removed comment in $KEY_FILE" + grep "Delete:" $PRIVATE_FILE > /dev/null && log_error "unexpected removed in $PRIVATE_FILE" + grep "Removed: " $STATE_FILE > /dev/null && log_error "unexpected removed in $STATE_FILE" +} + +# Test: dnssec-verify zone $1. +dnssec_verify() +{ + n=$((n+1)) + echo_i "dnssec-verify zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @${SERVER} AXFR > dig.out.axfr.test$n || log_error "dig ${ZONE} AXFR failed" + $VERIFY -z -o $ZONE dig.out.axfr.test$n > /dev/null || log_error "dnssec verify zone $ZONE failed" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +############################################################################### +# Tests # +############################################################################### + +# +# dnssec-keygen +# +zone_properties "keys" "kasp" "kasp" "200" "10.53.0.1" + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)" +ret=0 +$KEYGEN -K keys -k $POLICY -l kasp.conf $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.$POLICY.test$n | wc -l) +test "$lines" -eq 4 || log_error "wrong number of keys created for policy kasp: $lines" +# Temporarily don't log errors because we are searching multiple files. +_log=0 +# Check one algorithm. +key_properties "KEY1" "csk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) +# Check the other algorithm. +key_properties "KEY1" "ksk" "31536000" "8" "RSASHA256" "2048" "no" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" + +key_properties "KEY2" "zsk" "2592000" "8" "RSASHA256" "1024" "yes" "no" +key_timings "KEY2" "none" "none" "none" "none" "none" +key_states "KEY2" "none" "none" "none" "none" "none" + +key_properties "KEY3" "zsk" "16070400" "8" "RSASHA256" "2000" "yes" "no" +key_timings "KEY3" "none" "none" "none" "none" "none" +key_states "KEY3" "none" "none" "none" "none" "none" + +ids=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +for id in $ids; do + # There are three key files with the same algorithm. + # Check them until a match is found. + ret=0 && check_key "KEY1" $id + test "$ret" -eq 0 && continue + + ret=0 && check_key "KEY2" $id + test "$ret" -eq 0 && continue + + ret=0 && check_key "KEY3" $id + # If ret is still non-zero, non of the files matched. + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +done +# Turn error logs on again. +_log=1 + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" +ret=0 +zone_properties "." "kasp" "default" "3600" "10.53.0.1" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +$KEYGEN -k $POLICY $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.default.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy default: $lines" +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" +ret=0 +zone_properties "." "kasp" "default" "3600" "10.53.0.1" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +$KEYGEN -k $POLICY $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.$POLICY.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy default: $lines" +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# +# dnssec-settime +# + +# These test builds upon the latest created key with dnssec-keygen and uses the +# environment variables BASE_FILE, KEY_FILE, PRIVATE_FILE and STATE_FILE. +CMP_FILE="${BASE_FILE}.cmp" +n=$((n+1)) +echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +$SETTIME -P +3600 $BASE_FILE > /dev/null || log_error "settime failed" +grep "; Publish: " $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE" +grep "Publish: " $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE" +$DIFF $CMP_FILE $STATE_FILE || log_error "unexpected file change in $STATE_FILE" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also sets publish time metadata and states in key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +now=$(date +%Y%m%d%H%M%S) +$SETTIME -s -P $now -g "omnipresent" -k "rumoured" $now -z "omnipresent" $now -r "rumoured" $now -d "hidden" $now $BASE_FILE > /dev/null || log_error "settime failed" +key_timings "KEY1" "published" "none" "none" "none" "none" +key_states "KEY1" "omnipresent" "rumoured" "omnipresent" "rumoured" "hidden" +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also unsets publish time metadata and states in key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +$SETTIME -s -P "none" -g "none" -k "none" $now -z "none" $now -r "none" $now -d "none" $now $BASE_FILE > /dev/null || log_error "settime failed" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase) ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +now=$(date +%Y%m%d%H%M%S) +$SETTIME -s -A $now -g "HIDDEN" -k "UNRETENTIVE" $now -z "UNRETENTIVE" $now -r "OMNIPRESENT" $now -d "OMNIPRESENT" $now $BASE_FILE > /dev/null || log_error "settime failed" +key_timings "KEY1" "none" "active" "none" "none" "none" +key_states "KEY1" "hidden" "unretentive" "unretentive" "omnipresent" "omnipresent" +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + + +# +# named +# +# +# The NSEC record at the apex of the zone and its RRSIG records are +# added as part of the last step in signing a zone. We wait for the +# NSEC records to appear before proceeding with a counter to prevent +# infinite loops if there is a error. +# +n=$((n+1)) +echo_i "waiting for kasp signing changes to take effect ($n)" +i=0 +while [ $i -lt 30 ] +do + ret=0 + for z in `cat ns3/zones` + do + dig_with_opts $z @10.53.0.3 nsec > dig.out.ns3.test$n.$z || ret=1 + grep "NS SOA" dig.out.ns3.test$n.$z > /dev/null || ret=1 + grep "$z\..*IN.*RRSIG" dig.out.ns3.test$n.$z > /dev/null || ret=1 + done + i=`expr $i + 1` + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# +# Zone: default.kasp. +# + +# Check the zone with default kasp policy has loaded and is signed. +zone_properties "ns3" "default.kasp" "default" "3600" "1" "10.53.0.3" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +# The first key is immediately published and activated. +key_timings "KEY1" "published" "active" "none" "none" "none" "none" +# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. +key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + +n=$((n+1)) +echo_i "check key is created for zone ${ZONE} ($n)" +ret=0 +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# Verify signed zone. +dnssec_verify $ZONE + +# Test DNSKEY query. +qtype="DNSKEY" +n=$((n+1)) +echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" +ret=0 +dig_with_opts $ZONE @${SERVER} $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" +grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" +grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*${KEY1[$ALG_NUM]}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" +lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) +test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" +get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# Test SOA query. +qtype="SOA" +n=$((n+1)) +echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" +ret=0 +dig_with_opts $ZONE @${SERVER} $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" +grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" +grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${qtype}.*mname1\..*\." dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" +lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) +test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" +get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# Update zone. +n=$((n+1)) +echo_i "check that we can update unsigned zone file and new record gets signed for zone ${ZONE} ($n)" +ret=0 +cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db" +rndccmd 10.53.0.3 reload $ZONE > /dev/null || log_error "rndc reload zone ${ZONE} failed" +_log=0 +i=0 +while [ $i -lt 5 ] +do + ret=0 + + dig_with_opts "a.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.a > /dev/null || log_error "mismatch status in DNS response" + grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" + lines=$(get_keys_which_signed A dig.out.$DIR.test$n.a | wc -l) + test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" + get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}" + + dig_with_opts "d.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" + grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" + lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) + test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" + get_keys_which_signed A dig.out.$DIR.test$n.d | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}" + + i=`expr $i + 1` + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +_log=1 +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# +# Zone: rsasha1.kasp. +# +zone_properties "ns3" "rsasha1.kasp" "rsasha1" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" "no" +# The first keys are immediately published and activated. +# Because lifetime > 0, retired timing is also set. +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_timings "KEY3" "published" "active" "retired" "none" "none" +# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. +# ZSK: DNSKEY, RRSIG (zsk) published. +key_states "KEY1" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_states "KEY2" "omnipresent" "rumoured" "rumoured" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "rumoured" "none" "none" + +# Check keys for a configured zone. This verifies: +# 1. The right number of keys exist in the key pool ($1). +# 2. The right number of keys is active (always expect three keys). +# The algorithm expected is set with $2 (string) and $3 (number), and the +# expected sizes for the keys are set with $4 (ksk), $5 and $6 (zsk). +# A size set to 0 means the corresponding key (KEY1, KEY2 or KEY3) is not +# expected. +# +# It is expected that KEY1, KEY2 and KEY3 arrays are set correctly. Found key +# identifiers are stored in the right key array. +check_keys() +{ + n=$((n+1)) + echo_i "check keys are created for zone ${ZONE} ($n)" + ret=0 + + _key_algnum="${KEY1[$ALG_NUM]}" + + n=$((n+1)) + echo_i "check number of keys with algorithm ${_key_algnum} for zone ${ZONE} in dir ${DIR} ($n)" + ret=0 + _numkeys=$(get_keyids $DIR $ZONE $_key_algnum | wc -l) + test "$_numkeys" -eq $NUM_KEYS || log_error "bad number ($_numkeys) of key files for zone $ZONE (expected $NUM_KEYS)" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + + # Temporarily don't log errors because we are searching multiple files. + _log=0 + + # Clear key ids. + KEY1[$ID]="0" + KEY2[$ID]="0" + KEY3[$ID]="0" + + # Check key files. + _ids=$(get_keyids $DIR $ZONE "$_key_algnum") + for _id in $_ids; do + # There are three key files with the same algorithm. + # Check them until a match is found. + echo_i "check key $_id" + + if [ "0" == "${KEY1[$ID]}" ] && [ "${KEY1[$EXPECT]}" == "yes" ]; then + ret=0 + check_key "KEY1" $_id + test "$ret" -eq 0 && KEY1[$ID]=$KEY_ID && continue + fi + if [ "0" == "${KEY2[$ID]}" ] && [ "${KEY2[$EXPECT]}" == "yes" ]; then + ret=0 + check_key "KEY2" $_id + test "$ret" -eq 0 && KEY2[$ID]=$KEY_ID && continue + fi + if [ "0" == "${KEY3[$ID]}" ] && [ "${KEY3[$EXPECT]}" == "yes" ]; then + ret=0 + check_key "KEY3" $_id + test "$ret" -eq 0 && KEY3[$ID]=$KEY_ID && continue + fi + + # This may be an unused key. + ret=0 && key_unused $_id + test "$ret" -eq 0 && continue + + # If ret is still non-zero, non of the files matched. + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + + # Turn error logs on again. + _log=1 + + ret=0 + if [ "${KEY1[$EXPECT]}" == "yes" ]; then + test "0" == "${KEY1[$ID]}" && log_error "No KEY1 found for zone ${ZONE}" + fi + if [ "${KEY2[$EXPECT]}" == "yes" ]; then + test "0" == "${KEY2[$ID]}" && log_error "No KEY2 found for zone ${ZONE}" + fi + if [ "${KEY3[$EXPECT]}" == "yes" ]; then + test "0" == "${KEY3[$ID]}" && log_error "No KEY3 found for zone ${ZONE}" + fi + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Check if RRset of type $1 in file $2 is signed with the right keys. +# The right keys are the ones that expect a signature and matches the role $3. +check_signatures() { + _qtype=$1 + _file=$2 + _role=$3 + + if [ $_role == $KSK ]; then + _expect_type=$EXPECT_KRRSIG + elif [ $_role == $ZSK ]; then + _expect_type=$EXPECT_ZRRSIG + fi + + if [ "${KEY1[$_expect_type]}" == "yes" ] && [ "${KEY1[$_role]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY1[$ID]}" + elif [ "${KEY1[$EXPECT]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY1[$ID]}" + fi + + if [ "${KEY2[$_expect_type]}" == "yes" ] && [ "${KEY2[$_role]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY2[$ID]}" + elif [ "${KEY2[$EXPECT]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY2[$ID]}" + fi + + if [ "${KEY3[$_expect_type]}" == "yes" ] && [ "${KEY3[$_role]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY3[$ID]}" + elif [ "${KEY3[$EXPECT]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY3[$ID]}" + fi +} + +# Test CDS and CDNSKEY publication. +check_cds() { + + _qtype="CDS" + _key_algnum="${KEY1[$ALG_NUM]}" + + n=$((n+1)) + echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + + if [ "${KEY1[$STATE_DS]}" == "rumoured" ] || [ "${KEY1[$STATE_DS]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY1[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY1[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + elif [ "${KEY1[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY1[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY1[$ID]}" + fi + + if [ "${KEY2[$STATE_DS]}" == "rumoured" ] || [ "${KEY2[$STATE_DS]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY2[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY2[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + elif [ "${KEY2[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY2[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY2[$ID]}" + fi + + if [ "${KEY3[$STATE_DS]}" == "rumoured" ] || [ "${KEY3[$STATE_DS]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY3[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY3[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + elif [ "${KEY3[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY3[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY3[$ID]}" + fi + + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Test the apex of a configured zone. This checks that the SOA and DNSKEY +# RRsets are signed correctly and with the appropriate keys. +check_apex() { + + # Test DNSKEY query. + _qtype="DNSKEY" + _key_algnum="${KEY1[$ALG_NUM]}" + n=$((n+1)) + echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + + if [ "${KEY1[$STATE_DNSKEY]}" == "rumoured" ] || [ "${KEY1[$STATE_DNSKEY]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY1[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + numkeys=$((numkeys+1)) + elif [ "${KEY1[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY1[$ID]}" + fi + + if [ "${KEY2[$STATE_DNSKEY]}" == "rumoured" ] || [ "${KEY2[$STATE_DNSKEY]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY2[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + numkeys=$((numkeys+1)) + elif [ "${KEY2[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY2[$ID]}" + fi + + if [ "${KEY3[$STATE_DNSKEY]}" == "rumoured" ] || [ "${KEY3[$STATE_DNSKEY]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY3[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + numkeys=$((numkeys+1)) + elif [ "${KEY3[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY3[$ID]}" + fi + + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n $KSK + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + + # Test SOA query. + _qtype="SOA" + n=$((n+1)) + echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response" + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n $ZSK + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + + # Test CDS publication. + check_cds +} + +# Test an RRset below the apex and verify it is signed correctly. +check_subdomain() { + _qtype="A" + n=$((n+1)) + echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts a.$ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig a.${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" dig.out.$DIR.test$n > /dev/null || log_error "missing a.${ZONE} ${_qtype} record in response" + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n $ZSK + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: unsigned.kasp. +# +zone_properties "ns3" "unsigned.kasp" "none" "0" "0" "10.53.0.3" +key_clear "KEY1" +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain + +# +# Zone: inherit.kasp. +# +zone_properties "ns3" "inherit.kasp" "rsasha1" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" "no" +# The first keys are immediately published and activated. +# Because lifetime > 0, retired timing is also set. +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_timings "KEY3" "published" "active" "retired" "none" "none" +# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. +# ZSK: DNSKEY, RRSIG (zsk) published. +key_states "KEY1" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_states "KEY2" "omnipresent" "rumoured" "rumoured" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "rumoured" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: dnssec-keygen.kasp. +# +zone_properties "ns3" "dnssec-keygen.kasp" "rsasha1" "1234" "3" "10.53.0.3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: some-keys.kasp. +# +zone_properties "ns3" "some-keys.kasp" "rsasha1" "1234" "3" "10.53.0.3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: legacy-keys.kasp. +# +zone_properties "ns3" "legacy-keys.kasp" "rsasha1" "1234" "3" "10.53.0.3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: pregenerated.kasp. +# +# There are more pregenerated keys than needed, hence the number of keys is +# six, not three. +zone_properties "ns3" "pregenerated.kasp" "rsasha1" "1234" "6" "10.53.0.3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: secondary.kasp. +# +zone_properties "ns3" "secondary.kasp" "rsasha1" "1234" "3" "10.53.0.3" +# KSK properties, timings and states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Update zone. +n=$((n+1)) +echo_i "check that we correctly sign the zone after IXFR for zone ${ZONE} ($n)" +ret=0 +cp ns2/secondary.kasp.db.in2 ns2/secondary.kasp.db +rndccmd 10.53.0.2 reload $ZONE > /dev/null || log_error "rndc reload zone ${ZONE} failed" +_log=0 +i=0 +while [ $i -lt 5 ] +do + ret=0 + + dig_with_opts "a.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.a > /dev/null || log_error "mismatch status in DNS response" + grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" + check_signatures $_qtype dig.out.$DIR.test$n.a $ZSK + + dig_with_opts "d.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" + grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" + lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n.d $ZSK + + i=`expr $i + 1` + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +_log=1 +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# TODO: we might want to test: +# - configuring a zone with too many active keys (should trigger retire). +# - configuring a zone with keys not matching the policy. + +# +# Zone: rsasha1-nsec3.kasp. +# +zone_properties "ns3" "rsasha1-nsec3.kasp" "rsasha1-nsec3" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "7" "NSEC3RSASHA1" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "7" "NSEC3RSASHA1" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "7" "NSEC3RSASHA1" "2000" "yes" "no" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: rsasha256.kasp. +# +zone_properties "ns3" "rsasha256.kasp" "rsasha256" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "8" "RSASHA256" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "8" "RSASHA256" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "8" "RSASHA256" "2000" "yes" "no" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: rsasha512.kasp. +# +zone_properties "ns3" "rsasha512.kasp" "rsasha512" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "10" "RSASHA512" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "10" "RSASHA512" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "10" "RSASHA512" "2000" "yes" "no" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: ecdsa256.kasp. +# +zone_properties "ns3" "ecdsa256.kasp" "ecdsa256" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: ecdsa512.kasp. +# +zone_properties "ns3" "ecdsa384.kasp" "ecdsa384" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "14" "ECDSAP384SHA384" "384" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "14" "ECDSAP384SHA384" "384" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "14" "ECDSAP384SHA384" "384" "yes" "no" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# TODO: ED25519 and ED448. + +# +# Zone: expired-sigs.autosign. +# +zone_properties "ns3" "expired-sigs.autosign" "autosign" "300" "2" "10.53.0.3" +# Both KSK and ZSK stay OMNIPRESENT. +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +# Expect only two keys. +key_clear "KEY3" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Verify all signatures have been refreshed. +check_rrsig_refresh() { + # Apex. + _qtypes="DNSKEY SOA NS NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + # If this exact RRSIG is also in the zone file it is not refreshed. + _rrsig=`cat rrsig.out.$ZONE.$_qtype` + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + + # Below apex. + _labels="a b c ns3" + for _label in $_labels; + do + _qtypes="A NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_label} ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts "${_label}.${ZONE}" @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + _rrsig=`cat rrsig.out.$ZONE.$_qtype` + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + done +} + +check_rrsig_refresh + +# +# Zone: fresh-sigs.autosign. +# +zone_properties "ns3" "fresh-sigs.autosign" "autosign" "300" "2" "10.53.0.3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Verify signature reuse. +check_rrsig_reuse() { + # Apex. + _qtypes="NS NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + # If this exact RRSIG is also in the zone file it is not refreshed. + _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' < rrsig.out.$ZONE.$_qtype) + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + + # Below apex. + _labels="a b c ns3" + for _label in $_labels; + do + _qtypes="A NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_label} ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts "${_label}.${ZONE}" @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' < rrsig.out.$ZONE.$_qtype) + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + done +} + +check_rrsig_reuse + +# +# Zone: unfresh-sigs.autosign. +# +zone_properties "ns3" "unfresh-sigs.autosign" "autosign" "300" "2" "10.53.0.3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify +check_rrsig_refresh + +# +# Zone: zsk-missing.autosign. +# +zone_properties "ns3" "zsk-missing.autosign" "autosign" "300" "2" "10.53.0.3" +# KSK stays OMNIPRESENT. +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +# key_properties, key_timings and key_states same as above. +# TODO + +# +# Zone: zsk-retired.autosign. +# +zone_properties "ns3" "zsk-retired.autosign" "autosign" "300" "3" "10.53.0.3" +# KSK properties, timings and states same as above. +# The ZSK goal is set to HIDDEN but records stay OMNIPRESENT until the new ZSK +# is active. +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_states "KEY2" "hidden" "omnipresent" "omnipresent" "none" "none" +# A new ZSK should be introduced, so expect a key with goal OMNIPRESENT, +# the DNSKEY introduced (RUMOURED) and the signatures HIDDEN. +key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "no" "no" +key_timings "KEY3" "published" "active" "retired" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" + +# +# Test dnssec-policy inheritance. +# + +# These zones should be unsigned: +# ns2/unsigned.tld +# ns4/none.inherit.signed +# ns4/none.override.signed +# ns4/inherit.none.signed +# ns4/none.none.signed +# ns5/inherit.inherit.unsigned +# ns5/none.inherit.unsigned +# ns5/none.override.unsigned +# ns5/inherit.none.unsigned +# ns5/none.none.unsigned +key_clear "KEY1" +key_clear "KEY2" +key_clear "KEY3" + +zone_properties "ns2" "unsigned.tld" "none" "0" "0" "10.53.0.2" +TSIG="" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "none.inherit.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "none.override.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "inherit.none.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "none.none.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "inherit.inherit.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "none.inherit.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "none.override.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "inherit.none.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "none.none.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +# These zones should be signed with the default policy: +# ns2/signed.tld +# ns4/override.inherit.signed +# ns4/inherit.override.signed +# 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_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + +zone_properties "ns2" "signed.tld" "default" "3600" "1" "10.53.0.2" +TSIG="" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "override.inherit.signed" "default" "3600" "1" "10.53.0.4" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "inherit.override.signed" "default" "3600" "1" "10.53.0.4" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "override.inherit.unsigned" "default" "3600" "1" "10.53.0.5" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "inherit.override.unsigned" "default" "3600" "1" "10.53.0.5" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +# These zones should be signed with the test policy: +# ns4/inherit.inherit.signed +# ns4/override.override.signed +# ns4/override.none.signed +# 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_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + +zone_properties "ns4" "inherit.inherit.signed" "test" "3600" "1" "10.53.0.4" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "override.override.signed" "test" "3600" "1" "10.53.0.4" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "override.none.signed" "test" "3600" "1" "10.53.0.4" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "override.override.unsigned" "test" "3600" "1" "10.53.0.5" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "override.none.unsigned" "test" "3600" "1" "10.53.0.5" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Clear TSIG. +TSIG="" + +# +# Testing ZSK Pre-Publication rollover. +# + +# +# Zone: step1.zsk-prepub.autosign. +# +zone_properties "ns3" "step1.zsk-prepub.autosign" "zsk-prepub" "3600" "2" "10.53.0.3" +# Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +# Initially only two keys. +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +check_next_key_event() { + _expect=$1 + + n=$((n+1)) + echo_i "check next key event for zone ${ZONE} ($n)" + ret=0 + grep "zone ${ZONE}.*: next key event in .* seconds" "${DIR}/named.run" > keyevent.out.$ZONE.test$n || log_error "no next key event for zone ${ZONE}" + + _time=$(awk '{print $10}' < keyevent.out.$ZONE.test$n) + + # The next key event time must within 10 seconds of the + # expected time. + _expectmin=$((_expect-10)) + _expectmax=$((_expect+10)) + + test $_expectmin -le $_time || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" + test $_expectmax -ge $_time || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" + + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Next key event is when the successor ZSK needs to be published. That is +# the ZSK lifetime - prepublication time. The prepublication time is DNSKEY +# TTL plus publish safety plus the zone propagation delay. For the +# zsk-prepub policy that means: 30d - 3600s + 1d + 1h = 2498400 seconds. +check_next_key_event 2498400 + +# +# Zone: step2.zsk-prepub.autosign. +# +zone_properties "ns3" "step2.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) remains active, no change in properties/timings/states. +# New ZSK (KEY3) is prepublished. +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" +key_timings "KEY3" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor ZSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the zsk-prepub policy, this means: 3600s + 1h + 1d = 93600 seconds. +check_next_key_event 93600 + +# +# Zone: step3.zsk-prepub.autosign. +# +zone_properties "ns3" "step3.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) properties and timing metadata same as above. +# ZSK (KEY2) no longer is actively signing, RRSIG state in UNRETENTIVE. +# New ZSK (KEY3) is now actively signing, RRSIG state in RUMOURED. +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY2" "hidden" "omnipresent" "unretentive" "none" "none" + +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_states "KEY3" "omnipresent" "omnipresent" "rumoured" "none" "none" +check_keys +check_apex +# Subdomain still has good signatures of ZSK (KEY2) +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" +check_subdomain +dnssec_verify + +# Next key event is when all the RRSIG records have been replaced with +# signatures of the new ZSK, in other words when ZRRSIG becomes OMNIPRESENT. +# That is Dsgn plus the maximum zone TTL plus the zone propagation delay plus +# retire-safety. For the zsk-prepub policy that means: 1w (because 2w validity +# and refresh within a week) + 1d + 1h + 2d = 10d1h = 867600 seconds. +check_next_key_event 867600 + +# +# Zone: step4.zsk-prepub.autosign. +# +zone_properties "ns3" "step4.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) properties and timing metadata same as above. +# ZSK (KEY2) DNSKEY is no longer needed. +# ZSK (KEY3) is now actively signing, RRSIG state in RUMOURED. +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY2" "hidden" "unretentive" "hidden" "none" "none" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_states "KEY3" "omnipresent" "omnipresent" "omnipresent" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the zsk-prepub policy this is: +# 3600s + 1h = 7200s +check_next_key_event 7200 + +# +# Zone: step5.zsk-prepub.autosign. +# +zone_properties "ns3" "step5.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) properties and timing metadata same as above. +# ZSK (KEY3) DNSKEY is now completely HIDDEN and removed. +key_timings "KEY2" "published" "active" "retired" "none" "removed" +key_states "KEY2" "hidden" "hidden" "hidden" "none" "none" +# ZSK (KEY3) remains actively signing, staying in OMNIPRESENT. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. This is the +# ZSK lifetime minus Iret minus Ipub minus DNSKEY TTL. For the zsk-prepub +# policy this is: 30d - 867600s - 93600s - 3600s = 1627200 seconds. +check_next_key_event 1627200 + +# +# Testing KSK Double-KSK rollover. +# + +# +# Zone: step1.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step1.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "2" "10.53.0.3" +# Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. +key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +# Initially only two keys. +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor KSK needs to be published. That is +# the KSK lifetime - prepublication time - DS registration delay. The +# prepublication time is DNSKEY TTL plus publish safety plus the zone +# propagation delay. For the ksk-doubleksk policy that means: +# 60d - (1d3h) - (1d) = 5000400 seconds. +check_next_key_event 5000400 + +# +# Zone: step2.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step2.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) remains active, no change in properties/timings/states. +# New KSK (KEY3) is prepublished (and signs DNSKEY RRset). +key_properties "KEY3" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY3" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_timings "KEY3" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor KSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the ksk-doubleksk policy, this means: 7200s + 1h + 1d = 97200 seconds. +check_next_key_event 97200 + +# +# Zone: step3.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step3.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) DS will be removed, so it is UNRETENTIVE. +key_states "KEY1" "hidden" "omnipresent" "none" "omnipresent" "unretentive" +# New KSK (KEY3) has its DS submitted. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "rumoured" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the predecessor DS has been replaced with the +# successor DS and enough time has passed such that the all validators that +# have this DS RRset cached only know about the successor DS. This is the +# registration delay plus the retire interval, which is the parent +# propagation delay plus the DS TTL plus the retire-safety. For the +# ksk-double-ksk policy this means: 1d + 1h + 3600s + 2d = 3d2h = +# 266400 seconds. +check_next_key_event 266400 + +# +# Zone: step4.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step4.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) DNSKEY can be removed. +key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "unretentive" "none" "unretentive" "hidden" +# New KSK (KEY3) DS is now OMNIPRESENT. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the ksk-doubleksk policy this is: +# 7200s + 1h = 10800s +check_next_key_event 10800 + +# +# Zone: step5.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step5.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) DNSKEY is now HIDDEN. +key_states "KEY1" "hidden" "hidden" "none" "hidden" "hidden" +# New KSK (KEY3) stays OMNIPRESENT. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. This is the +# KSK lifetime minus Ipub minus Dreg minus Iret minus DNSKEY TTL. For the +# ksk-doubleksk this is: 60d - 1d3h - 1d - 2d2h - 2h = +# 5184000 - 97200 - 86400 - 180000 - 7200 = 4813200 seconds. +check_next_key_event 4813200 + +# +# Testing CSK key rollover (1). +# + +# +# Zone: step1.csk-roll.autosign. +# +zone_properties "ns3" "step1.csk-roll.autosign" "csk-roll" "3600" "1" "10.53.0.3" +# The CSK (KEY1) starts in OMNIPRESENT. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +# Initially only one key. +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK needs to be published. That is +# the CSK lifetime - prepublication time - DS registration delay. The +# prepublication time is DNSKEY TTL plus publish safety plus the zone +# propagation delay. For the csk-roll policy that means: +# 6mo - 1d - 3h = 15973200 seconds. +check_next_key_event 15973200 + +# +# Zone: step2.csk-roll.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step2.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# CSK (KEY1) remains active, no change in properties/timings/states. +# New CSK (KEY2) is prepublished (and signs DNSKEY RRset). +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY2" "omnipresent" "rumoured" "hidden" "rumoured" "hidden" +key_timings "KEY2" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the csk-roll policy, this means 3 hours = 10800 seconds. +check_next_key_event 10800 + +# +# Zone: step3.csk-roll.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step3.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# CSK (KEY1) DS and ZRRSIG will be removed, so it is UNRETENTIVE. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "omnipresent" "unretentive" +# New CSK (KEY2) has its DS submitted, and is signing, so the DS and ZRRSIG +# are in RUMOURED state. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "rumoured" +check_keys +check_apex +# Subdomain still has good signatures of old CSK (KEY1) +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +check_subdomain +dnssec_verify + +# Next key event is when the predecessor DS has been replaced with the +# successor DS and enough time has passed such that the all validators that +# have this DS RRset cached only know about the successor DS. This is the +# registration delay plus the retire interval, which is the parent +# propagation delay plus the DS TTL plus the retire-safety. For the +# csk-roll policy this means: 1d + 1h + 1h + 2h = 1d4h = 100800 seconds. +check_next_key_event 100800 + +# +# Zone: step4.csk-roll.autosign. +# +zone_properties "ns3" "step4.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) DS is hidden. We still need to keep the DNSKEY public +# but can remove the KRRSIG records. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "unretentive" "hidden" +# The new CSK (KEY2) DS is now OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the KRRSIG enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the csk-roll policy this is: +# 1h + 1h = 7200 seconds. +check_next_key_event 7200 + +# +# Zone: step5.csk-roll.autosign. +# +zone_properties "ns3" "step5.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) KRRSIG records are now all hidden. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "hidden" "hidden" +# The new CSK (KEY2) state does not change. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY can be removed. This is when all ZRRSIG +# records have been replaced with signatures of the new CSK. We have +# calculated the interval to be 26d3h of which 1d4h (Dreg + Iret(KSK)) plus +# 2h (DNSKEY TTL + Dprp) have already passed. So next key event is in +# 26d3h - 1d4h - 2h = 597h = 2149200 seconds. +check_next_key_event 2149200 + +# +# Zone: step6.csk-roll.autosign. +# +zone_properties "ns3" "step6.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) DNSKEY can be removed. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "unretentive" "hidden" "hidden" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the csk-roll policy this is: +# 1h + 1h = 7200 seconds. +check_next_key_event 7200 + +# +# Zone: step7.csk-roll.autosign. +# +zone_properties "ns3" "step7.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) is now completely HIDDEN. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. This is the +# CSK lifetime minus Ipub minus Dreg minus Iret minus DNSKEY TTL minus zone +# propagation delay. For the csk-roll this is: +# 6mo - 3h - 1d - 26d3h - 1h - 1h = 6mo - 27d8h = 13708800 seconds. +check_next_key_event 13708800 + +# +# Testing CSK key rollover (1). +# + +# +# Zone: step1.csk-roll2.autosign. +# +zone_properties "ns3" "step1.csk-roll2.autosign" "csk-roll2" "3600" "1" "10.53.0.3" +# The CSK (KEY1) starts in OMNIPRESENT. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +# Initially only one key. +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK needs to be published. That is +# the CSK lifetime - prepublication time - DS registration delay. The +# prepublication time is DNSKEY TTL plus publish safety plus the zone +# propagation delay. For the csk-roll2 policy that means: +# 6mo - 3h - 1w = 15454800 seconds. +check_next_key_event 15454800 + +# +# Zone: step2.csk-roll2.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step2.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" +# CSK (KEY1) remains active, no change in properties/timings/states. +# New CSK (KEY2) is prepublished (and signs DNSKEY RRset). +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY2" "omnipresent" "rumoured" "hidden" "rumoured" "hidden" +key_timings "KEY2" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the csk-roll2 policy, this means 3 hours = 10800 seconds. +check_next_key_event 10800 + +# +# Zone: step3.csk-roll2.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step3.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" +# CSK (KEY1) DS and ZRRSIG will be removed, so it is UNRETENTIVE. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "omnipresent" "unretentive" +# New CSK (KEY2) has its DS submitted, and is signing, so the DS and ZRRSIG +# are in RUMOURED state. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "rumoured" +check_keys +check_apex +# Subdomain still has good signatures of old CSK (KEY1) +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +check_subdomain +dnssec_verify + +# Next key event is when the predecessor ZRRSIG records have been replaced +# with that of the successor and enough time has passed such that the all +# validators that have such signed RRsets in cache only know about the +# successor signatures. This is the retire interval: Dsgn plus the +# maximum zone TTL plus the zone propagation delay plus retire-safety. For the +# csk-roll2 policy that means: 12h (because 1d validity and refresh within +# 12 hours) + 1d + 1h + 1h = 38h = 136800 seconds. +check_next_key_event 136800 + +# +# Zone: step4.csk-roll2.autosign. +# +zone_properties "ns3" "step4.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) ZRRSIG is now HIDDEN. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY1" "hidden" "omnipresent" "hidden" "omnipresent" "unretentive" +# The new CSK (KEY2) ZRRSIG is now OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "rumoured" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the predecessor DS has been replaced with the +# successor DS and enough time has passed such that the all validators that +# have this DS RRset cached only know about the successor DS. This is the +# registration delay plus the retire interval, which is the parent +# propagation delay plus the DS TTL plus the retire-safety. For the +# csk-roll2 policy this means: 1w + 1h + 1h + 1h = 171h = 615600 seconds. +# However, 136800 seconds have passed already, so 478800 seconds left. +check_next_key_event 478800 + +# +# Zone: step5.csk-roll2.autosign. +# +zone_properties "ns3" "step5.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) DNSKEY can be removed. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "unretentive" "hidden" "unretentive" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the csk-roll policy this is: +# 1h + 1h = 7200 seconds. +check_next_key_event 7200 + +# +# Zone: step6.csk-roll2.autosign. +# +zone_properties "ns3" "step6.csk-roll2.autosign" "csk-roll" "3600" "2" "10.53.0.3" +# The old CSK (KEY1) is now completely HIDDEN. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. +check_next_key_event 14684400 + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 96fcb0fb24..2562c1f348 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -3120,6 +3120,16 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. + + + dnssec-policy + + + + describes a DNSSEC key and signing policy for zones. + + + include @@ -11004,6 +11014,224 @@ example.com CNAME rpz-tcp-only. +
<command>dnssec-policy</command> Statement Grammar + +
+ +
<command>dnssec-policy</command> Statement Definition + and Usage + + + The dnssec-policy statement defines a key and + signing policy (KASP) for zones. + + + KASP is used to determine how one or more zones need to be signed + with DNSSEC. For example, how often RRSIG records need to be + refreshed, or what cryptographic algorithms to use. + + + You can configure multiple policies. To attach a policy to a zone + simply add dnssec-policy "policy_name" + option to the zone statement with a matching + policy name. + + + + + + dnskey-ttl + + + The TTL of the DNSKEY resource records. + Default is 3600 seconds. + + + + + + keys + + + A list of keys to use. Each line represents one key. Here is + an example (for illustration purposes only) of some possible + keys in a dnssec-policy: + + +keys { + ksk key-directory lifetime P5Y algorithm 8 2048; + zsk key-directory lifetime P30D algorithm 8; + csk key-directory lifetime P6MT12H3M15S algorithm 13; +}; + + + + This example lists three keys. The first token determines + what RRsets the key will sign. If set to + ksk the key will sign the DNSKEY, CDS, + and CDNSKEY RRsets, if set to zsk the + key will sign the other RRsets, and if set to + csk the key will sign all RRsets. + + + The following part determines where the key will be stored. + Currently keys can only be stored in the configured + 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 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. + + + + + + publish-safety + + + A margin that is added to the publish interval in key timing + equations to give some extra time to cover unforeseen events. + Default is PT5M (5 minutes). + + + + + + retire-safety + + + A margin that is added to the retire interval in key timing + equations to give some extra time to cover unforeseen events. + Default is PT5M (5 minutes). + + + + + + signatures-refresh + + + This determines when a RRSIG record needs to be refreshed. + The signatures is renewed when the time until the expiration + time is closer than signatures-refresh. + signatures-resign interval. + Default is P5D (5 days), meaning a + signature that will expire in 5 days or sooner will be + refreshed. + + + + + + signatures-validity + + + The validity period of an RRSIG record (minus the inception + offset and jitter). Default is P2W + (2 weeks). + + + + + + signatures-validity-dnskey + + + Like signatures-validity but for DNSKEY + records. Default is P2W (2 weeks). + + + + + + zone-max-ttl + + + Like max-zone-ttl, specifies the maximum + permissible TTL value in seconds. When loading a zone file + using a or + text or raw, + any record encountered with a TTL higher than + will be capped to the maximum + permissible TTL value. + + + This is needed in DNSSEC-maintained zones because when + rolling to a new DNSKEY, the old key needs to remain + available until RRSIG records have expired from + caches. The option guarantees + that the largest TTL in the zone will be no higher than the + set value. + + + (NOTE: Because map-format files + load directly into memory, this option cannot be + used with them.) + + + The default value is PT24H (24 hours). + A of zero is treated as if + the default value is in use. + + + + + + zone-propagation-delay + + + The expected propagation delay from when a zone is updated + and when the new version of the zone is served by all its + name servers. Default is PT5M (5 minutes). + + + + + + parent-ds-ttl + + + The TTL of the DS RRset that the parent uses. Default is + PT1H (1 hour). + + + + + + parent-propagation-delay + + + The expected propagation delay from when the parent zone is + updated and when the new version of the parent zone is served + by all its name servers. Default is + PT1H (1 hour). + + + + + + parent-registration-delay + + + The expected registration delay from when a DS RRset change + is requested and when the DS RRset has been updated in the + parent zone. Default is P1D (1 day). + + + + + + +
+
<command>managed-keys</command> Statement Grammar
@@ -11878,6 +12106,17 @@ view "external" {
+ + dnssec-policy + + + The key and signing policy for this zone. Set to + "default" if you want to make use + of the default policy. + + + + dnssec-update-mode diff --git a/doc/arm/dnssec-policy.grammar.xml b/doc/arm/dnssec-policy.grammar.xml new file mode 100644 index 0000000000..c7df40c4d3 --- /dev/null +++ b/doc/arm/dnssec-policy.grammar.xml @@ -0,0 +1,30 @@ + + + + + +dnssec-policy string { + dnskey-ttl ttlval; + keys { ( csk | ksk | zsk ) key-directory duration integer [ integer ] ; ... }; + parent-ds-ttl duration; + parent-propagation-delay duration; + parent-registration-delay duration; + publish-safety duration; + retire-safety duration; + signatures-refresh duration; + signatures-validity duration; + signatures-validity-dnskey duration; + zone-max-ttl duration; + zone-propagation-delay duration; +}; + + diff --git a/doc/arm/dnssec.xml b/doc/arm/dnssec.xml index de922dcb5a..3c0cf4dfec 100644 --- a/doc/arm/dnssec.xml +++ b/doc/arm/dnssec.xml @@ -15,30 +15,50 @@
Converting from insecure to secure
- Changing a zone from insecure to secure can be done in two - ways: using a dynamic DNS update, or the - auto-dnssec zone option. - For either method, you need to configure - named so that it can see the - K* files which contain the public and private - parts of the keys that will be used to sign the zone. These files - will have been generated by - dnssec-keygen. You can do this by placing them - in the key-directory, as specified in - named.conf: - + + Changing a zone from insecure to secure can be done in three + ways: using a dynamic DNS update, use the + auto-dnssec zone option, or set a DNSSEC + policy for the zone with dnssec-policy. + + + For either method, you need to configure + named so that it can see the + K* files which contain the public and private + parts of the keys that will be used to sign the zone. These files + will have been generated by + dnssec-keygen (or created when needed by + named if dnssec-policy is + used). Keys should be placed in the key-directory, as specified in + named.conf: + zone example.net { type master; update-policy local; file "dynamic/example.net/example.net"; key-directory "dynamic/example.net"; }; - - If one KSK and one ZSK DNSKEY key have been generated, this - configuration will cause all records in the zone to be signed - with the ZSK, and the DNSKEY RRset to be signed with the KSK as - well. An NSEC chain will be generated as part of the initial - signing process. + + + If one KSK and one ZSK DNSKEY key have been generated, this + configuration will cause all records in the zone to be signed + with the ZSK, and the DNSKEY RRset to be signed with the KSK as + well. An NSEC chain will be generated as part of the initial + signing process. + + + With dnssec-policy you specify what keys should + be KSK and/or ZSK. If you want a key to sign all records with a key + you will need to specify a CSK: + + + dnssec-policy csk { + keys { + csk key-directory lifetime P5Y algorithm 13; + }; + }; + +
Dynamic DNS update method
@@ -50,16 +70,20 @@ > update add example.net DNSKEY 257 3 7 AwEAAd/7odU/64o2LGsifbLtQmtO8dFDtTAZXSX2+X3e/UNlq9IHq3Y0 XtC0Iuawl/qkaKVxXe2lo8Ct+dM6UehyCqk= > send - While the update request will complete almost immediately, - the zone will not be completely signed until - named has had time to walk the zone and - generate the NSEC and RRSIG records. The NSEC record at the apex - will be added last, to signal that there is a complete NSEC - chain. - If you wish to sign using NSEC3 instead of NSEC, you should - add an NSEC3PARAM record to the initial update request. If you - wish the NSEC3 chain to have the OPTOUT bit set, set it in the - flags field of the NSEC3PARAM record. + + While the update request will complete almost immediately, + the zone will not be completely signed until + named has had time to walk the zone and + generate the NSEC and RRSIG records. The NSEC record at the apex + will be added last, to signal that there is a complete NSEC + chain. + + + If you wish to sign using NSEC3 instead of NSEC, you should + add an NSEC3PARAM record to the initial update request. If you + wish the NSEC3 chain to have the OPTOUT bit set, set it in the + flags field of the NSEC3PARAM record. + % nsupdate > ttl 3600 @@ -68,90 +92,113 @@ > update add example.net NSEC3PARAM 1 1 100 1234567890 > send - Again, this update request will complete almost - immediately; however, the record won't show up until - named has had a chance to build/remove the - relevant chain. A private type record will be created to record - the state of the operation (see below for more details), and will - be removed once the operation completes. - While the initial signing and NSEC/NSEC3 chain generation - is happening, other updates are possible as well. + + Again, this update request will complete almost + immediately; however, the record won't show up until + named has had a chance to build/remove the + relevant chain. A private type record will be created to record + the state of the operation (see below for more details), and will + be removed once the operation completes. + + + While the initial signing and NSEC/NSEC3 chain generation + is happening, other updates are possible as well. + +
Fully automatic zone signing
- To enable automatic signing, add the - auto-dnssec option to the zone statement in - named.conf. - auto-dnssec has two possible arguments: - allow or - maintain. - With - auto-dnssec allow, - named can search the key directory for keys - matching the zone, insert them into the zone, and use them to - sign the zone. It will do so only when it receives an - rndc sign <zonename>. - - auto-dnssec maintain includes the above - functionality, but will also automatically adjust the zone's - DNSKEY records on schedule according to the keys' timing metadata. - (See and - for more information.) + To enable automatic signing, you can set a + dnssec-policy, or add the + auto-dnssec option to the zone statement in + named.conf. + auto-dnssec has two possible arguments: + allow or + maintain. - named will periodically search the key directory - for keys matching the zone, and if the keys' metadata indicates - that any change should be made the zone, such as adding, removing, - or revoking a key, then that action will be carried out. By default, - the key directory is checked for changes every 60 minutes; this period - can be adjusted with the , up - to a maximum of 24 hours. The rndc loadkeys forces - named to check for key updates immediately. + With auto-dnssec allow, + named can search the key directory for keys + matching the zone, insert them into the zone, and use them to + sign the zone. It will do so only when it receives an + rndc sign <zonename>. - If keys are present in the key directory the first time the zone - is loaded, the zone will be signed immediately, without waiting for an - rndc sign or rndc loadkeys - command. (Those commands can still be used when there are unscheduled - key changes, however.) + + auto-dnssec maintain includes the above + functionality, but will also automatically adjust the zone's + DNSKEY records on schedule according to the keys' timing metadata. + (See and + for more information.) - When new keys are added to a zone, the TTL is set to match that - of any existing DNSKEY RRset. If there is no existing DNSKEY RRset, - then the TTL will be set to the TTL specified when the key was - created (using the dnssec-keygen -L option), if - any, or to the SOA TTL. + dnssec-policy is like + auto-dnssec maintain, but will also automatically + create new keys when necessary. Also any configuration related + to DNSSEC signing is retrieved from the policy (ignoring existing + DNSSEC named.conf options). - If you wish the zone to be signed using NSEC3 instead of NSEC, - submit an NSEC3PARAM record via dynamic update prior to the - scheduled publication and activation of the keys. If you wish the - NSEC3 chain to have the OPTOUT bit set, set it in the flags field - of the NSEC3PARAM record. The NSEC3PARAM record will not appear in - the zone immediately, but it will be stored for later reference. When - the zone is signed and the NSEC3 chain is completed, the NSEC3PARAM - record will appear in the zone. + named will periodically search the key directory + for keys matching the zone, and if the keys' metadata indicates + that any change should be made the zone, such as adding, removing, + or revoking a key, then that action will be carried out. By default, + the key directory is checked for changes every 60 minutes; this period + can be adjusted with the , up + to a maximum of 24 hours. The rndc loadkeys forces + named to check for key updates immediately. - Using the - auto-dnssec option requires the zone to be - configured to allow dynamic updates, by adding an - allow-update or - update-policy statement to the zone - configuration. If this has not been done, the configuration will - fail. + + If keys are present in the key directory the first time the zone + is loaded, the zone will be signed immediately, without waiting for an + rndc sign or rndc loadkeys + command. (Those commands can still be used when there are unscheduled + key changes, however.) + + + When new keys are added to a zone, the TTL is set to match that + of any existing DNSKEY RRset. If there is no existing DNSKEY RRset, + then the TTL will be set to the TTL specified when the key was + created (using the dnssec-keygen -L option), if + any, or to the SOA TTL. + + + If you wish the zone to be signed using NSEC3 instead of NSEC, + submit an NSEC3PARAM record via dynamic update prior to the + scheduled publication and activation of the keys. If you wish the + NSEC3 chain to have the OPTOUT bit set, set it in the flags field + of the NSEC3PARAM record. The NSEC3PARAM record will not appear in + the zone immediately, but it will be stored for later reference. When + the zone is signed and the NSEC3 chain is completed, the NSEC3PARAM + record will appear in the zone. + + + Using the + auto-dnssec option requires the zone to be + configured to allow dynamic updates, by adding an + allow-update or + update-policy statement to the zone + configuration. If this has not been done, the configuration will + fail. + +
Private-type records
- The state of the signing process is signaled by - private-type records (with a default type value of 65534). When - signing is complete, these records will have a nonzero value for - the final octet (for those records which have a nonzero initial - octet). - The private type record format: If the first octet is - non-zero then the record indicates that the zone needs to be - signed with the key matching the record, or that all signatures - that match the record should be removed. + + The state of the signing process is signaled by + private-type records (with a default type value of 65534). When + signing is complete, these records will have a nonzero value for + the final octet (for those records which have a nonzero initial + octet). + + + The private type record format: If the first octet is + non-zero then the record indicates that the zone needs to be + signed with the key matching the record, or that all signatures + that match the record should be removed. + @@ -161,14 +208,18 @@ complete flag (octet 5) - Only records flagged as "complete" can be removed via - dynamic update. Attempts to remove other private type records - will be silently ignored. - If the first octet is zero (this is a reserved algorithm - number that should never appear in a DNSKEY record) then the - record indicates changes to the NSEC3 chains are in progress. The - rest of the record contains an NSEC3PARAM record. The flag field - tells what operation to perform based on the flag bits. + + Only records flagged as "complete" can be removed via + dynamic update. Attempts to remove other private type records + will be silently ignored. + + + If the first octet is zero (this is a reserved algorithm + number that should never appear in a DNSKEY record) then the + record indicates changes to the NSEC3 chains are in progress. The + rest of the record contains an NSEC3PARAM record. The flag field + tells what operation to perform based on the flag bits. + @@ -181,106 +232,139 @@
DNSKEY rollovers
- As with insecure-to-secure conversions, rolling DNSSEC - keys can be done in two ways: using a dynamic DNS update, or the - auto-dnssec zone option. + + As with insecure-to-secure conversions, rolling DNSSEC + keys can be done in two ways: using a dynamic DNS update, or the + auto-dnssec zone option. + +
Dynamic DNS update method
- To perform key rollovers via dynamic update, you need to add - the K* files for the new keys so that - named can find them. You can then add the new - DNSKEY RRs via dynamic update. - named will then cause the zone to be signed - with the new keys. When the signing is complete the private type - records will be updated so that the last octet is non - zero. - If this is for a KSK you need to inform the parent and any - trust anchor repositories of the new KSK. - You should then wait for the maximum TTL in the zone before - removing the old DNSKEY. If it is a KSK that is being updated, - you also need to wait for the DS RRset in the parent to be - updated and its TTL to expire. This ensures that all clients will - be able to verify at least one signature when you remove the old - DNSKEY. - The old DNSKEY can be removed via UPDATE. Take care to - specify the correct key. - named will clean out any signatures generated - by the old key after the update completes. + + To perform key rollovers via dynamic update, you need to add + the K* files for the new keys so that + named can find them. You can then add the new + DNSKEY RRs via dynamic update. + named will then cause the zone to be signed + with the new keys. When the signing is complete the private type + records will be updated so that the last octet is non + zero. + + + If this is for a KSK you need to inform the parent and any + trust anchor repositories of the new KSK. + + + You should then wait for the maximum TTL in the zone before + removing the old DNSKEY. If it is a KSK that is being updated, + you also need to wait for the DS RRset in the parent to be + updated and its TTL to expire. This ensures that all clients will + be able to verify at least one signature when you remove the old + DNSKEY. + + + The old DNSKEY can be removed via UPDATE. Take care to + specify the correct key. + named will clean out any signatures generated + by the old key after the update completes. + +
Automatic key rollovers
- When a new key reaches its activation date (as set by - dnssec-keygen or dnssec-settime), - if the auto-dnssec zone option is set to - maintain, named will - automatically carry out the key rollover. If the key's algorithm - has not previously been used to sign the zone, then the zone will - be fully signed as quickly as possible. However, if the new key - is replacing an existing key of the same algorithm, then the - zone will be re-signed incrementally, with signatures from the - old key being replaced with signatures from the new key as their - signature validity periods expire. By default, this rollover - completes in 30 days, after which it will be safe to remove the - old key from the DNSKEY RRset. + + When a new key reaches its activation date (as set by + dnssec-keygen or dnssec-settime), + if the auto-dnssec zone option is set to + maintain, named will + automatically carry out the key rollover. If the key's algorithm + has not previously been used to sign the zone, then the zone will + be fully signed as quickly as possible. However, if the new key + is replacing an existing key of the same algorithm, then the + zone will be re-signed incrementally, with signatures from the + old key being replaced with signatures from the new key as their + signature validity periods expire. By default, this rollover + completes in 30 days, after which it will be safe to remove the + old key from the DNSKEY RRset. + +
NSEC3PARAM rollovers via UPDATE
- Add the new NSEC3PARAM record via dynamic update. When the - new NSEC3 chain has been generated, the NSEC3PARAM flag field - will be zero. At this point you can remove the old NSEC3PARAM - record. The old chain will be removed after the update request - completes. + + Add the new NSEC3PARAM record via dynamic update. When the + new NSEC3 chain has been generated, the NSEC3PARAM flag field + will be zero. At this point you can remove the old NSEC3PARAM + record. The old chain will be removed after the update request + completes. + +
Converting from NSEC to NSEC3
- To do this, you just need to add an NSEC3PARAM record. When - the conversion is complete, the NSEC chain will have been removed - and the NSEC3PARAM record will have a zero flag field. The NSEC3 - chain will be generated before the NSEC chain is - destroyed. + + To do this, you just need to add an NSEC3PARAM record. When + the conversion is complete, the NSEC chain will have been removed + and the NSEC3PARAM record will have a zero flag field. The NSEC3 + chain will be generated before the NSEC chain is + destroyed. + + + NSEC3 is not supported yet with dnssec-policy. + +
Converting from NSEC3 to NSEC
- To do this, use nsupdate to - remove all NSEC3PARAM records with a zero flag - field. The NSEC chain will be generated before the NSEC3 chain is - removed. + + To do this, use nsupdate to + remove all NSEC3PARAM records with a zero flag + field. The NSEC chain will be generated before the NSEC3 chain is + removed. + +
Converting from secure to insecure
- To convert a signed zone to unsigned using dynamic DNS, - delete all the DNSKEY records from the zone apex using - nsupdate. All signatures, NSEC or NSEC3 chains, - and associated NSEC3PARAM records will be removed automatically. - This will take place after the update request completes. - This requires the - dnssec-secure-to-insecure option to be set to - yes in - named.conf. - In addition, if the auto-dnssec maintain - zone statement is used, it should be removed or changed to - allow instead (or it will re-sign). + + To convert a signed zone to unsigned using dynamic DNS, + delete all the DNSKEY records from the zone apex using + nsupdate. All signatures, NSEC or NSEC3 chains, + and associated NSEC3PARAM records will be removed automatically. + This will take place after the update request completes. + This requires the + dnssec-secure-to-insecure option to be set to + yes in + named.conf. + In addition, if the auto-dnssec maintain + zone statement is used, it should be removed or changed to + allow instead (or it will re-sign). +
Periodic re-signing
- In any secure zone which supports dynamic updates, named - will periodically re-sign RRsets which have not been re-signed as - a result of some update action. The signature lifetimes will be - adjusted so as to spread the re-sign load over time rather than - all at once. + + In any secure zone which supports dynamic updates, named + will periodically re-sign RRsets which have not been re-signed as + a result of some update action. The signature lifetimes will be + adjusted so as to spread the re-sign load over time rather than + all at once. + +
NSEC3 and OPTOUT
- named only supports creating new NSEC3 chains - where all the NSEC3 records in the zone have the same OPTOUT - state. - named supports UPDATES to zones where the NSEC3 - records in the chain have mixed OPTOUT state. - named does not support changing the OPTOUT - state of an individual NSEC3 record, the entire chain needs to be - changed if the OPTOUT state of an individual NSEC3 needs to be - changed. + named only supports creating new NSEC3 chains + where all the NSEC3 records in the zone have the same OPTOUT + state. + named supports UPDATES to zones where the NSEC3 + records in the chain have mixed OPTOUT state. + named does not support changing the OPTOUT + state of an individual NSEC3 record, the entire chain needs to be + changed if the OPTOUT state of an individual NSEC3 needs to be + changed. +
diff --git a/doc/arm/master.zoneopt.xml b/doc/arm/master.zoneopt.xml index d612445880..054b440492 100644 --- a/doc/arm/master.zoneopt.xml +++ b/doc/arm/master.zoneopt.xml @@ -36,6 +36,7 @@ dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-secure-to-insecure boolean; dnssec-update-mode ( maintain | no-resign ); file quoted_string; @@ -51,7 +52,7 @@ max-records integer; max-transfer-idle-out integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); notify ( explicit | master-only | boolean ); notify-delay integer; notify-source ( ipv4_address | * ) [ port ( integer | * ) ] [ dscp integer ]; diff --git a/doc/arm/options.grammar.xml b/doc/arm/options.grammar.xml index 3cd76e6d3d..64a95defb4 100644 --- a/doc/arm/options.grammar.xml +++ b/doc/arm/options.grammar.xml @@ -45,7 +45,7 @@ [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -127,18 +127,18 @@ fstrm-set-output-notify-threshold integer; fstrm-set-output-queue-model ( mpsc | spsc ); fstrm-set-output-queue-size integer; - fstrm-set-reopen-interval ttlval; + fstrm-set-reopen-interval duration; geoip-directory ( quoted_string | none ); glue-cache boolean; heartbeat-interval integer; hostname ( quoted_string | none ); inline-signing boolean; - interface-interval ttlval; + interface-interval duration; ixfr-from-differences ( primary | master | secondary | slave | boolean ); keep-response-order { address_match_element; ... }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; listen-on [ port integer ] [ dscp integer ] { address_match_element; ... }; @@ -152,28 +152,28 @@ masterfile-style ( full | relative ); match-mapped-addresses boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; max-rsa-exponent-size integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); memstatistics boolean; memstatistics-file quoted_string; message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -190,8 +190,8 @@ notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; pid-file ( quoted_string | none ); port integer; @@ -238,13 +238,13 @@ response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -258,7 +258,7 @@ serial-query-rate integer; serial-update-method ( date | increment | unixtime ); server-id ( quoted_string | none | hostname ); - servfail-ttl ttlval; + servfail-ttl duration; session-keyalg string; session-keyfile ( quoted_string | none ); session-keyname string; @@ -269,7 +269,7 @@ sortlist { address_match_element; ... }; stacksize ( default | unlimited | sizeval ); stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; startup-notify-rate integer; statistics-file quoted_string; synth-from-dnssec boolean; diff --git a/doc/arm/redirect.zoneopt.xml b/doc/arm/redirect.zoneopt.xml index 335cfa0387..91622cc2c1 100644 --- a/doc/arm/redirect.zoneopt.xml +++ b/doc/arm/redirect.zoneopt.xml @@ -21,7 +21,7 @@ masterfile-style ( full | relative ); masters [ port integer ] [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... }; max-records integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); zone-statistics ( full | terse | none | boolean ); }; diff --git a/doc/arm/slave.zoneopt.xml b/doc/arm/slave.zoneopt.xml index 63c0a4acf1..e78f296119 100644 --- a/doc/arm/slave.zoneopt.xml +++ b/doc/arm/slave.zoneopt.xml @@ -29,6 +29,7 @@ dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-update-mode ( maintain | no-resign ); file quoted_string; forward ( first | only ); diff --git a/doc/design/dnssec-policy b/doc/design/dnssec-policy new file mode 100644 index 0000000000..73f032b77d --- /dev/null +++ b/doc/design/dnssec-policy @@ -0,0 +1,226 @@ +Copyright (C) Internet Systems Consortium, Inc. ("ISC") + +See COPYRIGHT in the source root or http://isc.org/copyright.html for terms. + +# DNSSEC Key and Signing Policy + +A DNSSEC key and signing policy (KASP) defines a DNSSEC policy that can be +applied to one or more zones. + +For some background information, see: + + https://www.ietf.org/archive/id/draft-mekking-dnsop-kasp-00.txt + +# DNSSEC in BIND 9 + +DNSSEC is first implemented in BIND 9. Many adaptations have been made since +then. A lot of configuration knobs were added. One aim with introducing KASP +configuration is that all these configuration options are grouped together, +making the named configuration more intuitive when it comes to DNSSEC, and +making it easier to turn on DNSSEC for zones. Instead of configuring many +different options per zone, you would be able to do the following: + +``` +zone "example.com." { + ... + dnssec-policy "_default"; +}; +``` + +## Existing DNSSEC configuration options + +### Signing + +The following configuration options exist nowadays for `named` to maintain +DNSSEC signed zones. These will no longer work if an explicit DNSSEC policy +is set for a zone. + +1. `auto-dnssec`: When setting a DNSSEC policy for a zone instead, the + behavior will be as if `auto-dnssec` was set to `maintain`. + +1. `dnskey-sig-validity`: This option will be replaced in favor of the KASP + configuration value `signatures-validity-dnskey`. + +1. `dnssec-dnskey-kskonly`: This option will be removed and the key + configuration from the policy will be used to determine what RRsets will be + signed with which keys (Keys will have a role "KSK" and/or "ZSK"). + +1. `dnssec-loadkeys-interval`: This option will determine how the period that + BIND 9 will check its key repository (default once per hour) to see if + there are new keys added or if existing keys metadata has changed. This + option might go away because the entity that performs DNSSEC maintenance + knows exactly when the next step needs to happen. We can set the interval + accordingly. This does mean that whenever a new key is added or deprecated + manually, the interval needs to be set to now. Alternatively, we keep this + option and only pick up new keys when at a certain interval. + +1. `dnssec-secure-to-insecure`: This option allows a dynamic zone to + transition from secure to insecure. This seems to be a safety check + when named is not responsible for signing. This will likely go away + because explicitly removing the dnssec-policy will be the same signal + to (safely) make the zone insecure. + +1. `dnssec-update-mode`: This option determines how DNSSEC signed dynamic + zones are updated. Default is `maintain` and it is unclear how it is + different from `auto-dnssec`. With KASP, the behavior will be as if + the `dnssec-update-mode` was set to `maintain`. If you want DNSSEC + maintenance to be done outside `named`, you should not configure a + `dnssec-policy` for that zone. + +1. `inline-signing`: When set to "yes", this option will sign transferred + unsigned zones, and unsigned zone from file. This is also no longer needed + when KASP is introduced because when setting a `dnssec-policy` for a + secondary zone or a zone with zone file, this indicates that + `inline-signing` is desired. + +1. `max-zone-ttl`: This will cap all TTLs in a zone file to the specified + value. Although this option may be used for non-DNSSEC zones, it is really + only useful for DNSSEC-signed zones because when performing key rollovers + the timing depends on the largest TTL in the zone. The value set in the + `dnssec-policy` statement will override the existing `max-zone-ttl` value. + +1. `sig-signing-nodes`: This specifies the number of nodes to be examined + in a quantum when signing a zone with a new DNSKEY. This presumable is + to avoid keeping the database connection open for a long time. With the + current database approach this probably needs to stay. + +1. `sig-signing-signatures`: This specifies a threshold number of how many + signatures will be generated in a quantum. Similar to `sig-signing-nodes`. + +1. `sig-signing-type`: Internal record type number, used to track zone + signing process. This likely will go away in favor of a new method. + +1. `sig-validity-interval`: Specifies the number of days a signature is valid. + The second optional value is the refresh interval. Thos option will + be replaced by KASP configuration values "signatures-validity" and + "signatures-refresh". + +1. `update-check-ksk`: When set to "no", KSK will also sign non-DNSKEY RRsets. + This option will go away and key roles will be used to determine what + keys sign which RRsets (A KSK that should sign all RRsets will have both + the KSK and ZSK role and is referred to as a CSK). + +Other DNSSEC related configuration options that are not related to the policy +are likely to stay: + +1. `key-directory`: This is where the DNSKEY key files can be found. + +1. `serial-update-method`: This is used for dynamic zones to determne how + the SOA SERIAL should be updated. There will likely be a separate + configuration option for the serial update method when resigning a zone. + + +# KASP Configuration + +The KASP Configuration may look something like the example configuration +below. This includes all options as described in the KASP draft, but we may +decide that some options are not required. + +``` +dnssec-policy "nsec3" { + + description "policy for zones that require zone walking mitigation"; + + // Signatures + signatures-refresh P3D; + signatures-validity P14D; + signatures-validity-dnskey P14D; + + // Denial of existence + denial-type nsec3; + nsec3-param ttl 0 hash algorithm 1 iterations 5 optout; + nsec3-salt length 8 resalt P100D; + + // Keys + dnskey-ttl 3600; + publish-safety PT3600S; + retire-safety PT3600S; + share-keys no; + purge-keys-after P14D; + + keys { + ksk key-directory P5Y ECDSAP256SHA256; + zsk key-directory P30D ECDSAP256SHA256; + csk key-directory PT0S 8 2048; + }; + + // Parent synchronization + cds yes; + cdnskey yes; + check-ds { 127.0.0.53; }; + check-ds-interval PT3600S; + + // Zone properties + zone-propagation-delay PT3600S; + zone-registration-delay PT3600S; + zone-soa-ttl 3600; + zone-soa-minimum 3600; + zone-soa-serial-update-method unixtime; + zone-max-ttl 24h; + + // Parent properties + parent-propagation-delay PT24H; + parent-ds-ttl 3600; + parent-soa-ttl 3600; + parent-soa-minimum 3600; +}; +``` + +# KASP design + +## dnssec-policy versus dnssec-keymgr + +Key management in BIND 9 is currently implemented with a Python script +called `dnssec-keymgr`. It uses the DNSSEC tools for manipulating DNSSEC key +metadata. + +With `dnssec-policy` configured in `named.conf` you no longer need to manually +call `dnssec-keymgr` or the tools it wraps around, `dnssec-keygen` and +`dnssec-settime` (although it is still possible to use them). The policy in +`named.conf` will make `named` create keys when necessary and set the key +timings accordingly. + +## Key roles + +BIND 9.14 allows sign your zones with a Zone Signing Key (ZSK) and a +Key Signing Key (KSK). If you provide only one key, the zone will be signed +with just one key (effectively acting as a Combined Signing Key (CSK). If +one of the keys is offline, BIND 9 will temporarily change the key usage: A +KSK may sign DNSKEY unrelated RRsets. + +With BIND 9.14, ZSKs by default sign the complete zone, except when +`dnssec-dnskey-kskonly` and `update-check-ksk` are both set to `yes`. + +KASP introduces key roles making key usage more explicit, without depending +on state of the keys or additional configuration values. A key that has the +KSK role will always sign only DNSKEY related RRsets, and a key with a ZSK role +will always sign only DNSKEY unrelated RRsets. A key can have both roles, which +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; + zsk key-directory lifetime P30D algorithm ECDSAP256SHA256; + csk key-directory lifetime PT0S algorithm 8 2048; + }; +``` + +## NSEC3 + +Currently if you want to sign your zone with NSEC3 you can do so by introducing +an NSEC3PARAM record via Dynamic Update. This is no longer necessary with +`dnssec-policy` as you can configure NSEC3 usage in `named.conf`. + +## Changing policies + +You can change a zone's policy by referring to a different `dnssec-policy` +or by changing the `dnssec-policy` itself. After a reload of the configuration +key timings may be adjusted. This may trigger a key rollover (for example if +the key lifetimes have been shortened, or if other key properties have changed. + +## Key state machines + +Rollover correctness are guaranteed by key state machines. See for more +information: + + https://nlnetlabs.nl/downloads/publications/satin2012-Schaeffer.pdf diff --git a/doc/misc/master.zoneopt b/doc/misc/master.zoneopt index aa55ed33e8..694d84eb69 100644 --- a/doc/misc/master.zoneopt +++ b/doc/misc/master.zoneopt @@ -23,6 +23,7 @@ zone [ ] { dnskey-sig-validity ; dnssec-dnskey-kskonly ; dnssec-loadkeys-interval ; + dnssec-policy ; dnssec-secure-to-insecure ; dnssec-update-mode ( maintain | no-resign ); file ; diff --git a/doc/misc/options b/doc/misc/options index 509cc38cf9..61dad9bbba 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -25,6 +25,22 @@ dnssec-keys { ( static-key | initial-key ) ; ... }; // may occur multiple times +dnssec-policy { + dnskey-ttl ; + keys { ( csk | ksk | zsk ) key-directory lifetime algorithm + [ ]; ... }; + parent-ds-ttl ; + parent-propagation-delay ; + parent-registration-delay ; + publish-safety ; + retire-safety ; + signatures-refresh ; + signatures-validity ; + signatures-validity-dnskey ; + zone-max-ttl ; + zone-propagation-delay ; +}; // may occur multiple times + dyndb { }; // may occur multiple times diff --git a/doc/misc/slave.zoneopt b/doc/misc/slave.zoneopt index 750392f254..2dc3fd535c 100644 --- a/doc/misc/slave.zoneopt +++ b/doc/misc/slave.zoneopt @@ -16,6 +16,7 @@ zone [ ] { dnskey-sig-validity ; dnssec-dnskey-kskonly ; dnssec-loadkeys-interval ; + dnssec-policy ; dnssec-update-mode ( maintain | no-resign ); file ; forward ( first | only ); diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 1426c41867..402a679053 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -842,6 +842,21 @@ check_name(const char *str) { return (dns_name_fromstring(dns_fixedname_name(&fixed), str, 0, NULL)); } +static bool +kasp_name_allowed(const cfg_listelt_t *element) +{ + const char* name = cfg_obj_asstring(cfg_tuple_get( + cfg_listelt_value(element), "name")); + + if (strcmp("none", name) == 0) { + return false; + } + if (strcmp("default", name) == 0) { + return false; + } + return true; +} + static isc_result_t check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, optlevel_t optlevel) @@ -856,6 +871,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, const char *str; isc_buffer_t b; uint32_t lifetime = 3600; + bool has_dnssecpolicy = false; const char *ccalg = "siphash24"; /* @@ -929,7 +945,11 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, (void)cfg_map_get(options, intervals[i].name, &obj); if (obj == NULL) continue; - val = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + val = cfg_obj_asduration(obj); + } else { + val = cfg_obj_asuint32(obj); + } if (val > intervals[i].max) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' is out of range (0..%u)", @@ -944,6 +964,56 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, } } + /* + * Check dnssec-policy. + */ + obj = NULL; + (void)cfg_map_get(options, "dnssec-policy", &obj); + if (obj != NULL) { + bool bad_kasp = false; + bool bad_name = false; + if (optlevel != optlevel_config && !cfg_obj_isstring(obj)) { + bad_kasp = true; + } else if (optlevel == optlevel_config) { + if (cfg_obj_islist(obj)) { + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + if (!cfg_obj_istuple( + cfg_listelt_value(element))) + { + bad_kasp = true; + } + if (!kasp_name_allowed(element)) { + bad_name = true; + } + } + } + } + + if (bad_kasp) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-policy may only be configured at " + "the top level, please use name reference " + "at the zone level"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + + if (bad_name) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-policy name may not be 'none' or " + "'default' (which is the built-in policy)"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + + has_dnssecpolicy = true; + } + obj = NULL; cfg_map_get(options, "max-rsa-exponent-size", &obj); if (obj != NULL) { @@ -992,6 +1062,13 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, result = ISC_R_RANGE; } } + + if (has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "sig-validity-interval: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } } obj = NULL; @@ -1008,6 +1085,12 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, result = ISC_R_RANGE; } + if (has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnskey-sig-validity: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } } obj = NULL; @@ -1113,8 +1196,9 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) result = tresult; } - if (symtab != NULL) + if (symtab != NULL) { isc_symtab_destroy(&symtab); + } } /* @@ -1176,7 +1260,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, obj = NULL; (void)cfg_map_get(options, "nta-lifetime", &obj); if (obj != NULL) { - lifetime = cfg_obj_asuint32(obj); + lifetime = cfg_obj_asduration(obj); if (lifetime > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' cannot exceed one week"); @@ -1193,7 +1277,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, obj = NULL; (void)cfg_map_get(options, "nta-recheck", &obj); if (obj != NULL) { - uint32_t recheck = cfg_obj_asuint32(obj); + uint32_t recheck = cfg_obj_asduration(obj); if (recheck > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-recheck' cannot exceed one week"); @@ -1271,7 +1355,11 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (obj == NULL) continue; - value = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + value = cfg_obj_asduration(obj); + } else { + value = cfg_obj_asuint32(obj); + } if (value < fstrm[i].min || (fstrm[i].max != 0U && value > fstrm[i].max)) { if (fstrm[i].max != 0U) @@ -1850,6 +1938,7 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, bool dlz; dns_masterformat_t masterformat; bool ddns = false; + bool has_dnssecpolicy = false; const void *clauses = NULL; const char *option = NULL; static const char *acls[] = { @@ -2062,6 +2151,45 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (check_nonzero(zoptions, logctx) != ISC_R_SUCCESS) result = ISC_R_FAILURE; + /* + * Check if a dnssec-policy is set. + */ + obj = NULL; + (void)cfg_map_get(zoptions, "dnssec-policy", &obj); + if (obj != NULL) { + const cfg_obj_t *kasps = NULL; + const char* kaspname = cfg_obj_asstring(obj); + + if (strcmp(kaspname, "default") == 0) { + has_dnssecpolicy = true; + } else if (strcmp(kaspname, "none") == 0) { + has_dnssecpolicy = false; + } else { + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + const char* kn = cfg_obj_asstring( + cfg_tuple_get(cfg_listelt_value(element), + "name")); + if (strcmp(kaspname, kn) == 0) { + has_dnssecpolicy = true; + } + } + + if (!has_dnssecpolicy) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': option " + "'dnssec-policy %s' has no " + "matching dnssec-policy config", + znamestr, kaspname); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + } + } + /* * Check validity of the zone options. */ @@ -2248,19 +2376,36 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (res1 == ISC_R_SUCCESS) signing = cfg_obj_asboolean(obj); + if (signing && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "inline-signing: cannot be configured if " + "dnssec-policy is also set"); + result = ISC_R_FAILURE; + } + obj = NULL; arg = "off"; res3 = cfg_map_get(zoptions, "auto-dnssec", &obj); - if (res3 == ISC_R_SUCCESS) + if (res3 == ISC_R_SUCCESS) { arg = cfg_obj_asstring(obj); - if (strcasecmp(arg, "off") != 0 && !ddns && !signing) { - cfg_obj_log(obj, logctx, ISC_LOG_ERROR, - "'auto-dnssec %s;' requires%s " - "inline-signing to be configured for " - "the zone", arg, - (ztype == CFG_ZONE_MASTER) ? - " dynamic DNS or" : ""); - result = ISC_R_FAILURE; + } + if (strcasecmp(arg, "off") != 0) { + if (!ddns && !signing) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'auto-dnssec %s;' requires%s " + "inline-signing to be configured " + "for the zone", arg, + (ztype == CFG_ZONE_MASTER) ? + " dynamic DNS or" : ""); + result = ISC_R_FAILURE; + } + if (has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'auto-dnssec %s;' cannot be " + "configured if dnssec-policy is " + "also set", arg); + result = ISC_R_FAILURE; + } } obj = NULL; @@ -2285,6 +2430,21 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, "inline-signing when used in slave zone"); result = ISC_R_FAILURE; } + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-dnskey-kskonly: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } + + obj = NULL; + res1 = cfg_map_get(zoptions, "dnssec-secure-to-insecure", &obj); + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-secure-to-insecure: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } obj = NULL; res1 = cfg_map_get(zoptions, "dnssec-loadkeys-interval", &obj); @@ -2307,6 +2467,21 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, "inline-signing when used in slave zone"); result = ISC_R_FAILURE; } + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "update-check-ksk: cannot be configured " + "if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } + + obj = NULL; + res1 = cfg_map_get(zoptions, "dnssec-update-mode", &obj); + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-update-mode: cannot be configured " + "if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } } /* diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index d99b54919a..a3cf51436d 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -64,8 +64,8 @@ DNSOBJS = acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \ db.@O@ dbiterator.@O@ dbtable.@O@ diff.@O@ dispatch.@O@ \ dlz.@O@ dns64.@O@ dnsrps.@O@ dnssec.@O@ ds.@O@ dyndb.@O@ \ ecs.@O@ fixedname.@O@ forward.@O@ \ - ipkeylist.@O@ iptable.@O@ journal.@O@ keydata.@O@ \ - keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ + ipkeylist.@O@ iptable.@O@ journal.@O@ kasp.@O@ keydata.@O@ \ + keymgr.@O@ keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ master.@O@ masterdump.@O@ message.@O@ \ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ order.@O@ peer.@O@ portlist.@O@ private.@O@ \ @@ -100,9 +100,9 @@ DNSSRCS = acl.c adb.c badcache. byaddr.c \ cache.c callbacks.c clientinfo.c compress.c \ db.c dbiterator.c dbtable.c diff.c dispatch.c \ dlz.c dns64.c dnsrps.c dnssec.c ds.c dyndb.c \ - ecs.c fixedname.c forward.c \ - ipkeylist.c iptable.c journal.c keydata.c keytable.c lib.c \ - log.c lookup.c master.c masterdump.c message.c \ + ecs.c fixedname.c forward.c ipkeylist.c iptable.c \ + journal.c kasp.c keydata.c keymgr.c keytable.c \ + lib.c log.c lookup.c master.c masterdump.c message.c \ name.c ncache.c nsec.c nsec3.c nta.c \ order.c peer.c portlist.c \ rbt.c rbtdb.c rcode.c rdata.c rdatalist.c \ diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 1b18480fd5..9e21cc35b1 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -582,52 +583,51 @@ cleanup_struct: bool dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; - isc_stdtime_t publish, active, revoke, inactive, deltime; - bool pubset = false, actset = false; - bool revset = false, inactset = false; - bool delset = false; + isc_stdtime_t publish, active, revoke, remove; + bool hint_publish, hint_zsign, hint_ksign, hint_revoke, hint_remove; int major, minor; + bool ksk = false, zsk = false; + isc_result_t ret; /* Is this an old-style key? */ result = dst_key_getprivateformat(key, &major, &minor); RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* Is this a KSK? */ + ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0); + } + /* * Smart signing started with key format 1.3; prior to that, all - * keys are assumed active + * keys are assumed active. */ if (major == 1 && minor <= 2) return (true); - result = dst_key_gettime(key, DST_TIME_PUBLISH, &publish); - if (result == ISC_R_SUCCESS) - pubset = true; + hint_publish = dst_key_is_published(key, now, &publish); + hint_zsign = dst_key_is_signing(key, DST_BOOL_ZSK, now, &active); + hint_ksign = dst_key_is_signing(key, DST_BOOL_KSK, now, &active); + hint_revoke = dst_key_is_revoked(key, now, &revoke); + hint_remove = dst_key_is_removed(key, now, &remove); - result = dst_key_gettime(key, DST_TIME_ACTIVATE, &active); - if (result == ISC_R_SUCCESS) - actset = true; - - result = dst_key_gettime(key, DST_TIME_REVOKE, &revoke); - if (result == ISC_R_SUCCESS) - revset = true; - - result = dst_key_gettime(key, DST_TIME_INACTIVE, &inactive); - if (result == ISC_R_SUCCESS) - inactset = true; - - result = dst_key_gettime(key, DST_TIME_DELETE, &deltime); - if (result == ISC_R_SUCCESS) - delset = true; - - if ((inactset && inactive <= now) || (delset && deltime <= now)) + if (hint_remove) { return (false); - - if (revset && revoke <= now && pubset && publish <= now) + } + if (hint_publish && hint_revoke) { return (true); - - if (actset && active <= now) + } + if (hint_zsign && zsk) { return (true); - + } + if (hint_ksign && ksk) { + return (true); + } return (false); } @@ -635,7 +635,10 @@ dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { * Indicate whether a key is scheduled to to have CDS/CDNSKEY records * published now. * - * Returns true iff. + * Returns true if. + * - kasp says the DS record should be published (e.g. the DS state is in + * RUMOURED or OMNIPRESENT state). + * Or: * - SyncPublish is set and in the past, AND * - SyncDelete is unset or in the future */ @@ -643,6 +646,7 @@ static bool syncpublish(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; isc_stdtime_t when; + dst_key_state_t state; int major, minor; /* @@ -654,18 +658,29 @@ syncpublish(dst_key_t *key, isc_stdtime_t now) { /* * Smart signing started with key format 1.3 */ - if (major == 1 && minor <= 2) + if (major == 1 && minor <= 2) { return (false); + } + /* Check kasp state first. */ + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + return (state == DST_KEY_STATE_RUMOURED || + state == DST_KEY_STATE_OMNIPRESENT); + } + + /* If no kasp state, check timings. */ result = dst_key_gettime(key, DST_TIME_SYNCPUBLISH, &when); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (false); - + } result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (true); - if (when <= now) + } + if (when <= now) { return (false); + } return (true); } @@ -673,12 +688,17 @@ syncpublish(dst_key_t *key, isc_stdtime_t now) { * Indicate whether a key is scheduled to to have CDS/CDNSKEY records * deleted now. * - * Returns true iff. SyncDelete is set and in the past. + * Returns true if: + * - kasp says the DS record should be unpublished (e.g. the DS state is in + * UNRETENTIVE or HIDDEN state). + * Or: + * - SyncDelete is set and in the past. */ static bool syncdelete(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; isc_stdtime_t when; + dst_key_state_t state; int major, minor; /* @@ -690,14 +710,25 @@ syncdelete(dst_key_t *key, isc_stdtime_t now) { /* * Smart signing started with key format 1.3. */ - if (major == 1 && minor <= 2) + if (major == 1 && minor <= 2) { return (false); + } + /* Check kasp state first. */ + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + return (state == DST_KEY_STATE_UNRETENTIVE || + state == DST_KEY_STATE_HIDDEN); + } + + /* If no kasp state, check timings. */ result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (false); - if (when <= now) + } + if (when <= now) { return (true); + } return (false); } @@ -742,7 +773,8 @@ dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, result = dst_key_fromfile(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, + DST_TYPE_PUBLIC|DST_TYPE_PRIVATE| + DST_TYPE_STATE, directory, mctx, &keys[count]); @@ -761,7 +793,8 @@ dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dst_key_id(pubkey), dst_key_alg(pubkey), DST_TYPE_PUBLIC| - DST_TYPE_PRIVATE, + DST_TYPE_PRIVATE| + DST_TYPE_STATE, directory, mctx, &keys[count]); if (result == ISC_R_SUCCESS && @@ -784,8 +817,9 @@ dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, result2 = dst_key_getfilename(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - (DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE), + (DST_TYPE_PUBLIC| + DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &buf); if (result2 != ISC_R_SUCCESS) { @@ -1219,6 +1253,7 @@ dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey, dk->force_sign = false; dk->hint_publish = false; dk->hint_sign = false; + dk->hint_revoke = false; dk->hint_remove = false; dk->first_sign = false; dk->is_active = false; @@ -1227,7 +1262,14 @@ dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey, dk->index = 0; /* KSK or ZSK? */ - dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0); + result = dst_key_getbool(dk->key, DST_BOOL_KSK, &dk->ksk); + if (result != ISC_R_SUCCESS) { + dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0); + } + result = dst_key_getbool(dk->key, DST_BOOL_ZSK, &dk->zsk); + if (result != ISC_R_SUCCESS) { + dk->zsk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) == 0); + } /* Is this an old-style key? */ result = dst_key_getprivateformat(dk->key, &major, &minor); @@ -1253,80 +1295,48 @@ dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp) { *dkp = NULL; } -static void -get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { - isc_result_t result; - isc_stdtime_t publish, active, revoke, inactive, deltime; - bool pubset = false, actset = false; - bool revset = false, inactset = false; - bool delset = false; +void +dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { + isc_stdtime_t publish = 0, active = 0, revoke = 0, remove = 0; REQUIRE(key != NULL && key->key != NULL); - result = dst_key_gettime(key->key, DST_TIME_PUBLISH, &publish); - if (result == ISC_R_SUCCESS) - pubset = true; + key->hint_publish = dst_key_is_published(key->key, now, &publish); + key->hint_sign = dst_key_is_signing(key->key, DST_BOOL_ZSK, now, + &active); + key->hint_revoke = dst_key_is_revoked(key->key, now, &revoke); + key->hint_remove = dst_key_is_removed(key->key, now, &remove); - result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); - if (result == ISC_R_SUCCESS) - actset = true; - - result = dst_key_gettime(key->key, DST_TIME_REVOKE, &revoke); - if (result == ISC_R_SUCCESS) - revset = true; - - result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &inactive); - if (result == ISC_R_SUCCESS) - inactset = true; - - result = dst_key_gettime(key->key, DST_TIME_DELETE, &deltime); - if (result == ISC_R_SUCCESS) - delset = true; - - /* Metadata says publish (but possibly not activate) */ - if (pubset && publish <= now) + /* + * Activation date is set (maybe in the future), but publication date + * isn't. Most likely the user wants to publish now and activate later. + * Most likely because this is true for most rollovers, except for: + * 1. The unpopular ZSK Double-RRSIG method. + * 2. When introducing a new algorithm. + * These two cases are rare enough that we will set hint_publish + * anyway when hint_sign is set, because BIND 9 natively does not + * support the ZSK Double-RRSIG method, and when introducing a new + * algorihtm, we strive to publish its signatures and DNSKEY records + * at the same time. + */ + if (key->hint_sign && publish == 0) { key->hint_publish = true; - - /* Metadata says activate (so we must also publish) */ - if (actset && active <= now) { - key->hint_sign = true; - - /* Only publish if publish time has already passed. */ - if (pubset && publish <= now) - key->hint_publish = true; } /* - * Activation date is set (maybe in the future), but - * publication date isn't. Most likely the user wants to - * publish now and activate later. + * If activation date is in the future, make note of how far off. */ - if (actset && !pubset) - key->hint_publish = true; - - /* - * If activation date is in the future, make note of how far off - */ - if (key->hint_publish && actset && active > now) { + if (key->hint_publish && active > now) { key->prepublish = active - now; } /* - * Key has been marked inactive: we can continue publishing, - * but don't sign. - */ - if (key->hint_publish && inactset && inactive <= now) { - key->hint_sign = false; - } - - /* - * Metadata says revoke. If the key is published, - * we *have to* sign with it per RFC5011--even if it was - * not active before. + * Metadata says revoke. If the key is published, we *have to* sign + * with it per RFC5011 -- even if it was not active before. * * If it hasn't already been done, we should also revoke it now. */ - if (key->hint_publish && (revset && revoke <= now)) { + if (key->hint_publish && key->hint_revoke) { uint32_t flags; key->hint_sign = true; flags = dst_key_flags(key->key); @@ -1337,17 +1347,17 @@ get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { } /* - * Metadata says delete, so don't publish this key or sign with it. + * Metadata says delete, so don't publish this key or sign with it + * (note that signatures of a removed key may still be reused). */ - if (delset && deltime <= now) { + if (key->hint_remove) { key->hint_publish = false; key->hint_sign = false; - key->hint_remove = true; } } /*% - * Get a list of DNSSEC keys from the key repository + * Get a list of DNSSEC keys from the key repository. */ isc_result_t dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, @@ -1416,10 +1426,10 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, continue; dstkey = NULL; - result = dst_key_fromnamedfile(dir.entry.name, - directory, + result = dst_key_fromnamedfile(dir.entry.name, directory, DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE, + DST_TYPE_PRIVATE | + DST_TYPE_STATE, mctx, &dstkey); switch (alg) { @@ -1447,7 +1457,7 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, RETERR(dns_dnsseckey_create(mctx, &dstkey, &key)); key->source = dns_keysource_repository; - get_hints(key, now); + dns_dnssec_get_hints(key, now); if (key->legacy) { dns_dnsseckey_destroy(mctx, &key); @@ -1638,7 +1648,8 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, result = dst_key_fromfile(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, + (DST_TYPE_PUBLIC|DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &privkey); /* @@ -1655,8 +1666,9 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, result = dst_key_fromfile(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC| - DST_TYPE_PRIVATE, + (DST_TYPE_PUBLIC| + DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &privkey); if (result == ISC_R_SUCCESS && @@ -1680,7 +1692,8 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, dst_key_id(pubkey), dst_key_alg(pubkey), (DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE), + DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &buf); if (result2 != ISC_R_SUCCESS) { @@ -1800,7 +1813,7 @@ delrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin, static isc_result_t publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin, - dns_ttl_t ttl, isc_mem_t *mctx, bool allzsk, + dns_ttl_t ttl, isc_mem_t *mctx, void (*report)(const char *, ...)) { isc_result_t result; @@ -1813,7 +1826,7 @@ publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin, dst_key_format(key->key, keystr, sizeof(keystr)); report("Fetching %s (%s) from key %s.\n", - keystr, key->ksk ? (allzsk ? "KSK/ZSK" : "KSK") : "ZSK", + keystr, key->ksk ? (key->zsk ? "CSK" : "KSK") : "ZSK", key->source == dns_keysource_user ? "file" : "repository"); if (key->prepublish && ttl > key->prepublish) { @@ -2023,8 +2036,7 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, isc_result_t dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, dns_dnsseckeylist_t *removed, const dns_name_t *origin, - dns_ttl_t hint_ttl, dns_diff_t *diff, - bool allzsk, isc_mem_t *mctx, + dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx, void (*report)(const char *, ...)) { isc_result_t result; @@ -2047,8 +2059,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, if (key->source == dns_keysource_user && (key->hint_publish || key->force_publish)) { - RETERR(publish_key(diff, key, origin, ttl, - mctx, allzsk, report)); + RETERR(publish_key(diff, key, origin, ttl, mctx, + report)); } if (key->source == dns_keysource_zoneapex) { ttl = dst_key_getttl(key->key); @@ -2125,14 +2137,14 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, (key1->hint_publish || key1->force_publish)) { RETERR(publish_key(diff, key1, origin, ttl, - mctx, allzsk, report)); + mctx, report)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now published", keystr1, key1->ksk ? - (allzsk ? "KSK/ZSK" : "KSK") : + (key1->zsk ? "CSK" : "KSK") : "ZSK"); if (key1->hint_sign || key1->force_sign) { key1->first_sign = true; @@ -2143,7 +2155,7 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, "DNSKEY %s (%s) is now " "active", keystr1, key1->ksk ? - (allzsk ? "KSK/ZSK" : + (key1->zsk ? "CSK" : "KSK") : "ZSK"); } } @@ -2154,6 +2166,9 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, /* Printable version of key2 (the old key, if any) */ dst_key_format(key2->key, keystr2, sizeof(keystr2)); + /* Copy key metadata. */ + dst_key_copy_metadata(key2->key, key1->key); + /* Match found: remove or update it as needed */ if (key1->hint_remove) { RETERR(remove_key(diff, key2, origin, ttl, mctx, @@ -2167,8 +2182,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now deleted", - keystr2, key2->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK"); + keystr2, key2->ksk ? (key2->zsk ? + "CSK" : "KSK") : "ZSK"); } else { dns_dnsseckey_destroy(mctx, &key2); } @@ -2192,15 +2207,15 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, ISC_LOG_INFO, "DNSKEY %s (%s) is now revoked; " "new ID is %05d", - keystr2, key2->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK", + keystr2, key2->ksk ? (key2->zsk ? + "CSK" : "KSK") : "ZSK", dst_key_id(key1->key)); } else { dns_dnsseckey_destroy(mctx, &key2); } - RETERR(publish_key(diff, key1, origin, ttl, - mctx, allzsk, report)); + RETERR(publish_key(diff, key1, origin, ttl, mctx, + report)); ISC_LIST_UNLINK(*newkeys, key1, link); ISC_LIST_APPEND(*keys, key1, link); @@ -2224,8 +2239,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now active", - keystr1, key1->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK"); + keystr1, key1->ksk ? (key1->zsk ? + "CSK" : "KSK") : "ZSK"); } else if (key2->is_active && !key1->hint_sign && !key1->force_sign) { @@ -2234,8 +2249,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now inactive", - keystr1, key1->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK"); + keystr1, key1->ksk ? (key1->zsk ? + "CSK" : "KSK") : "ZSK"); } key2->hint_sign = key1->hint_sign; diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index e874639f8d..06e9c611ad 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -64,6 +64,88 @@ #define DST_AS_STR(t) ((t).value.as_textregion.base) +#define NEXTTOKEN(lex, opt, token) { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } + +#define NEXTTOKEN_OR_EOF(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } while ((*token).type == isc_tokentype_eol); \ + +#define READLINE(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } while ((*token).type != isc_tokentype_eol) + +#define BADTOKEN() { \ + ret = ISC_R_UNEXPECTEDTOKEN; \ + goto cleanup; \ + } + +#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) +static const char *numerictags[NUMERIC_NTAGS] = { + "Predecessor:", + "Successor:", + "MaxTTL:", + "RollPeriod:", + "Lifetime:" +}; + +#define BOOLEAN_NTAGS (DST_MAX_BOOLEAN + 1) +static const char *booleantags[BOOLEAN_NTAGS] = { + "KSK:", + "ZSK:" +}; + +#define TIMING_NTAGS (DST_MAX_TIMES + 1) +static const char *timingtags[TIMING_NTAGS] = { + "Generated:", + "Published:", + "Active:", + "Revoked:", + "Retired:", + "Removed:", + + "DSPublish:", + "SyncPublish:", + "SyncDelete:", + + "DNSKEYChange:", + "ZRRSIGChange:", + "KRRSIGChange:", + "DSChange:" +}; + +#define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1) +static const char *keystatestags[KEYSTATES_NTAGS] = { + "DNSKEYState:", + "ZRRSIGState:", + "KRRSIGState:", + "DSState:", + "GoalState:" +}; + +#define KEYSTATES_NVALUES 4 +static const char *keystates[KEYSTATES_NVALUES] = { + "hidden", "rumoured", "omnipresent", "unretentive", +}; + +#define STATE_ALGORITHM_STR "Algorithm:" +#define STATE_LENGTH_STR "Length:" +#define MAX_NTAGS (DST_MAX_NUMERIC + DST_MAX_BOOLEAN + \ + DST_MAX_TIMES + DST_MAX_KEYSTATES) + static dst_func_t *dst_t_func[DST_MAX_ALGS]; static bool dst_initialized = false; @@ -83,6 +165,8 @@ static dst_key_t * get_key_struct(const dns_name_t *name, isc_mem_t *mctx); static isc_result_t write_public_key(const dst_key_t *key, int type, const char *directory); +static isc_result_t write_key_state(const dst_key_t *key, int type, + const char *directory); static isc_result_t buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, @@ -372,24 +456,35 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory) { REQUIRE(dst_initialized == true); REQUIRE(VALID_KEY(key)); - REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + REQUIRE((type & + (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0); CHECKALG(key->key_alg); - if (key->func->tofile == NULL) + if (key->func->tofile == NULL) { return (DST_R_UNSUPPORTEDALG); + } if ((type & DST_TYPE_PUBLIC) != 0) { ret = write_public_key(key, type, directory); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (ret); + } + } + + if ((type & DST_TYPE_STATE) != 0) { + ret = write_key_state(key, type, directory); + if (ret != ISC_R_SUCCESS) { + return (ret); + } } if (((type & DST_TYPE_PRIVATE) != 0) && (key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY) + { return (key->func->tofile(key, directory)); - else - return (ret); + } + return (ISC_R_SUCCESS); } void @@ -411,7 +506,8 @@ dst_key_getfilename(dns_name_t *name, dns_keytag_t id, REQUIRE(dst_initialized == true); REQUIRE(dns_name_isabsolute(name)); - REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + REQUIRE((type & + (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0); REQUIRE(mctx != NULL); REQUIRE(buf != NULL); @@ -534,8 +630,9 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, } key = get_key_struct(pubkey->key_name, pubkey->key_alg, - pubkey->key_flags, pubkey->key_proto, 0, - pubkey->key_class, pubkey->key_ttl, mctx); + pubkey->key_flags, pubkey->key_proto, + pubkey->key_size, pubkey->key_class, + pubkey->key_ttl, mctx); if (key == NULL) { dst_key_free(&pubkey); return (ISC_R_NOMEMORY); @@ -544,6 +641,31 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, if (key->func->parse == NULL) RETERR(DST_R_UNSUPPORTEDALG); + /* + * Read the state file, if requested by type. + */ + if ((type & DST_TYPE_STATE) != 0) { + + newfilenamelen = strlen(filename) + 7; + if (dirname != NULL) { + newfilenamelen += strlen(dirname) + 1; + } + newfilename = isc_mem_get(mctx, newfilenamelen); + result = addsuffix(newfilename, newfilenamelen, + dirname, filename, ".state"); + INSIST(result == ISC_R_SUCCESS); + + result = dst_key_read_state(newfilename, mctx, &key); + if (result == ISC_R_FILENOTFOUND) { + /* Having no state is valid. */ + result = ISC_R_SUCCESS; + } + + isc_mem_put(mctx, newfilename, newfilenamelen); + newfilename = NULL; + RETERR(result); + } + newfilenamelen = strlen(filename) + 9; if (dirname != NULL) newfilenamelen += strlen(dirname) + 1; @@ -884,14 +1006,45 @@ dst_key_generate(const dns_name_t *name, unsigned int alg, return (ISC_R_SUCCESS); } +isc_result_t +dst_key_getbool(const dst_key_t *key, int type, bool *valuep) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_BOOLEAN); + if (!key->boolset[type]) { + return (ISC_R_NOTFOUND); + } + *valuep = key->bools[type]; + return (ISC_R_SUCCESS); +} + +void +dst_key_setbool(dst_key_t *key, int type, bool value) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_BOOLEAN); + key->bools[type] = value; + key->boolset[type] = true; +} + +void +dst_key_unsetbool(dst_key_t *key, int type) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_BOOLEAN); + key->boolset[type] = false; +} + isc_result_t dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep) { REQUIRE(VALID_KEY(key)); REQUIRE(valuep != NULL); REQUIRE(type <= DST_MAX_NUMERIC); - if (!key->numset[type]) + if (!key->numset[type]) { return (ISC_R_NOTFOUND); + } *valuep = key->nums[type]; return (ISC_R_SUCCESS); } @@ -918,8 +1071,9 @@ dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep) { REQUIRE(VALID_KEY(key)); REQUIRE(timep != NULL); REQUIRE(type <= DST_MAX_TIMES); - if (!key->timeset[type]) + if (!key->timeset[type]) { return (ISC_R_NOTFOUND); + } *timep = key->times[type]; return (ISC_R_SUCCESS); } @@ -939,6 +1093,36 @@ dst_key_unsettime(dst_key_t *key, int type) { key->timeset[type] = false; } +isc_result_t +dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(statep != NULL); + REQUIRE(type <= DST_MAX_KEYSTATES); + if (!key->keystateset[type]) { + return (ISC_R_NOTFOUND); + } + *statep = key->keystates[type]; + return (ISC_R_SUCCESS); +} + +void +dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_KEYSTATES); + key->keystates[type] = state; + key->keystateset[type] = true; +} + +void +dst_key_unsetstate(dst_key_t *key, int type) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_KEYSTATES); + key->keystateset[type] = false; +} + isc_result_t dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp) { REQUIRE(VALID_KEY(key)); @@ -1116,7 +1300,7 @@ dst_key_buildfilename(const dst_key_t *key, int type, REQUIRE(VALID_KEY(key)); REQUIRE(type == DST_TYPE_PRIVATE || type == DST_TYPE_PUBLIC || - type == 0); + type == DST_TYPE_STATE || type == 0); return (buildfilename(key->key_name, key->key_id, key->key_alg, type, directory, out)); @@ -1321,7 +1505,7 @@ dst_key_setinactive(dst_key_t *key, bool inactive) { } /*% - * Reads a public key from disk + * Reads a public key from disk. */ isc_result_t dst_key_read_public(const char *filename, int type, @@ -1363,17 +1547,6 @@ dst_key_read_public(const char *filename, int type, if (ret != ISC_R_SUCCESS) goto cleanup; -#define NEXTTOKEN(lex, opt, token) { \ - ret = isc_lex_gettoken(lex, opt, token); \ - if (ret != ISC_R_SUCCESS) \ - goto cleanup; \ - } - -#define BADTOKEN() { \ - ret = ISC_R_UNEXPECTEDTOKEN; \ - goto cleanup; \ - } - /* Read the domain name */ NEXTTOKEN(lex, opt, &token); if (token.type != isc_tokentype_string) @@ -1446,6 +1619,216 @@ dst_key_read_public(const char *filename, int type, return (ret); } +static int +find_metadata(const char *s, const char *tags[], int ntags) { + for (int i = 0; i < ntags; i++) { + if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) + return (i); + } + return (-1); +} + +static int +find_numericdata(const char *s) { + return (find_metadata(s, numerictags, NUMERIC_NTAGS)); +} + +static int +find_booleandata(const char *s) { + return (find_metadata(s, booleantags, BOOLEAN_NTAGS)); +} + +static int +find_timingdata(const char *s) { + return (find_metadata(s, timingtags, TIMING_NTAGS)); +} + +static int +find_keystatedata(const char *s) { + return (find_metadata(s, keystatestags, KEYSTATES_NTAGS)); +} + +static isc_result_t +keystate_fromtext(const char *s, dst_key_state_t *state) { + for (int i = 0; i < KEYSTATES_NVALUES; i++) { + if (keystates[i] != NULL && strcasecmp(s, keystates[i]) == 0) { + *state = (dst_key_state_t) i; + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} + +/*% + * Reads a key state from disk. + */ +isc_result_t +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp) +{ + isc_lex_t *lex = NULL; + isc_token_t token; + isc_result_t ret; + unsigned int opt = ISC_LEXOPT_EOL; + + ret = isc_lex_create(mctx, 1500, &lex); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE); + + ret = isc_lex_openfile(lex, filename); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Read the comment line. + */ + READLINE(lex, opt, &token); + + /* + * Read the algorithm line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), STATE_ALGORITHM_STR) != 0) + { + BADTOKEN(); + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long) dst_key_alg(*keyp)) + { + BADTOKEN(); + } + + READLINE(lex, opt, &token); + + /* + * Read the length line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), STATE_LENGTH_STR) != 0) + { + BADTOKEN(); + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long) dst_key_size(*keyp)) + { + BADTOKEN(); + } + + READLINE(lex, opt, &token); + + /* + * Read the metadata. + */ + for (int n = 0; n < MAX_NTAGS; n++) { + int tag; + + NEXTTOKEN_OR_EOF(lex, opt, &token); + if (ret == ISC_R_EOF) { + break; + } + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + /* Numeric metadata */ + tag = find_numericdata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < NUMERIC_NTAGS); + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number) { + BADTOKEN(); + } + + dst_key_setnum(*keyp, tag, token.value.as_ulong); + goto next; + } + + /* Boolean metadata */ + tag = find_booleandata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < BOOLEAN_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + if (strcmp(DST_AS_STR(token), "yes") == 0) { + dst_key_setbool(*keyp, tag, true); + } else if (strcmp(DST_AS_STR(token), "no") == 0) { + dst_key_setbool(*keyp, tag, false); + } else { + BADTOKEN(); + } + goto next; + } + + /* Timing metadata */ + tag = find_timingdata(DST_AS_STR(token)); + if (tag >= 0) { + uint32_t when; + + INSIST(tag < TIMING_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = dns_time32_fromtext(DST_AS_STR(token), &when); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + dst_key_settime(*keyp, tag, when); + goto next; + } + + /* Keystate metadata */ + tag = find_keystatedata(DST_AS_STR(token)); + if (tag >= 0) { + dst_key_state_t state; + + INSIST(tag < KEYSTATES_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = keystate_fromtext(DST_AS_STR(token), &state); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + dst_key_setstate(*keyp, tag, state); + goto next; + } + +next: + READLINE(lex, opt, &token); + + } + + /* Done, successfully parsed the whole file. */ + ret = ISC_R_SUCCESS; + +cleanup: + if (lex != NULL) { + isc_lex_destroy(&lex); + } + return (ret); +} + static bool issymmetric(const dst_key_t *key) { REQUIRE(dst_initialized == true); @@ -1476,6 +1859,36 @@ issymmetric(const dst_key_t *key) { } } +/*% + * Write key boolean metadata to a file pointer, preceded by 'tag' + */ +static void +printbool(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + bool value = 0; + + result = dst_key_getbool(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %s\n", tag, value ? "yes" : "no"); +} + +/*% + * Write key numeric metadata to a file pointer, preceded by 'tag' + */ +static void +printnum(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + uint32_t value = 0; + + result = dst_key_getnum(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %u\n", tag, value); +} + /*% * Write key timing metadata to a file pointer, preceded by 'tag' */ @@ -1517,6 +1930,106 @@ printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) { fprintf(stream, "%s: (set, unable to display)\n", tag); } +/*% + * Write key state metadata to a file pointer, preceded by 'tag' + */ +static void +printstate(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + dst_key_state_t value = 0; + + result = dst_key_getstate(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %s\n", tag, keystates[value]); +} + +/*% + * Writes a key state to disk. + */ +static isc_result_t +write_key_state(const dst_key_t *key, int type, const char *directory) { + FILE *fp; + isc_buffer_t fileb; + char filename[NAME_MAX]; + isc_result_t ret; + isc_fsaccess_t access; + + REQUIRE(VALID_KEY(key)); + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + ret = dst_key_buildfilename(key, DST_TYPE_STATE, directory, &fileb); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + /* + * Create public key file. + */ + if ((fp = fopen(filename, "w")) == NULL) { + return (DST_R_WRITEERROR); + } + + if (issymmetric(key)) { + access = 0; + isc_fsaccess_add(ISC_FSACCESS_OWNER, + ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, + &access); + (void)isc_fsaccess_set(filename, access); + } + + /* Write key state */ + if ((type & DST_TYPE_KEY) == 0) { + fprintf(fp, "; This is the state of key %d, for ", + key->key_id); + ret = dns_name_print(key->key_name, fp); + if (ret != ISC_R_SUCCESS) { + fclose(fp); + return (ret); + } + fputc('\n', fp); + + fprintf(fp, "Algorithm: %u\n", key->key_alg); + fprintf(fp, "Length: %u\n", key->key_size); + + printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); + printnum(key, DST_NUM_PREDECESSOR, "Predecessor", fp); + printnum(key, DST_NUM_SUCCESSOR, "Successor", fp); + + printbool(key, DST_BOOL_KSK, "KSK", fp); + printbool(key, DST_BOOL_ZSK, "ZSK", fp); + + printtime(key, DST_TIME_CREATED, "Generated", fp); + printtime(key, DST_TIME_PUBLISH, "Published", fp); + printtime(key, DST_TIME_ACTIVATE, "Active", fp); + printtime(key, DST_TIME_INACTIVE, "Retired", fp); + printtime(key, DST_TIME_REVOKE, "Revoked", fp); + printtime(key, DST_TIME_DELETE, "Removed", fp); + + printtime(key, DST_TIME_DNSKEY, "DNSKEYChange", fp); + printtime(key, DST_TIME_ZRRSIG, "ZRRSIGChange", fp); + printtime(key, DST_TIME_KRRSIG, "KRRSIGChange", fp); + printtime(key, DST_TIME_DS, "DSChange", fp); + + printstate(key, DST_KEY_DNSKEY, "DNSKEYState", fp); + printstate(key, DST_KEY_ZRRSIG, "ZRRSIGState", fp); + printstate(key, DST_KEY_KRRSIG, "KRRSIGState", fp); + printstate(key, DST_KEY_DS, "DSState", fp); + printstate(key, DST_KEY_GOAL, "GoalState", fp); + } + + fflush(fp); + if (ferror(fp)) + ret = DST_R_WRITEERROR; + fclose(fp); + + return (ret); +} + /*% * Writes a public key to disk in DNS format. */ @@ -1540,33 +2053,38 @@ write_public_key(const dst_key_t *key, int type, const char *directory) { isc_buffer_init(&classb, class_array, sizeof(class_array)); ret = dst_key_todns(key, &keyb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (ret); + } isc_buffer_usedregion(&keyb, &r); dns_rdata_fromregion(&rdata, key->key_class, dns_rdatatype_dnskey, &r); ret = dns_rdata_totext(&rdata, (dns_name_t *) NULL, &textb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (DST_R_INVALIDPUBLICKEY); + } ret = dns_rdataclass_totext(key->key_class, &classb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (DST_R_INVALIDPUBLICKEY); + } /* * Make the filename. */ isc_buffer_init(&fileb, filename, sizeof(filename)); ret = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &fileb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (ret); + } /* * Create public key file. */ - if ((fp = fopen(filename, "w")) == NULL) + if ((fp = fopen(filename, "w")) == NULL) { return (DST_R_WRITEERROR); + } if (issymmetric(key)) { access = 0; @@ -1641,10 +2159,14 @@ buildfilename(dns_name_t *name, dns_keytag_t id, isc_result_t result; REQUIRE(out != NULL); - if ((type & DST_TYPE_PRIVATE) != 0) + if ((type & DST_TYPE_PRIVATE) != 0) { suffix = ".private"; - else if (type == DST_TYPE_PUBLIC) + } else if ((type & DST_TYPE_PUBLIC) != 0) { suffix = ".key"; + } else if ((type & DST_TYPE_STATE) != 0) { + suffix = ".state"; + } + if (directory != NULL) { if (isc_buffer_availablelength(out) < strlen(directory)) return (ISC_R_NOSPACE); @@ -1761,3 +2283,356 @@ dst_key_tkeytoken(const dst_key_t *key) { REQUIRE(VALID_KEY(key)); return (key->key_tkeytoken); } + +/* + * A key is considered unused if it does not have any timing metadata set + * other than "Created". + * + */ +bool +dst_key_is_unused(dst_key_t* key) +{ + isc_stdtime_t val; + dst_key_state_t st; + int state_type; + bool state_type_set; + + REQUIRE(VALID_KEY(key)); + + /* + * None of the key timing metadata, except Created, may be set. Key + * state times may be set only if their respective state is HIDDEN. + */ + for (int i = 0; i < DST_MAX_TIMES+1; i++) { + state_type_set = false; + + switch (i) { + case DST_TIME_CREATED: + break; + case DST_TIME_DNSKEY: + state_type = DST_KEY_DNSKEY; + state_type_set = true; + break; + case DST_TIME_ZRRSIG: + state_type = DST_KEY_ZRRSIG; + state_type_set = true; + break; + case DST_TIME_KRRSIG: + state_type = DST_KEY_KRRSIG; + state_type_set = true; + break; + case DST_TIME_DS: + state_type = DST_KEY_DS; + state_type_set = true; + break; + default: + break; + } + + /* Created is fine. */ + if (i == DST_TIME_CREATED) { + continue; + } + /* No such timing metadata found, that is fine too. */ + if (dst_key_gettime(key, i, &val) == ISC_R_NOTFOUND) { + continue; + } + /* + * Found timing metadata and it is not related to key states. + * This key is used. + */ + if (!state_type_set) { + return false; + } + /* + * If the state is not HIDDEN, the key is in use. + * If the state is not set, this is odd and we default to NA. + */ + if (dst_key_getstate(key, state_type, &st) != ISC_R_SUCCESS) { + st = DST_KEY_STATE_NA; + } + if (st != DST_KEY_STATE_HIDDEN) { + return false; + } + } + /* This key is unused. */ + return true; +} + + +static void +get_ksk_zsk(dst_key_t *key, bool *ksk, bool *zsk) +{ + bool k = false, z = false; + + if (dst_key_getbool(key, DST_BOOL_KSK, &k) == ISC_R_SUCCESS) { + *ksk = k; + } else { + *ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0); + } + if (dst_key_getbool(key, DST_BOOL_ZSK, &z) == ISC_R_SUCCESS) { + *zsk = z; + } else { + *zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0); + } +} + +/* Hints on key whether it can be published and/or used for signing. */ + +bool +dst_key_is_published(dst_key_t *key, isc_stdtime_t now, + isc_stdtime_t *publish) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when; + bool state_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_PUBLISH, &when); + if (result == ISC_R_SUCCESS) { + *publish = when; + time_ok = (when <= now); + } + + /* Check key states: + * If the DNSKEY state is RUMOURED or OMNIPRESENT, it means it + * should be published. + */ + result = dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (result == ISC_R_SUCCESS) { + state_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + } + + return state_ok && time_ok; +} + +bool +dst_key_is_active(dst_key_t *key, isc_stdtime_t now) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool ksk = false, zsk = false, inactive = false; + bool ds_ok = true, zrrsig_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &when); + if (result == ISC_R_SUCCESS) { + inactive = (when <= now); + } + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when); + if (result == ISC_R_SUCCESS) { + time_ok = (when <= now); + } + + get_ksk_zsk(key, &ksk, &zsk); + + /* Check key states: + * KSK: If the DS is RUMOURED or OMNIPRESENT the key is considered + * active. + */ + if (ksk) { + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + ds_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + /* + * ZSK: If the ZRRSIG state is RUMOURED or OMNIPRESENT, it means the + * key is active. + */ + if (zsk) { + result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); + if (result == ISC_R_SUCCESS) { + zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + return ds_ok && zrrsig_ok && time_ok && !inactive; +} + + +bool +dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now, isc_stdtime_t *active) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool ksk = false, zsk = false, inactive = false; + bool krrsig_ok = true, zrrsig_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &when); + if (result == ISC_R_SUCCESS) { + inactive = (when <= now); + } + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when); + if (result == ISC_R_SUCCESS) { + *active = when; + time_ok = (when <= now); + } + + get_ksk_zsk(key, &ksk, &zsk); + + /* Check key states: + * If the RRSIG state is RUMOURED or OMNIPRESENT, it means the key + * is active. + */ + if (ksk && role == DST_BOOL_KSK) { + result = dst_key_getstate(key, DST_KEY_KRRSIG, &state); + if (result == ISC_R_SUCCESS) { + krrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } else if (zsk && role == DST_BOOL_ZSK) { + result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); + if (result == ISC_R_SUCCESS) { + zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + return krrsig_ok && zrrsig_ok && time_ok && !inactive; +} + +bool +dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke) +{ + isc_result_t result; + isc_stdtime_t when = 0; + bool time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_REVOKE, &when); + if (result == ISC_R_SUCCESS) { + *revoke = when; + time_ok = (when <= now); + } + + return time_ok; +} + +bool +dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool state_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + if (dst_key_is_unused(key)) { + /* This key was never used. */ + return false; + } + + result = dst_key_gettime(key, DST_TIME_DELETE, &when); + if (result == ISC_R_SUCCESS) { + *remove = when; + time_ok = (when <= now); + } + + /* Check key states: + * If the DNSKEY state is UNRETENTIVE or HIDDEN, it means the key + * should not be published. + */ + result = dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (result == ISC_R_SUCCESS) { + state_ok = ((state == DST_KEY_STATE_UNRETENTIVE) || + (state == DST_KEY_STATE_HIDDEN)); + /* + * Key states trump timing metadata. + * Ignore delete time. + */ + time_ok = true; + } + + return state_ok && time_ok; +} + +void +dst_key_copy_metadata(dst_key_t *to, dst_key_t *from) +{ + dst_key_state_t state; + isc_stdtime_t when; + uint32_t num; + bool yesno; + isc_result_t result; + + REQUIRE(VALID_KEY(to)); + REQUIRE(VALID_KEY(from)); + + for (int i = 0; i < DST_MAX_TIMES+1; i++) { + result = dst_key_gettime(from, i, &when); + if (result == ISC_R_SUCCESS) { + dst_key_settime(to, i, when); + } else { + dst_key_unsettime(to, i); + } + } + + for (int i = 0; i < DST_MAX_NUMERIC+1; i++) { + result = dst_key_getnum(from, i, &num); + if (result == ISC_R_SUCCESS) { + dst_key_setnum(to, i, num); + } else { + dst_key_unsetnum(to, i); + } + } + + for (int i = 0; i < DST_MAX_BOOLEAN+1; i++) { + result = dst_key_getbool(from, i, &yesno); + if (result == ISC_R_SUCCESS) { + dst_key_setbool(to, i, yesno); + } else { + dst_key_unsetnum(to, i); + } + } + + for (int i = 0; i < DST_MAX_KEYSTATES+1; i++) { + result = dst_key_getstate(from, i, &state); + if (result == ISC_R_SUCCESS) { + dst_key_setstate(to, i, state); + } else { + dst_key_unsetstate(to, i); + } + } +} diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index 3657733d49..0d8be243d8 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -107,11 +107,18 @@ struct dst_key { } keydata; /*%< pointer to key in crypto pkg fmt */ isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */ - bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ - isc_stdtime_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ - bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ - bool inactive; /*%< private key not present as it is - inactive */ + bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ + + uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ + bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ + + bool bools[DST_MAX_BOOLEAN + 1]; /*%< boolean metadata */ + bool boolset[DST_MAX_BOOLEAN + 1]; /*%< data set? */ + + dst_key_state_t keystates[DST_MAX_KEYSTATES + 1]; /*%< key states */ + bool keystateset[DST_MAX_KEYSTATES + 1]; /*%< data set? */ + + bool inactive; /*%< private key not present as it is inactive */ bool external; /*%< external key */ int fmt_major; /*%< private key format, major version */ diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c index 4b83617a21..e989be5301 100644 --- a/lib/dns/dst_parse.c +++ b/lib/dns/dst_parse.c @@ -61,7 +61,11 @@ static const char *timetags[TIMING_NTAGS] = { "Delete:", "DSPublish:", "SyncPublish:", - "SyncDelete:" + "SyncDelete:", + NULL, + NULL, + NULL, + NULL }; #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) @@ -69,7 +73,8 @@ static const char *numerictags[NUMERIC_NTAGS] = { "Predecessor:", "Successor:", "MaxTTL:", - "RollPeriod:" + "RollPeriod:", + NULL }; struct parse_map { @@ -734,7 +739,9 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, result = dst_key_getnum(key, i, &value); if (result != ISC_R_SUCCESS) continue; - fprintf(fp, "%s %u\n", numerictags[i], value); + if (numerictags[i] != NULL) { + fprintf(fp, "%s %u\n", numerictags[i], value); + } } for (i = 0; i < TIMING_NTAGS; i++) { result = dst_key_gettime(key, i, &when); @@ -750,8 +757,10 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, isc_buffer_usedregion(&b, &r); - fprintf(fp, "%s %.*s\n", timetags[i], (int)r.length, - r.base); + if (timetags[i] != NULL) { + fprintf(fp, "%s %.*s\n", timetags[i], + (int)r.length, r.base); + } } } diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h index a03b3e3af5..767e5e9b23 100644 --- a/lib/dns/include/dns/dnssec.h +++ b/lib/dns/include/dns/dnssec.h @@ -53,12 +53,14 @@ struct dns_dnsseckey { bool force_publish; /*% publish regardless of metadata */ bool hint_sign; /*% metadata says to sign with this key */ bool force_sign; /*% sign with key regardless of metadata */ + bool hint_revoke; /*% metadata says revoke key */ bool hint_remove; /*% metadata says *don't* publish */ bool is_active; /*% key is already active */ bool first_sign; /*% key is newly becoming active */ unsigned int prepublish; /*% how long until active? */ dns_keysource_t source; /*% how the key was found */ bool ksk; /*% this is a key-signing key */ + bool zsk; /*% this is a zone-signing key */ bool legacy; /*% this is old-style key with no metadata (possibly generated by an older version of BIND9) and @@ -267,6 +269,16 @@ dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp); *\li '*dkp' is NULL. */ +void +dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now); +/*%< + * Get hints on DNSSEC key whether this key can be published + * and/or is active. Timing metadata is compared to 'now'. + * + * Requires: + *\li 'key' is a pointer to a DNSSEC key and is not NULL. + */ + isc_result_t dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, isc_stdtime_t now, isc_mem_t *mctx, @@ -312,8 +324,8 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, isc_result_t dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, dns_dnsseckeylist_t *removed, const dns_name_t *origin, - dns_ttl_t hint_ttl, dns_diff_t *diff, bool allzsk, - isc_mem_t *mctx, void (*report)(const char *, ...)); + dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx, + void (*report)(const char *, ...)); /*%< * Update the list of keys in 'keys' with new key information in 'newkeys'. * @@ -328,9 +340,6 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, * copy the key into that list; otherwise destroy it. * - Otherwise, make sure keys has current metadata. * - * If 'allzsk' is true, we are allowing KSK-flagged keys to be used as - * ZSKs. - * * 'hint_ttl' is the TTL to use for the DNSKEY RRset if there is no * existing RRset, and if none of the keys to be added has a default TTL * (in which case we would use the shortest one). If the TTL is longer diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h new file mode 100644 index 0000000000..396ef5cade --- /dev/null +++ b/lib/dns/include/dns/kasp.h @@ -0,0 +1,635 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef DNS_KASP_H +#define DNS_KASP_H 1 + +/***** + ***** Module Info + *****/ + +/*! \file dns/kasp.h + * \brief + * DNSSEC Key and Signing Policy (KASP) + * + * A "kasp" is a DNSSEC policy, that determines how a zone should be + * signed and maintained. + */ + +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/* Stores a KASP key */ +struct dns_kasp_key { + isc_mem_t* mctx; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_kasp_key) link; + + /* Configuration */ + uint32_t lifetime; + uint32_t algorithm; + int length; + uint8_t role; +}; + +/* Stores a DNSSEC policy */ +struct dns_kasp { + unsigned int magic; + isc_mem_t* mctx; + char* name; + + /* Internals. */ + isc_mutex_t lock; + bool frozen; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_kasp) link; + + /* Configuration: signatures */ + uint32_t signatures_refresh; + uint32_t signatures_validity; + uint32_t signatures_validity_dnskey; + + /* Configuration: Keys */ + dns_kasp_keylist_t keys; + dns_ttl_t dnskey_ttl; + + /* Configuration: Timings */ + uint32_t publish_safety; + uint32_t retire_safety; + + /* Zone settings */ + dns_ttl_t zone_max_ttl; + uint32_t zone_propagation_delay; + + /* Parent settings */ + dns_ttl_t parent_ds_ttl; + uint32_t parent_propagation_delay; + uint32_t parent_registration_delay; + + // TODO: The rest of the KASP configuration +}; + +#define DNS_KASP_MAGIC ISC_MAGIC('K','A','S','P') +#define DNS_KASP_VALID(kasp) ISC_MAGIC_VALID(kasp, DNS_KASP_MAGIC) + +/* Defaults */ +#define DNS_KASP_SIG_REFRESH (86400*5) +#define DNS_KASP_SIG_VALIDITY (86400*14) +#define DNS_KASP_SIG_VALIDITY_DNSKEY (86400*14) +#define DNS_KASP_KEY_TTL (3600) +#define DNS_KASP_DS_TTL (3600) +#define DNS_KASP_PUBLISH_SAFETY (300) +#define DNS_KASP_RETIRE_SAFETY (300) +#define DNS_KASP_ZONE_MAXTTL (86400) +#define DNS_KASP_ZONE_PROPDELAY (300) +#define DNS_KASP_PARENT_PROPDELAY (3600) +#define DNS_KASP_PARENT_REGDELAY (86400) + +/* Key roles */ +#define DNS_KASP_KEY_ROLE_KSK 0x01 +#define DNS_KASP_KEY_ROLE_ZSK 0x02 + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char* name, dns_kasp_t **kaspp); +/*%< + * Create a KASP. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'name' is a valid C string. + * + *\li kaspp != NULL && *kaspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen kasp. + * + *\li 'targetp' points to a NULL dns_kasp_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + *\li While *targetp is attached, the kasp will not shut down. + */ + +void +dns_kasp_detach(dns_kasp_t **kaspp); +/*%< + * Detach KASP. + * + * Requires: + * + *\li 'kaspp' points to a valid dns_kasp_t * + * + * Ensures: + * + *\li *kaspp is NULL. + */ + +void +dns_kasp_freeze(dns_kasp_t *kasp); +/*%< + * Freeze kasp. No changes can be made to kasp configuration while frozen. + * + * Requires: + * + *\li 'kasp' is a valid, unfrozen kasp. + * + * Ensures: + * + *\li 'kasp' is frozen. + */ + +void +dns_kasp_thaw(dns_kasp_t *kasp); +/*%< + * Thaw kasp. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Ensures: + * + *\li 'kasp' is no longer frozen. + */ + +const char* +dns_kasp_getname(dns_kasp_t *kasp); +/*%< + * Get kasp name. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + * Returns: + * + *\li name of 'kasp'. + */ + +uint32_t +dns_kasp_signdelay(dns_kasp_t *kasp); +/*%< + * Get the delay that is needed to ensure that all existing RRsets have been + * re-signed with a successor key. This is the signature validity minus the + * signature refresh time (that indicates how far before signature expiry an + * RRSIG should be refreshed). + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature refresh interval. + */ + +uint32_t +dns_kasp_sigrefresh(dns_kasp_t *kasp); +/*%< + * Get signature refresh interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature refresh interval. + */ + +void +dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature refresh interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_sigvalidity(dns_kasp_t *kasp); +uint32_t +dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp); +/*%< + * Get signature validity. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature validity. + */ + +void +dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value); +void +dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature validity. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +dns_ttl_t +dns_kasp_dnskeyttl(dns_kasp_t *kasp); +/*%< + * Get DNSKEY TTL. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li DNSKEY TTL. + */ + +void +dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set DNSKEY TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_publishsafety(dns_kasp_t *kasp); +/*%< + * Get publish safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Publish safety interval. + */ + +void +dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set publish safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_retiresafety(dns_kasp_t *kasp); +/*%< + * Get retire safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Retire safety interval. + */ + +void +dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set retire safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +dns_ttl_t +dns_kasp_zonemaxttl(dns_kasp_t *kasp); +/*%< + * Get maximum zone TTL. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Maximum zone TTL. + */ + +void +dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set maximum zone TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_zonepropagationdelay(dns_kasp_t *kasp); +/*%< + * Get zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Zone propagation delay. + */ + +void +dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +dns_ttl_t +dns_kasp_dsttl(dns_kasp_t *kasp); +/*%< + * Get DS TTL (should match that of the parent DS record). + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Expected parent DS TTL. + */ + +void +dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set DS TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); +/*%< + * Get parent zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Parent zone propagation delay. + */ + +void +dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set parent propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_parentregistrationdelay(dns_kasp_t *kasp); +/*%< + * Get parent registration delay for submitting new DS. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Parent registration delay. + */ + +void +dns_kasp_setparentregistrationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set parent registration delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +isc_result_t +dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); +/*%< + * Search for a kasp with name 'name' in 'list'. + * If found, '*kaspp' is (strongly) attached to it. + * + * Requires: + * + *\li 'kaspp' points to a NULL dns_kasp_t *. + * + * Returns: + * + *\li #ISC_R_SUCCESS A matching kasp was found. + *\li #ISC_R_NOTFOUND No matching kasp was found. + */ + +dns_kasp_keylist_t +dns_kasp_keys(dns_kasp_t *kasp); +/*%< + * Get the list of kasp keys. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +bool +dns_kasp_keylist_empty(dns_kasp_t *kasp); +/*%< + * Check if the keylist is empty. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + * Returns: + * + *\li true if the keylist is empty, false otherwise. + */ + +void +dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key); +/*%< + * Add a key. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + *\li 'key' is not NULL. + */ + +isc_result_t +dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp); +/*%< + * Create a key inside a KASP. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + *\li keyp != NULL && *keyp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_kasp_key_destroy(dns_kasp_key_t* key); +/*%< + * Destroy a KASP key. + * + * Requires: + * + *\li key != NULL + */ + +uint32_t +dns_kasp_key_algorithm(dns_kasp_key_t *key); +/*%< + * Get the key algorithm. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Key algorithm. + */ + +unsigned int +dns_kasp_key_size(dns_kasp_key_t *key); +/*%< + * Get the key size. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Configured key size, or default key size for key algorithm if no size + * configured. + */ + +uint32_t +dns_kasp_key_lifetime(dns_kasp_key_t *key); +/*%< + * The lifetime of this key (how long may this key be active?) + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Lifetime of key. + * + */ + +bool +dns_kasp_key_ksk(dns_kasp_key_t *key); +/*%< + * Does this key act as a KSK? + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li True, if the key role has DNS_KASP_KEY_ROLE_KSK set. + *\li False, otherwise. + * + */ + +bool +dns_kasp_key_zsk(dns_kasp_key_t *key); +/*%< + * Does this key act as a ZSK? + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li True, if the key role has DNS_KASP_KEY_ROLE_ZSK set. + *\li False, otherwise. + * + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_KASP_H */ diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h new file mode 100644 index 0000000000..a97438bf3d --- /dev/null +++ b/lib/dns/include/dns/keymgr.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#ifndef DNS_KEYMGR_H +#define DNS_KEYMGR_H 1 + +/*! \file dns/keymgr.h */ + +#include +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, + const char *directory, isc_mem_t *mctx, + dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, + isc_stdtime_t now, isc_stdtime_t *nexttime); +/*%< + * Manage keys in 'keylist' and update timing data according to 'kasp' policy. + * Create new keys for 'origin' if necessary in 'directory'. Append all such + * keys, along with use hints gleaned from their metadata, onto 'keylist'. + * + * Update key states and store changes back to disk. Store when to run next + * in 'nexttime'. + * + * Requires: + *\li 'origin' is a valid FQDN. + *\li 'mctx' is a valid memory context. + *\li 'keyring' is not NULL. + *\li 'kasp' is not NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li any error returned by dst_key_generate(), isc_dir_open(), + * dst_key_to_file(), or dns_dnsseckey_create(). + * + * Ensures: + *\li On error, keypool is unchanged + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_KEYMGR_H */ diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 329ee7d277..728fb0c0a1 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -91,6 +91,10 @@ typedef struct dns_fwdtable dns_fwdtable_t; typedef struct dns_geoip_databases dns_geoip_databases_t; typedef struct dns_iptable dns_iptable_t; typedef uint32_t dns_iterations_t; +typedef struct dns_kasp dns_kasp_t; +typedef ISC_LIST(dns_kasp_t) dns_kasplist_t; +typedef struct dns_kasp_key dns_kasp_key_t; +typedef ISC_LIST(dns_kasp_key_t) dns_kasp_keylist_t; typedef uint16_t dns_keyflags_t; typedef struct dns_keynode dns_keynode_t; typedef ISC_LIST(dns_keynode_t) dns_keynodelist_t; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 0ad20356db..a80301f917 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -665,6 +665,24 @@ dns_zone_unload(dns_zone_t *zone); *\li 'zone' to be a valid zone. */ +dns_kasp_t* +dns_zone_getkasp(dns_zone_t *zone); +/*%< + * Returns the current kasp. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t* kasp); +/*%< + * Set kasp for zone. If a kasp is already set, it will be detached. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + void dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value); diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 3146d88cb9..afa2068623 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -44,6 +44,38 @@ ISC_LANG_BEGINDECLS typedef struct dst_key dst_key_t; typedef struct dst_context dst_context_t; +/*% + * Key states for the DNSSEC records related to a key: DNSKEY, RRSIG (ksk), + * RRSIG (zsk), and DS. + * + * DST_KEY_STATE_HIDDEN: Records of this type are not published in zone. + * This may be because the key parts were never + * introduced in the zone, or because the key has + * retired and has no records of this type left in + * the zone. + * DST_KEY_STATE_RUMOURED: Records of this type are published in zone, but + * not long enough to ensure all resolvers know + * about it. + * DST_KEY_STATE_OMNIPRESENT: Records of this type are published in zone long + * enough so that all resolvers that know about + * these records, no longer have outdated data. + * DST_KEY_STATE_UNRETENTIVE: Records of this type have been removed from the + * zone, but there may be resolvers that still have + * have predecessor records cached. Note that RRSIG + * records in this state may actually still be in the + * zone because they are reused, but retired RRSIG + * records will never be refreshed: A successor key + * is used to create signatures. + * DST_KEY_STATE_NA: The state is not applicable for this record type. + */ +typedef enum dst_key_state { + DST_KEY_STATE_HIDDEN = 0, + DST_KEY_STATE_RUMOURED = 1, + DST_KEY_STATE_OMNIPRESENT = 2, + DST_KEY_STATE_UNRETENTIVE = 3, + DST_KEY_STATE_NA = 4 +} dst_key_state_t; + /* DST algorithm codes */ #define DST_ALG_UNKNOWN 0 #define DST_ALG_RSA 1 /* Used for parsing RSASHA1, RSASHA256 and RSASHA512 */ @@ -85,6 +117,7 @@ typedef struct dst_context dst_context_t; #define DST_TYPE_KEY 0x1000000 /* KEY key */ #define DST_TYPE_PRIVATE 0x2000000 #define DST_TYPE_PUBLIC 0x4000000 +#define DST_TYPE_STATE 0x8000000 /* Key timing metadata definitions */ #define DST_TIME_CREATED 0 @@ -96,14 +129,32 @@ typedef struct dst_context dst_context_t; #define DST_TIME_DSPUBLISH 6 #define DST_TIME_SYNCPUBLISH 7 #define DST_TIME_SYNCDELETE 8 -#define DST_MAX_TIMES 8 +#define DST_TIME_DNSKEY 9 +#define DST_TIME_ZRRSIG 10 +#define DST_TIME_KRRSIG 11 +#define DST_TIME_DS 12 +#define DST_MAX_TIMES 12 /* Numeric metadata definitions */ #define DST_NUM_PREDECESSOR 0 #define DST_NUM_SUCCESSOR 1 #define DST_NUM_MAXTTL 2 #define DST_NUM_ROLLPERIOD 3 -#define DST_MAX_NUMERIC 3 +#define DST_NUM_LIFETIME 4 +#define DST_MAX_NUMERIC 4 + +/* Boolean metadata definitions */ +#define DST_BOOL_KSK 0 +#define DST_BOOL_ZSK 1 +#define DST_MAX_BOOLEAN 1 + +/* Key state metadata definitions */ +#define DST_KEY_DNSKEY 0 +#define DST_KEY_ZRRSIG 1 +#define DST_KEY_KRRSIG 2 +#define DST_KEY_DS 3 +#define DST_KEY_GOAL 4 +#define DST_MAX_KEYSTATES 4 /* * Current format version number of the private key parser. @@ -310,16 +361,17 @@ dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type, const char *directory, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key from permanent storage. The key can either be a public or - * private key, and is specified by name, algorithm, and id. If a private key - * is specified, the public key must also be present. If directory is NULL, - * the current directory is assumed. + * private key, or a key state. It specified by name, algorithm, and id. If + * a private key or key state is specified, the public key must also be + * present. If directory is NULL, the current directory is assumed. * * Requires: * \li "name" is a valid absolute dns name. * \li "id" is a valid key tag identifier. * \li "alg" is a supported key algorithm. - * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. - * DST_TYPE_KEY look for a KEY record otherwise DNSKEY + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * DST_TYPE_STATE to also read the key state. * \li "mctx" is a valid memory context. * \li "keyp" is not NULL and "*keyp" is NULL. * @@ -336,8 +388,8 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, int type, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key from permanent storage. The key can either be a public or - * key, and is specified by filename. If a private key is specified, the - * public key must also be present. + * private key, or a key stae. It is specified by filename. If a private key + * or key state is specified, the public key must also be present. * * If 'dirname' is not NULL, and 'filename' is a relative path, * then the file is looked up relative to the given directory. @@ -345,8 +397,9 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, * * Requires: * \li "filename" is not NULL - * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union - * DST_TYPE_KEY look for a KEY record otherwise DNSKEY + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * DST_TYPE_STATE to also read the key state. * \li "mctx" is a valid memory context * \li "keyp" is not NULL and "*keyp" is NULL. * @@ -366,9 +419,9 @@ dst_key_read_public(const char *filename, int type, * Reads a public key from permanent storage. The key must be a public key. * * Requires: - * \li "filename" is not NULL - * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY - * \li "mctx" is a valid memory context + * \li "filename" is not NULL. + * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * \li "mctx" is a valid memory context. * \li "keyp" is not NULL and "*keyp" is NULL. * * Returns: @@ -381,6 +434,22 @@ dst_key_read_public(const char *filename, int type, * \li If successful, *keyp will contain a valid key. */ +isc_result_t +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp); +/*%< + * Reads a key state from permanent storage. + * + * Requires: + * \li "filename" is not NULL. + * \li "mctx" is a valid memory context. + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key + * \li any other result indicates failure + */ + isc_result_t dst_key_tofile(const dst_key_t *key, int type, const char *directory); /*%< @@ -811,6 +880,37 @@ dst_key_setflags(dst_key_t *key, uint32_t flags); * "key" is a valid key. */ +isc_result_t +dst_key_getbool(const dst_key_t *key, int type, bool *valuep); +/*%< + * Get a member of the boolean metadata array and place it in '*valuep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + * "valuep" is not null. + */ + +void +dst_key_setbool(dst_key_t *key, int type, bool value); +/*%< + * Set a member of the boolean metadata array. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + */ + +void +dst_key_unsetbool(dst_key_t *key, int type); +/*%< + * Flag a member of the boolean metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + */ + isc_result_t dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep); /*%< @@ -819,7 +919,7 @@ dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep); * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_NUMERIC - * "timep" is not null. + * "valuep" is not null. */ void @@ -873,6 +973,38 @@ dst_key_unsettime(dst_key_t *key, int type); * "type" is no larger than DST_MAX_TIMES */ +isc_result_t +dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep); +/*%< + * Get a member of the keystate metadata array and place it in '*statep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_KEYSTATES + * "statep" is not null. + */ + +void +dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state); +/*%< + * Set a member of the keystate metadata array. + * + * Requires: + * "key" is a valid key. + * "state" is a valid state. + * "type" is no larger than DST_MAX_KEYSTATES + */ + +void +dst_key_unsetstate(dst_key_t *key, int type); +/*%< + * Flag a member of the keystate metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_KEYSTATES + */ + isc_result_t dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp); /*%< @@ -961,9 +1093,88 @@ dst_key_setinactive(dst_key_t *key, bool inactive); void dst_key_setexternal(dst_key_t *key, bool value); +/*%< + * Set key external state. + * + * Requires: + * 'key' to be valid. + */ bool dst_key_isexternal(dst_key_t *key); +/*%< + * Check if this is an external key. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_unused(dst_key_t *key); +/*%< + * Check if this key is unused. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_published(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *publish); +/*%< + * Check if it is safe to publish this key (e.g. put the DNSKEY in the zone). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_active(dst_key_t *key, isc_stdtime_t now); +/*%< + * Check if this key is active. This means that it is creating RRSIG records + * (ZSK), or that it is used to create a chain of trust (KSK), or both (CSK). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now, + isc_stdtime_t *active); +/*%< + * Check if it is safe to use this key for signing, given the role. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke); +/*%< + * Check if this key is revoked. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove); +/*%< + * Check if this key is removed from the zone (e.g. the DNSKEY record should + * no longer be in the zone). + * + * Requires: + * 'key' to be valid. + */ + +void +dst_key_copy_metadata(dst_key_t *to, dst_key_t *from); +/*%< + * Copy key metadata from one key to another. + * + * Requires: + * 'to' and 'from' to be valid. + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c new file mode 100644 index 0000000000..1784b46be0 --- /dev/null +++ b/lib/dns/kasp.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) +{ + dns_kasp_t *kasp; + + REQUIRE(name != NULL); + REQUIRE(kaspp != NULL && *kaspp == NULL); + + kasp = isc_mem_get(mctx, sizeof(*kasp)); + kasp->mctx = NULL; + isc_mem_attach(mctx, &kasp->mctx); + + kasp->name = isc_mem_strdup(mctx, name); + isc_mutex_init(&kasp->lock); + kasp->frozen = false; + + isc_refcount_init(&kasp->references, 1); + + ISC_LINK_INIT(kasp, link); + + kasp->signatures_refresh = DNS_KASP_SIG_REFRESH; + kasp->signatures_validity = DNS_KASP_SIG_VALIDITY; + kasp->signatures_validity_dnskey = DNS_KASP_SIG_VALIDITY_DNSKEY; + + ISC_LIST_INIT(kasp->keys); + + kasp->dnskey_ttl = DNS_KASP_KEY_TTL; + kasp->publish_safety = DNS_KASP_PUBLISH_SAFETY; + kasp->retire_safety = DNS_KASP_RETIRE_SAFETY; + + kasp->zone_max_ttl = DNS_KASP_ZONE_MAXTTL; + kasp->zone_propagation_delay = DNS_KASP_ZONE_PROPDELAY; + + kasp->parent_ds_ttl = DNS_KASP_DS_TTL; + kasp->parent_propagation_delay = DNS_KASP_PARENT_PROPDELAY; + kasp->parent_registration_delay = DNS_KASP_PARENT_REGDELAY; + + // TODO: The rest of the KASP configuration + + kasp->magic = DNS_KASP_MAGIC; + *kaspp = kasp; + + return (ISC_R_SUCCESS); +} + +void +dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp) { + REQUIRE(DNS_KASP_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + *targetp = source; +} + +static inline void +destroy(dns_kasp_t *kasp) { + dns_kasp_key_t *key; + dns_kasp_key_t *key_next; + + ISC_INSIST(!ISC_LINK_LINKED(kasp, link)); + + for (key = ISC_LIST_HEAD(kasp->keys); key != NULL; key = key_next) { + key_next = ISC_LIST_NEXT(key, link); + ISC_LIST_UNLINK(kasp->keys, key, link); + dns_kasp_key_destroy(key); + } + ISC_INSIST(ISC_LIST_EMPTY(kasp->keys)); + + isc_mem_free(kasp->mctx, kasp->name); + isc_mem_putanddetach(&kasp->mctx, kasp, sizeof(*kasp)); +} + +void +dns_kasp_detach(dns_kasp_t **kaspp) { + REQUIRE(kaspp != NULL && DNS_KASP_VALID(*kaspp)); + dns_kasp_t *kasp = *kaspp; + *kaspp = NULL; + + if (isc_refcount_decrement(&kasp->references) == 1) { + destroy(kasp); + } +} + +const char* +dns_kasp_getname(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + return (kasp->name); +} + +void +dns_kasp_freeze(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->frozen = true; +} + +void +dns_kasp_thaw(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + kasp->frozen = false; +} + +uint32_t +dns_kasp_signdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->signatures_validity - kasp->signatures_refresh); +} + +uint32_t +dns_kasp_sigrefresh(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->signatures_refresh); +} + +void +dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->signatures_refresh = value; +} + +uint32_t +dns_kasp_sigvalidity(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->signatures_validity); +} + +void +dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->signatures_validity = value; +} + +uint32_t +dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->signatures_validity_dnskey); +} + +void +dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->signatures_validity = value; +} + +dns_ttl_t +dns_kasp_dnskeyttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->dnskey_ttl); +} + +void +dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->dnskey_ttl = ttl; +} + +uint32_t +dns_kasp_publishsafety(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->publish_safety); +} + +void +dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->publish_safety = value; +} + +uint32_t +dns_kasp_retiresafety(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->retire_safety); +} + +void +dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->retire_safety = value; +} + +dns_ttl_t +dns_kasp_zonemaxttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->zone_max_ttl); +} + +void +dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->zone_max_ttl = ttl; +} + +uint32_t +dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->zone_propagation_delay); +} + +void +dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->zone_propagation_delay = value; +} + +dns_ttl_t +dns_kasp_dsttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->parent_ds_ttl); +} + +void +dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->parent_ds_ttl = ttl; +} + +uint32_t +dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->parent_propagation_delay); +} + +void +dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->parent_propagation_delay = value; +} + +uint32_t +dns_kasp_parentregistrationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->parent_registration_delay); +} + +void +dns_kasp_setparentregistrationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->parent_registration_delay = value; +} + +isc_result_t +dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) +{ + dns_kasp_t *kasp = NULL; + + REQUIRE(kaspp != NULL && *kaspp == NULL); + + if (list == NULL) { + return (ISC_R_NOTFOUND); + } + INSIST(list != NULL); + + for (kasp = ISC_LIST_HEAD(*list); kasp != NULL; + kasp = ISC_LIST_NEXT(kasp, link)) + { + if (strcmp(kasp->name, name) == 0) { + break; + } + } + if (kasp == NULL) { + return (ISC_R_NOTFOUND); + } + dns_kasp_attach(kasp, kaspp); + return (ISC_R_SUCCESS); +} + +dns_kasp_keylist_t +dns_kasp_keys(dns_kasp_t *kasp) +{ + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->keys); +} + +bool +dns_kasp_keylist_empty(dns_kasp_t *kasp) +{ + REQUIRE(DNS_KASP_VALID(kasp)); + return (ISC_LIST_EMPTY(kasp->keys)); +} + +void +dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) +{ + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + REQUIRE(key != NULL); + + ISC_LIST_APPEND(kasp->keys, key, link); +} + +isc_result_t +dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) +{ + dns_kasp_key_t *key; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyp != NULL && *keyp == NULL); + + key = isc_mem_get(kasp->mctx, sizeof(*key)); + key->mctx = NULL; + isc_mem_attach(kasp->mctx, &key->mctx); + + ISC_LINK_INIT(key, link); + + key->lifetime = 0; + key->algorithm = 0; + key->length = -1; + key->role = 0; + *keyp = key; + return (ISC_R_SUCCESS); +} + +void +dns_kasp_key_destroy(dns_kasp_key_t* key) +{ + REQUIRE(key != NULL); + isc_mem_putanddetach(&key->mctx, key, sizeof(*key)); +} + +uint32_t +dns_kasp_key_algorithm(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->algorithm); +} + +unsigned int +dns_kasp_key_size(dns_kasp_key_t *key) { + unsigned int size = 0; + unsigned int min = 0; + + REQUIRE(key != NULL); + + switch (key->algorithm) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + case DNS_KEYALG_RSASHA512: + min = DNS_KEYALG_RSASHA512 ? 1024 : 512; + if (key->length > -1) { + size = (unsigned int) key->length; + if (size < min) { + size = min; + } + if (size > 4096) { + size = 4096; + } + } else if (key->role & DNS_KASP_KEY_ROLE_KSK) { + size = 2048; + } else { + size = 1024; + } + break; + case DNS_KEYALG_ECDSA256: + size = 256; + break; + case DNS_KEYALG_ECDSA384: + size = 384; + break; + case DNS_KEYALG_ED25519: + size = 32; + break; + case DNS_KEYALG_ED448: + size = 57; + break; + default: + /* unsupported */ + break; + } + return (size); +} + +uint32_t +dns_kasp_key_lifetime(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->lifetime); +} + +bool +dns_kasp_key_ksk(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->role & DNS_KASP_KEY_ROLE_KSK); +} + +bool +dns_kasp_key_zsk(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->role & DNS_KASP_KEY_ROLE_ZSK); +} diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c new file mode 100644 index 0000000000..8c1441cacd --- /dev/null +++ b/lib/dns/keymgr.c @@ -0,0 +1,1573 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define RETERR(x) do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/* + * Set key state to HIDDEN and change last changed to now, + * only if key state has not been set before. + */ +#define INITIALIZE_STATE(key, state, time) \ + do { \ + dst_key_state_t s; \ + if (dst_key_getstate((key), (state), &s) == \ + ISC_R_NOTFOUND) \ + { \ + isc_stdtime_t t; \ + dst_key_gettime((key), DST_TIME_CREATED, &t); \ + dst_key_setstate((key), (state), HIDDEN); \ + dst_key_settime((key), (time), t); \ + } \ + } while (0) + +/* Shorter keywords for better readability. */ +#define HIDDEN DST_KEY_STATE_HIDDEN +#define RUMOURED DST_KEY_STATE_RUMOURED +#define OMNIPRESENT DST_KEY_STATE_OMNIPRESENT +#define UNRETENTIVE DST_KEY_STATE_UNRETENTIVE +#define NA DST_KEY_STATE_NA + +/* Quickly get key state timing metadata. */ +#define NUM_KEYSTATES (DST_MAX_KEYSTATES) +static int keystatetimes[NUM_KEYSTATES] = { + DST_TIME_DNSKEY, DST_TIME_ZRRSIG, DST_TIME_KRRSIG, DST_TIME_DS +}; +/* Readable key state types and values. */ +static const char* keystatetags[NUM_KEYSTATES] = { + "DNSKEY", "ZRRSIG", "KRRSIG", "DS" +}; +static const char* keystatestrings[4] = { + "HIDDEN", "RUMOURED", "OMNIPRESENT", "UNRETENTIVE" +}; + +/* + * Print key role. + * + */ +static const char* +keymgr_keyrole(dst_key_t* key) +{ + bool ksk, zsk; + dst_key_getbool(key, DST_BOOL_KSK, &ksk); + dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ksk && zsk) { + return ("CSK"); + } else if (ksk) { + return ("KSK"); + } else { + return ("ZSK"); + } + return ("NOSIGN"); +} + +/* + * Calculate prepublication time of a successor key of 'key'. + * This function can have side effects: + * If the lifetime is not set, it will be set now. + * If there should be a retire time and it is not set, it will be set now. + * If there is no active time set, which would be super weird, set it now. + * + * This returns when the successor key needs to be published in the zone. + * A special value of 0 means there is no need for a successor. + * + */ +static isc_stdtime_t +keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t* kasp, + uint32_t lifetime, isc_stdtime_t now) +{ + isc_result_t ret; + isc_stdtime_t active, retire, prepub; + bool ksk = false; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + active = 0; + retire = 0; + prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) + + dns_kasp_zonepropagationdelay(kasp); + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + /* Add registration delay to the prepublication time. */ + prepub += dns_kasp_parentregistrationdelay(kasp); + } + + ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (ret != ISC_R_SUCCESS) { + uint32_t klifetime = 0; + /* + * An active key must have an activate timing metadata. + */ + ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); + if (ret != ISC_R_SUCCESS) { + /* Super weird, but if it happens, set it to now. */ + dst_key_settime(key->key, DST_TIME_ACTIVATE, now); + active = now; + } + + ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime); + if (ret != ISC_R_SUCCESS) { + dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime); + klifetime = lifetime; + } + if (klifetime == 0) { + /* + * No inactive time and no lifetime, + * so no need to start a rollover. + */ + return (0); + } + + retire = active + klifetime; + dst_key_settime(key->key, DST_TIME_INACTIVE, retire); + } + + /* + * Publish successor 'prepub' time before the 'retire' time of 'key'. + */ + return (retire - prepub); +} + + +static void +keymgr_key_retire(dns_dnsseckey_t *key, isc_stdtime_t now) +{ + char keystr[DST_KEY_FORMATSIZE]; + dst_key_state_t s; + bool ksk, zsk; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + /* This key wants to retire and hide in a corner. */ + dst_key_settime(key->key, DST_TIME_INACTIVE, now); + dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN); + + /* This key may not have key states set yet. Pretend as if they are + * in the OMNIPRESENT state. + */ + if (dst_key_getstate(key->key, DST_KEY_DNSKEY, &s) != ISC_R_SUCCESS) { + dst_key_setstate(key->key, DST_KEY_DNSKEY, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_DNSKEY, now); + } + + (void) dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ksk) { + if (dst_key_getstate(key->key, DST_KEY_KRRSIG, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_KRRSIG, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_KRRSIG, now); + } + if (dst_key_getstate(key->key, DST_KEY_DS, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_DS, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_DS, now); + } + } + (void) dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (zsk) { + if (dst_key_getstate(key->key, DST_KEY_ZRRSIG, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_ZRRSIG, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_ZRRSIG, now); + } + } + + dst_key_format(key->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, "keymgr: retire DNSKEY %s (%s)", keystr, + keymgr_keyrole(key->key)); +} + +/* + * Check if a dnsseckey matches kasp key configuration. A dnsseckey matches + * if it has the same algorithm and size, and if it has the same role as the + * kasp key configuration. + * + */ +static bool +keymgr_dnsseckey_kaspkey_match(dns_dnsseckey_t *dkey, dns_kasp_key_t *kkey) +{ + dst_key_t *key; + isc_result_t ret; + bool role = false; + + REQUIRE(dkey != NULL); + REQUIRE(kkey != NULL); + + key = dkey->key; + + /* Matching algorithms? */ + if (dst_key_alg(key) != dns_kasp_key_algorithm(kkey)) { + return (false); + } + /* Matching length? */ + if (dst_key_size(key) != dns_kasp_key_size(kkey)) { + return (false); + } + /* Matching role? */ + ret = dst_key_getbool(key, DST_BOOL_KSK, &role); + if (ret != ISC_R_SUCCESS || role != dns_kasp_key_ksk(kkey)) { + return (false); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &role); + if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(kkey)) { + return (false); + } + + /* Found a match. */ + return (true); +} + +/* + * Create a new key for 'origin' given the kasp key configuration 'kkey'. + * This will check for key id collisions with keys in 'keylist'. + * The created key will be stored in 'dst_key'. + * + */ +static isc_result_t +keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin, + dns_rdataclass_t rdclass, isc_mem_t *mctx, + dns_dnsseckeylist_t *keylist, dst_key_t **dst_key) +{ + bool conflict; + int keyflags = DNS_KEYOWNER_ZONE; + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *newkey = NULL; + + do { + uint16_t id; + uint32_t rid; + uint32_t algo = dns_kasp_key_algorithm(kkey); + int size = dns_kasp_key_size(kkey); + + conflict = false; + + if (dns_kasp_key_ksk(kkey)) { + keyflags |= DNS_KEYFLAG_KSK; + } + RETERR(dst_key_generate(origin, algo, size, 0, keyflags, + DNS_KEYPROTO_DNSSEC, rdclass, mctx, + &newkey, NULL)); + + /* Key collision? */ + id = dst_key_id(newkey); + rid = dst_key_rid(newkey); + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keylist); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (dst_key_alg(dkey->key) != algo) { + continue; + } + if (dst_key_id(dkey->key) == id || + dst_key_rid(dkey->key) == id || + dst_key_id(dkey->key) == rid || + dst_key_rid(dkey->key) == rid) + { + /* Try again. */ + conflict = true; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_WARNING, + "keymgr: key collision id %d", + dst_key_id(newkey)); + dst_key_free(&newkey); + } + } + } while (conflict == true); + + INSIST(!conflict); + dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey)); + dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey)); + dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey)); + *dst_key = newkey; + return (ISC_R_SUCCESS); + +failure: + return (result); +} + +/* + * Return the desired state for this record 'type'. The desired state depends + * on whether the key wants to be active, or wants to retire. This implements + * the edges of our state machine: + * + * ----> OMNIPRESENT ---- + * | | + * | \|/ + * + * RUMOURED <----> UNRETENTIVE + * + * /|\ | + * | | + * ---- HIDDEN <---- + * + * A key that wants to be active eventually wants to have its record types + * in the OMNIPRESENT state (that is, all resolvers that know about these + * type of records know about these records specifically). + * + * A key that wants to be retired eventually wants to have its record types + * in the HIDDEN state (that is, all resolvers that know about these type + * of records specifically don't know about these records). + * + */ +static dst_key_state_t +keymgr_desiredstate(dns_dnsseckey_t *key, dst_key_state_t state) +{ + dst_key_state_t goal; + + if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal) != ISC_R_SUCCESS) { + /* No goal? No movement. */ + return (state); + } + + if (goal == HIDDEN) { + switch (state) { + case RUMOURED: + case OMNIPRESENT: + return (UNRETENTIVE); + case HIDDEN: + case UNRETENTIVE: + return (HIDDEN); + default: + return (state); + } + } else if (goal == OMNIPRESENT) { + switch (state) { + case RUMOURED: + case OMNIPRESENT: + return (OMNIPRESENT); + case HIDDEN: + case UNRETENTIVE: + return (RUMOURED); + default: + return (state); + } + } + + /* Unknown goal. */ + return (state); +} + +/* + * Check if 'key' matches specific 'states'. + * A state in 'states' that is NA matches any state. + * A state in 'states' that is HIDDEN also matches if the state is not set. + * If 'next_state' is set (not NA), we are pretending as if record 'type' of + * 'subject' key already transitioned to the 'next state'. + * + */ +static bool +keymgr_key_match_state(dst_key_t *key, dst_key_t* subject, int type, + dst_key_state_t next_state, dst_key_state_t states[4]) +{ + REQUIRE(key != NULL); + + for (int i = 0; i < 4; i++) { + dst_key_state_t state; + if (states[i] == NA) { + continue; + } + if (next_state != NA && i == type && + dst_key_id(key) == dst_key_id(subject)) { + /* Check next state rather than current state. */ + state = next_state; + } else if (dst_key_getstate(key, i, &state) != ISC_R_SUCCESS) { + /* This is fine only if expected state is HIDDEN. */ + if (states[i] != HIDDEN) { + return (false); + } + continue; + } + if (state != states[i]) { + return (false); + } + } + /* Match. */ + return (true); +} + +/* + * Check if a 'k2' is a successor of 'k1'. This is a simplified version of + * Equation(2) of "Flexible and Robust Key Rollover" which defines a + * recursive relation. + * + */ +static bool +keymgr_key_is_successor(dst_key_t *k1, dst_key_t *k2) +{ + uint32_t suc = 0, pre = 0; + if (dst_key_getnum(k1, DST_NUM_SUCCESSOR, &suc) != ISC_R_SUCCESS) { + return (false); + } + if (dst_key_getnum(k2, DST_NUM_PREDECESSOR, &pre) != ISC_R_SUCCESS) { + return (false); + } + return (dst_key_id(k1) == pre && dst_key_id(k2) == suc); +} + +/* + * Check if a key exists in 'keyring' that matches 'states'. + * + * If 'match_algorithms', the key must also match the algorithm of 'key'. + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'check_successor', we also want to make sure there is a successor + * relationship with the found key that matches 'states2'. + */ +static bool +keymgr_key_exists_with_state(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + dst_key_state_t states[4], + dst_key_state_t states2[4], + bool check_successor, bool match_algorithms) +{ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (check_successor && + keymgr_key_match_state(dkey->key, key->key, type, + next_state, states2)) + { + /* Found a possible successor, look for predecessor. */ + for (dns_dnsseckey_t *pkey = ISC_LIST_HEAD(*keyring); + pkey != NULL; pkey = ISC_LIST_NEXT(pkey, link)) + { + if (pkey == dkey) { + continue; + } + if (!keymgr_key_match_state(pkey->key, + key->key, type, + next_state, states)) + { + continue; + } + + /* + * Found a possible predecessor, check + * relationship. + */ + if (keymgr_key_is_successor(pkey->key, + dkey->key)) + { + return (true); + } + } + } + + if (!check_successor && + keymgr_key_match_state(dkey->key, key->key, type, + next_state, states)) + { + return (true); + } + } + /* No match. */ + return (false); +} + +/* + * Check if a key has a successor. + */ +static bool +keymgr_key_has_successor(dns_dnsseckey_t *key, dns_dnsseckeylist_t *keyring) +{ + /* Don't worry about key states. */ + dst_key_state_t na[4] = { NA, NA, NA, NA }; + return (keymgr_key_exists_with_state(keyring, key, DST_KEY_DNSKEY, NA, + na, na, true, true)); +} + +/* + * Check if all keys have their DS hidden. If not, then there must be at + * least one key with an OMNIPRESENT DNSKEY. + * + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'match_algorithms', only consider keys with same algorithm of 'key'. + * + */ +static bool +keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + bool match_algorithms, bool must_be_hidden) +{ + dst_key_state_t dnskey_omnipresent[4] = + { OMNIPRESENT, NA, OMNIPRESENT, NA }; /* (3e) */ + dst_key_state_t ds_hidden[4] = { NA, NA, NA, HIDDEN }; /* (3e) */ + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(dkey->key, keystr, sizeof(keystr)); + + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, ds_hidden)) + { + /* This key has its DS hidden. */ + continue; + } + + if (must_be_hidden) { + return (false); + } + + /* + * This key does not have its DS hidden. There must be at + * least one key with the same algorithm that provides a + * chain of trust (can be this key). + */ + dnskey_omnipresent[DST_KEY_DS] = NA; + (void)dst_key_getstate(dkey->key, DST_KEY_DS, + &dnskey_omnipresent[DST_KEY_DS]); + if (!keymgr_key_exists_with_state(keyring, key, type, + next_state, + dnskey_omnipresent, na, + false, match_algorithms)) + { + /* There is no chain of trust. */ + return (false); + } + } + /* All good. */ + return (true); +} + +/* + * Check if all keys have their DNSKEY hidden. If not, then there must be at + * least one key with an OMNIPRESENT ZRRSIG. + * + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'match_algorithms', only consider keys with same algorithm of 'key'. + * + */ +static bool +keymgr_dnskey_hidden_or_chained(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + bool match_algorithms) +{ + dst_key_state_t rrsig_omnipresent[4] = + { NA, OMNIPRESENT, NA, NA }; /* (3i) */ + dst_key_state_t dnskey_hidden[4] = { HIDDEN, NA, NA, NA }; /* (3i) */ + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, dnskey_hidden)) + { + /* This key has its DNSKEY hidden. */ + continue; + } + + /* + * This key does not have its DNSKEY hidden. There must be at + * least one key with the same algorithm that has its RRSIG + * records OMNIPRESENT. + */ + rrsig_omnipresent[DST_KEY_DNSKEY] = NA; + (void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY, + &rrsig_omnipresent[DST_KEY_DNSKEY]); + if (!keymgr_key_exists_with_state(keyring, key, type, + next_state, + rrsig_omnipresent, na, + false, match_algorithms)) + { + /* There is no chain of trust. */ + return (false); + } + } + /* All good. */ + return (true); +} + +/* + * Check for existence of DS. + * + */ +static bool +keymgr_have_ds(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + dst_key_state_t states[2][4] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { NA, NA, NA, OMNIPRESENT }, /* DS present */ + { NA, NA, NA, RUMOURED } /* DS introducing */ + }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + /* + * Equation (3a): + * There is a key with the DS in either RUMOURD or OMNIPRESENT state. + */ + return (keymgr_key_exists_with_state(keyring, key, type, + next_state, states[0], na, + false, false) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[1], na, + false, false)); +} + +/* + * Check for existence of DNSKEY, or at least a good DNSKEY state. + * See equations what are good DNSKEY states. + * + */ +static bool +keymgr_have_dnskey(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + dst_key_state_t states[9][4] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }, /* (3b) */ + + { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }, /* (3c)p */ + { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }, /* (3c)s */ + + { UNRETENTIVE, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */ + { OMNIPRESENT, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */ + { UNRETENTIVE, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)p */ + { RUMOURED, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */ + { OMNIPRESENT, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */ + { RUMOURED, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)s */ + }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + return ( + /* + * Equation (3b): + * There is a key with the same algorithm with its DNSKEY, + * KRRSIG and DS records in OMNIPRESENT state. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[0], na, + false, true) || + /* + * Equation (3c): + * There are two or more keys with an OMNIPRESENT DNSKEY and + * the DS records get swapped. These keys must be in a + * successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[1], states[2], + true, true) || + /* + * Equation (3d): + * There are two or more keys with an OMNIPRESENT DS and + * the DNSKEY records and its KRRSIG records get swapped. + * These keys must be in a successor relation. Since the + * state for DNSKEY and KRRSIG move independently, we have + * to check all combinations for DNSKEY and KRRSIG in + * OMNIPRESENT/UNRETENTIVE state for the predecessor, and + * OMNIPRESENT/RUMOURED state for the successor. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[6], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[7], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[8], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[4], states[6], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[4], states[7], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[4], states[8], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[5], states[6], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[5], states[7], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[5], states[8], + true, true) || + /* + * Equation (3e): + * The key may be in any state as long as all keys have their + * DS HIDDEN, or when their DS is not HIDDEN, there must be a + * key with its DS in the same state and its DNSKEY omnipresent. + * In other words, if a DS record for the same algorithm is + * is still available to some validators, there must be a + * chain of trust for those validators. + */ + keymgr_ds_hidden_or_chained(keyring, key, type, + next_state, true, false) + ); +} + +/* + * Check for existence of RRSIG (zsk), or a good RRSIG state. + * See equations what are good RRSIG states. + * + */ +static bool +keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + dst_key_state_t states[11][4] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { OMNIPRESENT, OMNIPRESENT, NA, NA }, /* (3f) */ + { UNRETENTIVE, OMNIPRESENT, NA, NA }, /* (3g)p */ + { RUMOURED, OMNIPRESENT, NA, NA }, /* (3g)s */ + { OMNIPRESENT, UNRETENTIVE, NA, NA }, /* (3h)p */ + { OMNIPRESENT, RUMOURED, NA, NA }, /* (3h)s */ + }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + return ( + /* + * If all DS records are hidden than this rule can be ignored. + */ + keymgr_ds_hidden_or_chained(keyring, key, type, + next_state, true, true) || + /* + * Equation (3f): + * There is a key with the same algorithm with its DNSKEY and + * ZRRSIG records in OMNIPRESENT state. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[0], na, + false, true) || + /* + * Equation (3g): + * There are two or more keys with OMNIPRESENT ZRRSIG + * records and the DNSKEY records get swapped. These keys + * must be in a successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[1], states[2], + true, true) || + /* + * Equation (3h): + * There are two or more keys with an OMNIPRESENT DNSKEY + * and the ZRRSIG records get swapped. These keys must be in + * a successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[4], + true, true) || + /* + * Equation (3i): + * If no DNSKEYs are published, the state of the signatures is + * irrelevant. In case a DNSKEY is published however, there + * must be a path that can be validated from there. + */ + keymgr_dnskey_hidden_or_chained(keyring, key, type, + next_state, true) + ); +} + +/* + * Check if a transition in the state machine is allowed by the policy. + * This means when we do rollovers, we want to follow the rules of the + * 1. Pre-publish rollover method (in case of a ZSK) + * - First introduce the DNSKEY record. + * - Only if the DNSKEY record is OMNIPRESENT, introduce ZRRSIG records. + * + * 2. Double-KSK rollover method (in case of a KSK) + * - First introduce the DNSKEY record, as well as the KRRSIG records. + * - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS. + * + */ +static bool +keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next) +{ + dst_key_state_t dnskeystate = HIDDEN; + dst_key_state_t ksk_present[4] = + { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }; + dst_key_state_t ds_rumoured[4] = + { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }; + dst_key_state_t ds_retired[4] = + { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }; + dst_key_state_t ksk_rumoured[4] = { RUMOURED, NA, NA, OMNIPRESENT }; + dst_key_state_t ksk_retired[4] = { UNRETENTIVE, NA, NA, OMNIPRESENT }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + if (next != RUMOURED) { + /* + * Local policy only adds an extra barrier on transitions to + * the RUMOURED state. + */ + return (true); + } + + switch (type) { + case DST_KEY_DNSKEY: + /* No restrictions. */ + return (true); + case DST_KEY_ZRRSIG: + /* Make sure the DNSKEY record is OMNIPRESENT. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + if (dnskeystate == OMNIPRESENT) { + return (true); + } + /* + * Or are we introducing a new key for this algorithm? Because + * in that case allow publishing the RRSIG records before the + * DNSKEY. + */ + return (!( + keymgr_key_exists_with_state(keyring, key, type, + next, ksk_present, na, + false, true) || + keymgr_key_exists_with_state(keyring, key, type, + next, ds_retired, + ds_rumoured, true, + true) || + keymgr_key_exists_with_state(keyring, key, type, + next, ksk_retired, + ksk_rumoured, true, + true)) + ); + case DST_KEY_KRRSIG: + /* Only introduce if the DNSKEY is also introduced. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + return (dnskeystate != HIDDEN); + case DST_KEY_DS: + /* Make sure the DNSKEY record is OMNIPRESENT. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + return (dnskeystate == OMNIPRESENT); + default: + return (false); + } +} + +/* + * Check if a transition in the state machine is DNSSEC safe. + * This implements Equation(1) of "Flexible and Robust Key Rollover". + * + */ +static bool +keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + /* Debug logging. */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b; + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); + rule1a = keymgr_have_ds(keyring, key, type, NA); + rule1b = keymgr_have_ds(keyring, key, type, next_state); + rule2a = keymgr_have_dnskey(keyring, key, type, NA); + rule2b = keymgr_have_dnskey(keyring, key, type, next_state); + rule3a = keymgr_have_rrsig(keyring, key, type, NA); + rule3b = keymgr_have_rrsig(keyring, key, type, next_state); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: dnssec evaluation of %s %s record %s: " + "rule1=(~%s or %s) rule2=(~%s or %s) " + "rule3=(~%s or %s)", keymgr_keyrole(key->key), + keystr, keystatetags[type], + rule1a ? "true" : "false", + rule1b ? "true" : "false", + rule2a ? "true" : "false", + rule2b ? "true" : "false", + rule3a ? "true" : "false", + rule3b ? "true" : "false"); + } + + return ( + /* + * Rule 1: There must be a DS at all times. + * First check the current situation: if the rule check fails, + * we allow the transition to attempt to move us out of the + * invalid state. If the rule check passes, also check if + * the next state is also still a valid situation. + */ + (!keymgr_have_ds(keyring, key, type, NA) || + keymgr_have_ds(keyring, key, type, next_state)) && + /* + * Rule 2: There must be a DNSKEY at all times. Again, first + * check the current situation, then assess the next state. + */ + (!keymgr_have_dnskey(keyring, key, type, NA) || + keymgr_have_dnskey(keyring, key, type, next_state)) && + /* + * Rule 3: There must be RRSIG records at all times. Again, + * first check the current situation, then assess the next + * state. + */ + (!keymgr_have_rrsig(keyring, key, type, NA) || + keymgr_have_rrsig(keyring, key, type, next_state)) + ); +} + +/* + * Calculate the time when it is safe to do the next transition. + * + */ +static void +keymgr_transition_time(dns_dnsseckey_t* key, int type, + dst_key_state_t next_state, dns_kasp_t* kasp, + isc_stdtime_t now, isc_stdtime_t *when) +{ + isc_result_t ret; + isc_stdtime_t lastchange, nexttime = now; + + /* + * No need to wait if we move things into an uncertain state. + */ + if (next_state == RUMOURED || next_state == UNRETENTIVE) { + *when = now; + return; + } + + ret = dst_key_gettime(key->key, keystatetimes[type], &lastchange); + if (ret != ISC_R_SUCCESS) { + /* No last change, for safety purposes let's set it to now. */ + dst_key_settime(key->key, keystatetimes[type], now); + lastchange = now; + } + + switch(type) { + case DST_KEY_DNSKEY: + case DST_KEY_KRRSIG: + switch (next_state) { + case OMNIPRESENT: + /* + * RFC 7583: The publication interval (Ipub) is the + * amount of time that must elapse after the + * publication of a DNSKEY (plus RRSIG (KSK)) before + * it can be assumed that any resolvers that have the + * relevant RRset cached have a copy of the new + * information. This is the sum of the propagation + * delay (Dprp) and the DNSKEY TTL (TTLkey). This + * translates to zone-propagation-delay + dnskey-ttl. + * We will also add the publish-safety interval. + */ + nexttime = lastchange + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_publishsafety(kasp); + break; + case HIDDEN: + /* + * Same as OMNIPRESENT but without the publish-safety + * interval. + */ + nexttime = lastchange + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp); + break; + default: + nexttime = now; + break; + } + break; + case DST_KEY_ZRRSIG: + switch (next_state) { + case OMNIPRESENT: + case HIDDEN: + /* + * RFC 7583: The retire interval (Iret) is the amount + * of time that must elapse after a DNSKEY or + * associated data enters the retire state for any + * dependent information (RRSIG ZSK) to be purged from + * validating resolver caches. This is defined as: + * + * Iret = Dsgn + Dprp + TTLsig + * + * Where Dsgn is the Dsgn is the delay needed to + * ensure that all existing RRsets have been re-signed + * with the new key, Dprp is the propagation delay and + * TTLsig is the maximum TTL of all zone RRSIG + * records. This translates to: + * + * Dsgn + zone-propragation-delay + zone-max-ttl. + * + * We will also add the retire-safety interval. + */ + nexttime = lastchange + dns_kasp_signdelay(kasp) + + dns_kasp_zonemaxttl(kasp) + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + break; + default: + nexttime = now; + break; + } + break; + case DST_KEY_DS: + switch (next_state) { + case OMNIPRESENT: + case HIDDEN: + /* + * RFC 7583: The successor DS record is published in + * the parent zone and after the registration delay + * (Dreg), the time taken after the DS record has been + * submitted to the parent zone manager for it to be + * placed in the zone. Key N (the predecessor) must + * remain in the zone until any caches that contain a + * copy of the DS RRset have a copy containing the new + * DS record. This interval is the retire interval + * (Iret), given by: + * + * Iret = DprpP + TTLds + * + * So we need to wait Dreg + Iret before the DS becomes + * OMNIPRESENT. This translates to: + * + * parent-registration-delay + + * parent-propagation-delay + parent-ds-ttl. + * + * We will also add the retire-safety interval. + */ + nexttime = lastchange + dns_kasp_dsttl(kasp) + + dns_kasp_parentregistrationdelay(kasp) + + dns_kasp_parentpropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + break; + default: + nexttime = now; + break; + } + break; + default: + INSIST(0); + ISC_UNREACHABLE(); + break; + } + + *when = nexttime; +} + +/* + * Update keys. + * This implements Algorithm (1) of "Flexible and Robust Key Rollover". + * + */ +static isc_result_t +keymgr_update(dns_dnsseckeylist_t *keyring, dns_kasp_t* kasp, + isc_stdtime_t now, isc_stdtime_t *nexttime) +{ + bool changed; + + /* Repeat until nothing changed. */ +transition: + changed = false; + + /* For all keys in the zone. */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(dkey->key, keystr, sizeof(keystr)); + + /* For all records related to this key. */ + for (int i = 0; i < NUM_KEYSTATES; i++) { + isc_result_t ret; + isc_stdtime_t when; + dst_key_state_t state, next_state; + + ret = dst_key_getstate(dkey->key, i, &state); + if (ret == ISC_R_NOTFOUND) { + /* + * This record type is not applicable for this + * key, continue to the next record type. + */ + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: examine %s %s type %s " + "in state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state]); + + /* Get the desired next state. */ + next_state = keymgr_desiredstate(dkey, state); + if (state == next_state) { + /* + * This record is in a stable state. + * No change needed, continue with the next + * record type. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: %s %s type %s in " + "stable state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], + keystatestrings[state]); + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: can we transition %s %s type %s " + "state %s to state %s?", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + /* Is the transition allowed according to policy? */ + if (!keymgr_policy_approval(keyring, dkey, i, + next_state)) + { + /* No, please respect rollover methods. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: policy says no to %s %s type %s " + "state %s to state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + continue; + } + + /* Is the transition DNSSEC safe? */ + if (!keymgr_transition_allowed(keyring, dkey, i, + next_state)) + { + /* No, this would make the zone bogus. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: dnssec says no to %s %s type %s " + "state %s to state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + continue; + } + + /* Is it time to make the transition? */ + when = now; + keymgr_transition_time(dkey, i, next_state, kasp, now, + &when); + if (when > now) { + /* Not yet. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: time says no to %s %s type %s " + "state %s to state %s (wait %u seconds)", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state], when - now); + if (*nexttime == 0 || *nexttime > when) { + *nexttime = when; + } + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: transition %s %s type %s " + "state %s to state %s!", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + /* It is safe to make the transition. */ + dst_key_setstate(dkey->key, i, next_state); + dst_key_settime(dkey->key, keystatetimes[i], now); + changed = true; + } + } + + /* We changed something, continue processing. */ + if (changed) { + goto transition; + } + + return (ISC_R_SUCCESS); +} + +/* + * See if this key needs to be initialized with a role. A key created and + * derived from a dnssec-policy will have the required metadata available, + * otherwise these may be missing and need to be initialized. + * + */ +static void +keymgr_key_init_role(dns_dnsseckey_t *key) +{ + bool ksk, zsk; + isc_result_t ret; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0); + dst_key_setbool(key->key, DST_BOOL_KSK, ksk); + } + ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0); + dst_key_setbool(key->key, DST_BOOL_ZSK, zsk); + } +} + +/* + * Examine 'keys' and match 'kasp' policy. + * + */ +isc_result_t +dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, + const char *directory, isc_mem_t *mctx, + dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, + isc_stdtime_t now, isc_stdtime_t *nexttime) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_dnsseckeylist_t newkeys; + dns_kasp_key_t *kkey; + dns_dnsseckey_t *candidate = NULL; + dns_dnsseckey_t *newkey = NULL; + dst_key_t *dst_key = NULL; + isc_dir_t dir; + bool dir_open = false; + int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); + char keystr[DST_KEY_FORMATSIZE]; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyring != NULL); + + ISC_LIST_INIT(newkeys); + isc_dir_init(&dir); + if (directory == NULL) { + directory = "."; + } + + RETERR(isc_dir_open(&dir, directory)); + dir_open = true; + + *nexttime = 0; + + /* Debug logging: what keys are available in the keyring? */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + if (ISC_LIST_EMPTY(*keyring)) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: keyring empty (zone %s policy %s)", + namebuf, dns_kasp_getname(kasp)); + } + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + dst_key_format(dkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: keyring: dnskey %s (policy %s)", + keystr, + dns_kasp_getname(kasp)); + } + } + + /* Create keys according to the policy, if come in short. */ + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + isc_stdtime_t retire = 0, active = 0, prepub = 0; + uint32_t lifetime = dns_kasp_key_lifetime(kkey); + dns_dnsseckey_t *active_key = NULL; + + /* Do we have keys available for this kasp key? */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + /* Make sure this key knows about roles. */ + keymgr_key_init_role(dkey); + + if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { + /* Found a match. */ + + /* Initialize lifetime. */ + uint32_t l; + if (dst_key_getnum(dkey->key, + DST_NUM_LIFETIME, &l) != + ISC_R_SUCCESS) + { + dst_key_setnum(dkey->key, + DST_NUM_LIFETIME, + lifetime); + } + + if (dst_key_is_active(dkey->key, now)) { + if (active_key != NULL) { + /* + * Multiple signing keys match + * the kasp key configuration. + * Retire excess keys. + */ + keymgr_key_retire(dkey, now); + } else { + /* Save the matched key. */ + active_key = dkey; + } + } + } + } + + /* Do we need to create a successor for the active key? */ + if (active_key != NULL) { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, + sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: DNSKEY %s (%s) matches " + "policy %s", keystr, + keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + + /* + * Calculate when the successor needs to be published + * in the zone. + */ + prepub = keymgr_prepublication_time(active_key, kasp, + lifetime, now); + if (prepub == 0 || prepub > now) { + /* No need to start rollover now. */ + if (*nexttime == 0 || prepub < *nexttime) { + *nexttime = prepub; + } + continue; + } + if (keymgr_key_has_successor(active_key, keyring)) { + /* Key already has successor. */ + continue; + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, + sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: need successor for " + "DNSKEY %s (%s) (policy %s)", + keystr, + keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namestr, sizeof(namestr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: no active key found for %s " + "(policy %s)", namestr, + dns_kasp_getname(kasp)); + } + + /* It is time to do key rollover, we need a new key. */ + + /* + * Check if there is a key available in pool because keys + * may have been pregenerated with dnssec-keygen. + */ + for (candidate = ISC_LIST_HEAD(*keyring); + candidate != NULL; + candidate = ISC_LIST_NEXT(candidate, link)) + { + if (keymgr_dnsseckey_kaspkey_match(candidate, kkey) && + dst_key_is_unused(candidate->key)) + { + /* Found a candidate in keyring. */ + break; + } + } + + if (candidate == NULL) { + /* No key available in keyring, create a new one. */ + RETERR(keymgr_createkey(kkey, origin, rdclass, mctx, + keyring, &dst_key)); + dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp)); + dst_key_settime(dst_key, DST_TIME_CREATED, now); + RETERR(dns_dnsseckey_create(mctx, &dst_key, &newkey)); + } else { + newkey = candidate; + dst_key_setnum(newkey->key, DST_NUM_LIFETIME, lifetime); + } + + /* Got a key. */ + if (active_key == NULL) { + /* + * If there is no active key found yet for this kasp + * key configuration, immediately make this key active. + */ + dst_key_settime(newkey->key, DST_TIME_PUBLISH, now); + dst_key_settime(newkey->key, DST_TIME_ACTIVATE, now); + active = now; + } else { + /* + * This is a successor. Mark the relationship. + */ + dst_key_setnum(newkey->key, DST_NUM_PREDECESSOR, + dst_key_id(active_key->key)); + dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR, + dst_key_id(newkey->key)); + (void)dst_key_gettime(active_key->key, + DST_TIME_INACTIVE, &retire); + dst_key_settime(newkey->key, DST_TIME_PUBLISH, prepub); + dst_key_settime(newkey->key, DST_TIME_ACTIVATE, retire); + active = retire; + } + + /* This key wants to be present. */ + dst_key_setstate(newkey->key, DST_KEY_GOAL, OMNIPRESENT); + + /* Do we need to set retire time? */ + (void)dst_key_getnum(newkey->key, DST_NUM_LIFETIME, &lifetime); + if (lifetime > 0) { + dst_key_settime(newkey->key, DST_TIME_INACTIVE, + (active + lifetime)); + } + + /* Append dnsseckey to list of new keys. */ + dns_dnssec_get_hints(newkey, now); + newkey->source = dns_keysource_repository; + INSIST(!newkey->legacy); + if (candidate == NULL) { + ISC_LIST_APPEND(newkeys, newkey, link); + } + + /* Logging. */ + dst_key_format(newkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "keymgr: DNSKEY %s (%s) %s for policy %s", + keystr, keymgr_keyrole(newkey->key), + (candidate != NULL) ? "selected" : "created", + dns_kasp_getname(kasp)); + + /* Onwards to next kasp key configuration. */ + candidate = NULL; + newkey = NULL; + } + + /* Walked all kasp key configurations. Append new keys. */ + if (!ISC_LIST_EMPTY(newkeys)) { + ISC_LIST_APPENDLIST(*keyring, newkeys, link); + } + + /* Initialize key states (for keys that don't have them yet). */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + bool ksk = false, zsk = false; + + /* Set key states for all keys that do not have them. */ + INITIALIZE_STATE(dkey->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY); + (void) dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); + if (ksk) { + INITIALIZE_STATE(dkey->key, DST_KEY_KRRSIG, + DST_TIME_KRRSIG); + INITIALIZE_STATE(dkey->key, DST_KEY_DS, DST_TIME_DS); + } + (void) dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk); + if (zsk) { + INITIALIZE_STATE(dkey->key, DST_KEY_ZRRSIG, + DST_TIME_ZRRSIG); + } + } + + /* Read to update key states. */ + keymgr_update(keyring, kasp, now, nexttime); + + /* Store key states and update hints. */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + dns_dnssec_get_hints(dkey, now); + RETERR(dst_key_tofile(dkey->key, options, directory)); + } + + result = ISC_R_SUCCESS; + +failure: + if (dir_open) { + isc_dir_close(&dir); + } + + INSIST(newkey == NULL); + if (result != ISC_R_SUCCESS) { + while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) { + ISC_LIST_UNLINK(newkeys, newkey, link); + INSIST(newkey->key != NULL); + dst_key_free(&newkey->key); + dns_dnsseckey_destroy(mctx, &newkey); + } + } + + return (result); +} diff --git a/lib/dns/update.c b/lib/dns/update.c index 25827d4b54..9bd5896796 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -1076,6 +1077,7 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, { isc_result_t result; dns_dbnode_t *node = NULL; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdataset_t rdataset; dns_rdata_t sig_rdata = DNS_RDATA_INIT; dns_stats_t* dnssecsignstats = dns_zone_getdnssecsignstats(zone); @@ -1085,6 +1087,11 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, bool added_sig = false; isc_mem_t *mctx = diff->mctx; + if (kasp != NULL) { + check_ksk = false; + keyset_kskonly = true; + } + dns_rdataset_init(&rdataset); isc_buffer_init(&buffer, data, sizeof(data)); @@ -1155,7 +1162,63 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, } } - if (both) { + if (kasp != NULL) { + /* + * A dnssec-policy is found. Check what RRsets this + * key should sign. + */ + isc_stdtime_t when; + isc_result_t kresult; + bool ksk = false; + bool zsk = false; + + kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[i])) { + ksk = true; + } + } + kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[i])) { + zsk = true; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + /* + * DNSKEY RRset is signed with KSK. + * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). + */ + if (!ksk) { + continue; + } + } else if (!zsk) { + /* + * Other RRsets are signed with ZSK. + */ + continue; + } else if (zsk && !dst_key_is_signing(keys[i], + DST_BOOL_ZSK, + inception, + &when)) { + /* + * This key is not active for zone-signing. + */ + continue; + } + + /* + * If this key is revoked, it may only sign the + * DNSKEY RRset. + */ + if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + } else if (both) { /* * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). */ diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index a3c7a90af5..31af4b1c1e 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -316,6 +316,7 @@ dns_dns64_next dns_dns64_unlink dns_dnssec_findmatchingkeys dns_dnssec_findzonekeys +dns_dnssec_get_hints dns_dnssec_keyactive dns_dnssec_keyfromrdata dns_dnssec_keylistfromrdataset @@ -413,9 +414,50 @@ dns_journal_rollforward dns_journal_set_sourceserial dns_journal_write_transaction dns_journal_writediff +dns_kasp_addkey +dns_kasp_attach +dns_kasp_create +dns_kasp_detach +dns_kasp_dnskeyttl +dns_kasp_dsttl +dns_kasp_freeze +dns_kasp_getname +dns_kasp_key_algorithm +dns_kasp_key_create +dns_kasp_key_destroy +dns_kasp_key_ksk +dns_kasp_key_lifetime +dns_kasp_key_size +dns_kasp_key_zsk +dns_kasp_keylist_empty +dns_kasp_keys +dns_kasp_parentpropagationdelay +dns_kasp_parentregistrationdelay +dns_kasp_publishsafety +dns_kasp_retiresafety +dns_kasp_setdnskeyttl +dns_kasp_setdsttl +dns_kasp_setparentpropagationdelay +dns_kasp_setparentregistrationdelay +dns_kasp_setpublishsafety +dns_kasp_setretiresafety +dns_kasp_setsigrefresh +dns_kasp_setsigvalidity +dns_kasp_setsigvalidity_dnskey +dns_kasp_setzonemaxttl +dns_kasp_setzonepropagationdelay +dns_kasp_signdelay +dns_kasp_sigrefresh +dns_kasp_sigvalidity +dns_kasp_sigvalidity_dnskey +dns_kasp_thaw +dns_kasp_zonemaxttl +dns_kasp_zonepropagationdelay +dns_kasplist_find dns_keydata_fromdnskey dns_keydata_todnskey dns_keyflags_fromtext +dns_keymgr_run dns_keynode_attach dns_keynode_create dns_keynode_detach @@ -1154,6 +1196,7 @@ dns_zone_getidleout dns_zone_getincludes dns_zone_getjournal dns_zone_getjournalsize +dns_zone_getkasp dns_zone_getkeydirectory dns_zone_getkeyopts dns_zone_getkeyvalidityinterval @@ -1255,6 +1298,7 @@ dns_zone_setidleout dns_zone_setisself dns_zone_setjournal dns_zone_setjournalsize +dns_zone_setkasp dns_zone_setkeydirectory dns_zone_setkeyopt dns_zone_setkeyvalidityinterval @@ -1375,6 +1419,7 @@ dst_key_attach dst_key_buildfilename dst_key_buildinternal dst_key_class +dst_key_copy_metadata dst_key_compare dst_key_computesecret dst_key_dump @@ -1389,13 +1434,21 @@ dst_key_fromlabel dst_key_fromnamedfile dst_key_generate dst_key_getbits +dst_key_getbool dst_key_getfilename dst_key_getgssctx dst_key_getnum dst_key_getprivateformat +dst_key_getstate dst_key_gettime dst_key_getttl dst_key_id +dst_key_is_active +dst_key_is_published +dst_key_is_removed +dst_key_is_revoked +dst_key_is_signing +dst_key_is_unused dst_key_inactive dst_key_isexternal dst_key_isnullkey @@ -1407,15 +1460,18 @@ dst_key_privatefrombuffer dst_key_proto dst_key_pubcompare dst_key_read_public +dst_key_read_state dst_key_restore dst_key_rid dst_key_secretsize dst_key_setbits +dst_key_setbool dst_key_setexternal dst_key_setflags dst_key_setinactive dst_key_setnum dst_key_setprivateformat +dst_key_setstate dst_key_settime dst_key_setttl dst_key_sigsize @@ -1424,7 +1480,9 @@ dst_key_tkeytoken dst_key_tobuffer dst_key_todns dst_key_tofile +dst_key_unsetbool dst_key_unsetnum +dst_key_unsetstate dst_key_unsettime dst_lib_destroy dst_lib_init diff --git a/lib/dns/win32/libdns.vcxproj.filters.in b/lib/dns/win32/libdns.vcxproj.filters.in index 56f12aeba2..89811c148d 100644 --- a/lib/dns/win32/libdns.vcxproj.filters.in +++ b/lib/dns/win32/libdns.vcxproj.filters.in @@ -116,9 +116,15 @@ Library Source Files + + Library Source Files + Library Source Files + + Library Source Files + Library Source Files @@ -449,12 +455,18 @@ Library Header Files + + Library Header Files + Library Header Files Library Header Files + + Library Header Files + Library Header Files diff --git a/lib/dns/win32/libdns.vcxproj.in b/lib/dns/win32/libdns.vcxproj.in index 43c3acef8a..89019d8912 100644 --- a/lib/dns/win32/libdns.vcxproj.in +++ b/lib/dns/win32/libdns.vcxproj.in @@ -150,7 +150,9 @@ + + @@ -265,8 +267,10 @@ + + diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 6d54179557..41af9487ac 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -45,7 +45,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -303,6 +305,7 @@ struct dns_zone { uint32_t sigresigninginterval; dns_view_t *view; dns_view_t *prev_view; + dns_kasp_t *kasp; dns_checkmxfunc_t checkmx; dns_checksrvfunc_t checksrv; dns_checknsfunc_t checkns; @@ -1014,6 +1017,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->sigvalidityinterval = 30 * 24 * 3600; zone->keyvalidityinterval = 0; zone->sigresigninginterval = 7 * 24 * 3600; + zone->kasp = NULL; zone->view = NULL; zone->prev_view = NULL; zone->checkmx = NULL; @@ -1184,6 +1188,9 @@ zone_free(dns_zone_t *zone) { isc_mem_free(zone->mctx, zone->keydirectory); } zone->keydirectory = NULL; + if (zone->kasp != NULL) { + dns_kasp_detach(&zone->kasp); + } zone->journalsize = -1; if (zone->journal != NULL) { isc_mem_free(zone->mctx, zone->journal); @@ -1880,7 +1887,7 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { isc_time_t now; isc_time_t loadtime; dns_db_t *db = NULL; - bool rbt, hasraw; + bool rbt, hasraw, is_dynamic; REQUIRE(DNS_ZONE_VALID(zone)); @@ -1945,11 +1952,13 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { goto cleanup; } - if (zone->db != NULL && dns_zone_isdynamic(zone, false)) { + is_dynamic = dns_zone_isdynamic(zone, false) || + dns_zone_getkasp(zone) != NULL; + if (zone->db != NULL && is_dynamic) { /* - * This is a slave, stub, or dynamically updated - * zone being reloaded. Do nothing - the database - * we already have is guaranteed to be up-to-date. + * This is a slave, stub, dynamically updated, or kasp enabled + * zone being reloaded. Do nothing - the database we already + * have is guaranteed to be up-to-date. */ if (zone->type == dns_zone_master && !hasraw) result = DNS_R_DYNAMIC; @@ -3647,8 +3656,9 @@ set_resigntime(dns_zone_t *zone) { dns_db_t *db = NULL; /* We only re-sign zones that can be dynamically updated */ - if (zone->update_disabled) + if (zone->update_disabled) { return; + } if (!inline_secure(zone) && (zone->type != dns_zone_master || (zone->ssutable == NULL && @@ -3674,10 +3684,11 @@ set_resigntime(dns_zone_t *zone) { goto cleanup; } - resign = rdataset.resign - zone->sigresigninginterval; + resign = rdataset.resign - dns_zone_getsigresigninginterval(zone); dns_rdataset_disassociate(&rdataset); nanosecs = isc_random_uniform(1000000000); isc_time_set(&zone->resigntime, resign, nanosecs); + cleanup: dns_db_detach(&db); return; @@ -3694,6 +3705,7 @@ check_nsec3param(dns_zone_t *zone, dns_db_t *db) { dns_rdata_t rdata = DNS_RDATA_INIT; bool dynamic = (zone->type == dns_zone_master) ? dns_zone_isdynamic(zone, false) : false; + dynamic = dynamic || dns_zone_getkasp(zone); dns_rdataset_init(&rdataset); result = dns_db_findnode(db, &zone->origin, false, &node); @@ -4500,6 +4512,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, bool had_db = false; unsigned int options; dns_include_t *inc; + bool is_dynamic = false; INSIST(LOCKED_ZONE(zone)); if (inline_raw(zone)) { @@ -4640,7 +4653,9 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, * journal file if it isn't, as we wouldn't be able to apply * updates otherwise. */ - if (zone->journal != NULL && dns_zone_isdynamic(zone, true) && + is_dynamic = dns_zone_isdynamic(zone, true) || + dns_zone_getkasp(zone) != NULL; + if (zone->journal != NULL && is_dynamic && ! DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS)) { uint32_t jserial; @@ -4812,7 +4827,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, if (zone->type == dns_zone_master && (zone->update_acl != NULL || zone->ssutable != NULL) && - zone->sigresigninginterval < (3 * refresh) && + dns_zone_getsigresigninginterval(zone) < (3 * refresh) && dns_db_issecure(db)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, @@ -4944,10 +4959,11 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, resume_addnsec3chain(zone); } + is_dynamic = dns_zone_isdynamic(zone, false) || + dns_zone_getkasp(zone) != NULL; if (zone->type == dns_zone_master && !DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN) && - dns_zone_isdynamic(zone, false) && - dns_db_issecure(db)) + is_dynamic && dns_db_issecure(db)) { dns_name_t *name; dns_fixedname_t fixed; @@ -4970,7 +4986,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, "next resign: %s/%s " "in %d seconds", namebuf, typebuf, next.resign - timenow - - zone->sigresigninginterval); + dns_zone_getsigresigninginterval(zone)); dns_rdataset_disassociate(&next); } else { dnssec_log(zone, ISC_LOG_WARNING, @@ -5007,7 +5023,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, zone->nincludes++; } - if (! dns_db_ispersistent(db)) { + if (!dns_db_ispersistent(db)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "loaded serial %u%s", serial, dns_db_issecure(db) ? " (DNSSEC signed)" : ""); @@ -5063,7 +5079,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, } else if (zone->type == dns_zone_master || zone->type == dns_zone_redirect) { - if (! (inline_secure(zone) && result == ISC_R_FILENOTFOUND)) { + if (!(inline_secure(zone) && result == ISC_R_FILENOTFOUND)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "not loaded due to errors."); @@ -5537,6 +5553,29 @@ dns_zone_setflag(dns_zone_t *zone, unsigned int flags, bool value) { } } +void +dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t* kasp) +{ + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->kasp != NULL) { + dns_kasp_t* oldkasp = zone->kasp; + zone->kasp = NULL; + dns_kasp_detach(&oldkasp); + } + zone->kasp = kasp; + UNLOCK_ZONE(zone); +} + +dns_kasp_t* +dns_zone_getkasp(dns_zone_t *zone) +{ + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->kasp); +} + void dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value) @@ -6231,22 +6270,41 @@ delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, bool *warn) { unsigned int i = 0; + isc_result_t ret; bool have_ksk = false, have_zsk = false; bool have_pksk = false, have_pzsk = false; for (i = 0; i < nkeys; i++) { - if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) + bool ksk, zsk; + + if (have_pksk && have_ksk && have_pzsk && have_zsk) { + break; + } + + if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) { continue; - if (dst_key_isprivate(keys[i])) { - if (KSK(keys[i])) - have_ksk = have_pksk = true; - else - have_zsk = have_pzsk = true; - } else { - if (KSK(keys[i])) - have_ksk = true; - else - have_zsk = true; + } + + ret = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = KSK(keys[i]); + } + ret = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = !KSK(keys[i]); + } + + if (ksk) { + have_ksk = true; + if (dst_key_isprivate(keys[i])) { + have_pksk = true; + } + } + if (zsk) { + have_zsk = true; + if (dst_key_isprivate(keys[i])) { + have_pzsk = true; + } } } @@ -6325,7 +6383,9 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (type != dns_rdatatype_dnskey) { + if (type != dns_rdatatype_dnskey && + type != dns_rdatatype_cds && + type != dns_rdatatype_cdnskey) { bool warn = false, deleted = false; if (delsig_ok(&rrsig, keys, nkeys, &warn)) { result = update_one_rr(db, ver, zonediff->diff, @@ -6380,7 +6440,7 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, } /* - * RRSIG(DNSKEY) requires special processing. + * KSK RRSIGs requires special processing. */ found = false; for (i = 0; i < nkeys; i++) { @@ -6388,7 +6448,7 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, rrsig.keyid == dst_key_id(keys[i])) { found = true; /* - * Mark offline RRSIG(DNSKEY). + * Mark offline DNSKEY. * We want the earliest offline expire time * iff there is a new offline signature. */ @@ -6465,6 +6525,7 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, { isc_result_t result; dns_dbnode_t *node = NULL; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_stats_t* dnssecsignstats; dns_stats_t* dnssecrefreshstats; dns_rdataset_t rdataset; @@ -6473,6 +6534,11 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, isc_buffer_t buffer; unsigned int i, j; + if (kasp != NULL) { + check_ksk = false; + keyset_kskonly = true; + } + dns_rdataset_init(&rdataset); isc_buffer_init(&buffer, data, sizeof(data)); @@ -6546,7 +6612,61 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, } } - if (both) { + if (kasp != NULL) { + /* + * A dnssec-policy is found. Check what RRsets this + * key should sign. + */ + isc_result_t kresult; + isc_stdtime_t when; + bool ksk = false; + bool zsk = false; + + kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[i])) { + ksk = true; + } + } + kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[i])) { + zsk = true; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + /* + * DNSKEY RRset is signed with KSK. + * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). + */ + if (!ksk) { + continue; + } + } else if (!zsk) { + /* + * Other RRsets are signed with ZSK. + */ + continue; + } else if (!dst_key_is_signing(keys[i], DST_BOOL_ZSK, + inception, &when)) { + /* + * This key is not active for zone-signing. + */ + continue; + } + + /* + * If this key is revoked, it may only sign the + * DNSKEY RRset. + */ + if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + } else if (both) { /* * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). */ @@ -6659,7 +6779,7 @@ zone_resigninc(dns_zone_t *zone) { goto failure; } - sigvalidityinterval = zone->sigvalidityinterval; + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; expiryinterval = dns_zone_getsigresigninginterval(zone); @@ -6706,7 +6826,8 @@ zone_resigninc(dns_zone_t *zone) { i = 0; while (result == ISC_R_SUCCESS) { - resign = rdataset.resign - zone->sigresigninginterval; + resign = rdataset.resign - + dns_zone_getsigresigninginterval(zone); covers = rdataset.covers; dns_rdataset_disassociate(&rdataset); @@ -6878,13 +6999,16 @@ next_active(dns_db_t *db, dns_dbversion_t *version, dns_name_t *oldname, } static bool -signed_with_key(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, - dns_rdatatype_t type, dst_key_t *key) +signed_with_good_key(dns_zone_t* zone, dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdatatype_t type, + dst_key_t *key) { isc_result_t result; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t rrsig; + int count = 0; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_rrsig, @@ -6904,8 +7028,46 @@ signed_with_key(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdataset_disassociate(&rdataset); return (true); } + if (rrsig.algorithm == dst_key_alg(key)) { + count++; + } dns_rdata_reset(&rdata); } + + if (kasp) { + dns_kasp_key_t* kkey; + int zsk_count = 0; + bool approved; + + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) { + continue; + } + if (dns_kasp_key_zsk(kkey)) { + zsk_count++; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds) + { + /* + * CDS and CDNSKEY are signed with KSK like DNSKEY. + * (RFC 7344, section 4.1 specifies that they must + * be signed with a key in the current DS RRset, + * which would only include KSK's.) + */ + approved = false; + } else { + approved = (zsk_count == count); + } + + dns_rdataset_disassociate(&rdataset); + return (approved); + } + dns_rdataset_disassociate(&rdataset); return (false); } @@ -6988,11 +7150,12 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3, bool build_nsec, dst_key_t *key, isc_stdtime_t inception, isc_stdtime_t expire, - unsigned int minimum, bool is_ksk, + unsigned int minimum, bool is_ksk, bool is_zsk, bool keyset_kskonly, bool is_bottom_of_zone, dns_diff_t *diff, int32_t *signatures, isc_mem_t *mctx) { isc_result_t result; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdatasetiter_t *iterator = NULL; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; @@ -7060,6 +7223,8 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, } result = dns_rdatasetiter_first(iterator); while (result == ISC_R_SUCCESS) { + isc_stdtime_t when; + dns_rdatasetiter_current(iterator, &rdataset); if (rdataset.type == dns_rdatatype_soa || rdataset.type == dns_rdatatype_rrsig) @@ -7079,18 +7244,26 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, if (!is_ksk && keyset_kskonly) { goto next_rdataset; } - } else if (is_ksk) { + } else if (!is_zsk) { goto next_rdataset; + } else if (is_zsk && !dst_key_is_signing(key, DST_BOOL_ZSK, + inception, &when)) { + /* Only applies to dnssec-policy. */ + if (kasp != NULL) { + goto next_rdataset; + } } + if (seen_ns && !seen_soa && rdataset.type != dns_rdatatype_ds && rdataset.type != dns_rdatatype_nsec) { goto next_rdataset; } - if (signed_with_key(db, node, version, rdataset.type, key)) { + if (signed_with_good_key(zone, db, node, version, rdataset.type, key)) { goto next_rdataset; } + /* Calculate the signature, creating a RRSIG RDATA. */ isc_buffer_clear(&buffer); CHECK(dns_dnssec_sign(name, &rdataset, key, &inception, @@ -8680,13 +8853,14 @@ zone_sign(dns_zone_t *zone) { dns__zonediff_t zonediff; dns_fixedname_t fixed; dns_fixedname_t nextfixed; + dns_kasp_t *kasp; dns_name_t *name, *nextname; dns_rdataset_t rdataset; dns_signing_t *signing, *nextsigning; dns_signinglist_t cleanup; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; int32_t signatures; - bool check_ksk, keyset_kskonly, is_ksk; + bool check_ksk, keyset_kskonly, is_ksk, is_zsk; bool with_ksk, with_zsk; bool commit = false; bool is_bottom_of_zone; @@ -8747,6 +8921,8 @@ zone_sign(dns_zone_t *zone) { goto cleanup; } + kasp = dns_zone_getkasp(zone); + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; @@ -8783,8 +8959,10 @@ zone_sign(dns_zone_t *zone) { signing = ISC_LIST_HEAD(zone->signing); first = true; - check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); - keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); + check_ksk = (kasp != NULL) ? false : + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); + keyset_kskonly = (kasp != NULL) ? true : + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); /* Determine which type of chain to build */ CHECK(dns_private_chains(db, version, zone->privatetype, @@ -8802,7 +8980,7 @@ zone_sign(dns_zone_t *zone) { ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (signing->done || signing->db != zone->db) { /* - * The zone has been reloaded. We will have + * The zone has been reloaded. We will have to * created new signings as part of the reload * process so we can destroy this one. */ @@ -8830,8 +9008,15 @@ zone_sign(dns_zone_t *zone) { if (ALG(zone_keys[i]) == signing->algorithm && dst_key_id(zone_keys[i]) == signing->keyid) { - if (KSK(zone_keys[i])) + bool ksk = false; + isc_result_t ret = dst_key_getbool( + zone_keys[i], DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = KSK(zone_keys[i]); + } + if (ksk) { dst_key_free(&zone_keys[i]); + } continue; } zone_keys[j] = zone_keys[i]; @@ -8962,10 +9147,39 @@ zone_sign(dns_zone_t *zone) { } } } - if (both || REVOKE(zone_keys[i])) { + if (kasp != NULL) { + /* + * A dnssec-policy is found. Check what + * RRsets this key can sign. + */ + isc_result_t kresult; + is_ksk = false; + kresult = dst_key_getbool(zone_keys[i], + DST_BOOL_KSK, + &is_ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(zone_keys[i])) { + is_ksk = true; + } + } + + is_zsk = false; + kresult = dst_key_getbool(zone_keys[i], + DST_BOOL_ZSK, + &is_zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(zone_keys[i])) { + is_zsk = true; + } + } + /* Treat as if we have both KSK and ZSK. */ + both = true; + } else if (both || REVOKE(zone_keys[i])) { is_ksk = KSK(zone_keys[i]); + is_zsk = !KSK(zone_keys[i]); } else { is_ksk = false; + is_zsk = true; } /* @@ -8973,7 +9187,7 @@ zone_sign(dns_zone_t *zone) { * the RRset is still signed at least once by a * KSK and a ZSK. */ - if (signing->deleteit && !is_ksk && with_zsk) { + if (signing->deleteit && is_zsk && with_zsk) { continue; } @@ -8984,7 +9198,7 @@ zone_sign(dns_zone_t *zone) { CHECK(sign_a_node(db, zone, name, node, version, build_nsec3, build_nsec, zone_keys[i], inception, expire, - zone->minimum, is_ksk, + zone->minimum, is_ksk, is_zsk, (both && keyset_kskonly), is_bottom_of_zone, zonediff.diff, &signatures, zone->mctx)); @@ -8995,10 +9209,10 @@ zone_sign(dns_zone_t *zone) { if (!signing->deleteit) { break; } - if (!is_ksk) { + if (is_zsk) { with_zsk = true; } - if (KSK(zone_keys[i])) { + if (is_ksk) { with_ksk = true; } } @@ -15317,8 +15531,14 @@ receive_secure_db(isc_task_t *task, isc_event_t *event) { rdataset.type == dns_rdatatype_nsec3 || rdataset.type == dns_rdatatype_dnskey || rdataset.type == dns_rdatatype_nsec3param) { - dns_rdataset_disassociate(&rdataset); - continue; + /* + * Allow DNSSEC records with dnssec-policy. + * WMM: Perhaps add config option for it. + */ + if (dns_zone_getkasp(zone) == NULL) { + dns_rdataset_disassociate(&rdataset); + continue; + } } if (rdataset.type == dns_rdatatype_soa && have_oldserial) { @@ -18150,6 +18370,57 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, return (result); } + +/* + * See if dns__zone_updatesigs() will update signature for RRset 'rrtype' at + * the apex, and if not tickle them and cause to sign so that newly activated + * keys are used. + */ +static isc_result_t +tickle_apex_rrset(dns_rdatatype_t rrtype, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, + dns__zonediff_t *zonediff, dst_key_t **keys, + unsigned int nkeys, isc_stdtime_t inception, + isc_stdtime_t keyexpire, bool check_ksk, bool keyset_kskonly) +{ + dns_difftuple_t *tuple; + isc_result_t result; + + for (tuple = ISC_LIST_HEAD(diff->tuples); + tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + if (tuple->rdata.type == rrtype && + dns_name_equal(&tuple->name, &zone->origin)) + { + break; + } + } + + if (tuple == NULL) { + result = del_sigs(zone, db, ver, &zone->origin, rrtype, + zonediff, keys, nkeys, now, false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:del_sigs -> %s", + dns_result_totext(result)); + return (result); + } + result = add_sigs(db, ver, &zone->origin, zone, rrtype, + zonediff->diff, keys, nkeys, zone->mctx, + inception, keyexpire, check_ksk, + keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:add_sigs -> %s", + dns_result_totext(result)); + return (result); + } + } + + return (ISC_R_SUCCESS); +} + static isc_result_t sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, dns__zonediff_t *zonediff) @@ -18159,7 +18430,6 @@ sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, bool check_ksk, keyset_kskonly; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; unsigned int nkeys = 0, i; - dns_difftuple_t *tuple; result = dns__zone_findkeys(zone, db, ver, now, zone->mctx, DNS_MAXZONEKEYS, zone_keys, &nkeys); @@ -18184,40 +18454,27 @@ sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); /* - * See if dns__zone_updatesigs() will update DNSKEY signature and if - * not cause them to sign so that newly activated keys are used. + * See if dns__zone_updatesigs() will update DNSKEY/CDS/CDNSKEY + * signature and if not cause them to sign so that newly activated + * keys are used. */ - for (tuple = ISC_LIST_HEAD(diff->tuples); - tuple != NULL; - tuple = ISC_LIST_NEXT(tuple, link)) - { - if (tuple->rdata.type == dns_rdatatype_dnskey && - dns_name_equal(&tuple->name, &zone->origin)) - { - break; - } + result = tickle_apex_rrset(dns_rdatatype_dnskey, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; } - - if (tuple == NULL) { - result = del_sigs(zone, db, ver, &zone->origin, - dns_rdatatype_dnskey, zonediff, - zone_keys, nkeys, now, false); - if (result != ISC_R_SUCCESS) { - dnssec_log(zone, ISC_LOG_ERROR, - "sign_apex:del_sigs -> %s", - dns_result_totext(result)); - goto failure; - } - result = add_sigs(db, ver, &zone->origin, zone, - dns_rdatatype_dnskey, zonediff->diff, - zone_keys, nkeys, zone->mctx, inception, - keyexpire, check_ksk, keyset_kskonly); - if (result != ISC_R_SUCCESS) { - dnssec_log(zone, ISC_LOG_ERROR, - "sign_apex:add_sigs -> %s", - dns_result_totext(result)); - goto failure; - } + result = tickle_apex_rrset(dns_rdatatype_cds, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = tickle_apex_rrset(dns_rdatatype_cdnskey, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; } result = dns__zone_updatesigs(diff, db, ver, zone_keys, nkeys, zone, @@ -18394,6 +18651,7 @@ zone_rekey(dns_zone_t *zone) { dns_dnsseckeylist_t dnskeys, keys, rmkeys; dns_dnsseckey_t *key = NULL; dns_diff_t diff, _sig_diff; + dns_kasp_t *kasp; dns__zonediff_t zonediff; bool commit = false, newactive = false; bool newalg = false; @@ -18401,7 +18659,7 @@ zone_rekey(dns_zone_t *zone) { dns_ttl_t ttl = 3600; const char *dir = NULL; isc_mem_t *mctx = NULL; - isc_stdtime_t now; + isc_stdtime_t now, nexttime = 0; isc_time_t timenow; isc_interval_t ival; char timebuf[80]; @@ -18470,17 +18728,34 @@ zone_rekey(dns_zone_t *zone) { * True when called from "rndc sign". Indicates the zone should be * fully signed now. */ + kasp = dns_zone_getkasp(zone); fullsign = DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN); result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx, &keys); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_DEBUG(1), + "zone_rekey:dns_dnssec_findmatchingkeys failed: %s", + isc_result_totext(result)); + } + + if (kasp && (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND)) { + ttl = dns_kasp_dnskeyttl(kasp); + + result = dns_keymgr_run(&zone->origin, zone->rdclass, dir, + mctx, &keys, kasp, now, &nexttime); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_rekey:dns_dnssec_keymgr failed: %s", + isc_result_totext(result)); + goto failure; + } + } + if (result == ISC_R_SUCCESS) { - bool check_ksk; - check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys, &zone->origin, ttl, &diff, - !check_ksk, mctx, - dnssec_report); + mctx, dnssec_report); /* * Keys couldn't be updated for some reason; * try again later. @@ -18531,7 +18806,7 @@ zone_rekey(dns_zone_t *zone) { /* * This isn't a new algorithm; clear * first_sign so we won't sign the - * whole zone with this key later + * whole zone with this key later. */ key->first_sign = false; } else { @@ -18698,12 +18973,30 @@ zone_rekey(dns_zone_t *zone) { isc_time_settoepoch(&zone->refreshkeytime); + /* + * If keymgr provided a next time, use the calculated next rekey time. + */ + if (kasp != NULL && nexttime > 0) { + isc_time_t timenext; + + LOCK_ZONE(zone); + DNS_ZONE_TIME_ADD(&timenow, nexttime - now, &timenext); + zone->refreshkeytime = timenext; + UNLOCK_ZONE(zone); + + zone_settimer(zone, &timenow); + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + dnssec_log(zone, ISC_LOG_DEBUG(3), + "next key event in %u seconds: %s", + (nexttime - now), timebuf); + dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); + } /* * If we're doing key maintenance, set the key refresh timer to * the next scheduled key event or to 'dnssec-loadkeys-interval' * seconds in the future, whichever is sooner. */ - if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { + else if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { isc_time_t timethen; isc_stdtime_t then; @@ -19889,7 +20182,8 @@ dns_zone_setserial(dns_zone_t *zone, uint32_t serial) { LOCK_ZONE(zone); if (!inline_secure(zone)) { - if (!dns_zone_isdynamic(zone, true)) { + if (!dns_zone_isdynamic(zone, true) && + dns_zone_getkasp(zone) == NULL) { result = DNS_R_NOTDYNAMIC; goto failure; } diff --git a/lib/isccfg/Makefile.in b/lib/isccfg/Makefile.in index a04e88bcaa..269f4c6963 100644 --- a/lib/isccfg/Makefile.in +++ b/lib/isccfg/Makefile.in @@ -35,11 +35,11 @@ SUBDIRS = include TESTDIRS = @UNITTESTS@ # Alphabetically -OBJS = aclconf.@O@ dnsconf.@O@ log.@O@ namedconf.@O@ \ +OBJS = aclconf.@O@ dnsconf.@O@ kaspconf.@O@ log.@O@ namedconf.@O@ \ parser.@O@ version.@O@ # Alphabetically -SRCS = aclconf.c dnsconf.c log.c namedconf.c \ +SRCS = aclconf.c dnsconf.c kaspconf.c log.c namedconf.c \ parser.c version.c TARGETS = timestamp diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h index 35729a4991..2aefdce110 100644 --- a/lib/isccfg/include/isccfg/cfg.h +++ b/lib/isccfg/include/isccfg/cfg.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -331,6 +332,24 @@ cfg_obj_aspercentage(const cfg_obj_t *obj); * \li A 32-bit unsigned integer. */ +bool +cfg_obj_isduration(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of duration type. + */ + +uint32_t +cfg_obj_asduration(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of duration + * + * Requires: + * \li 'obj' points to a valid configuration object of duration type. + * + * Returns: + * \li A duration in seconds. + */ + bool cfg_obj_isstring(const cfg_obj_t *obj); /*%< diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h index e931282a0f..ad3dd81d17 100644 --- a/lib/isccfg/include/isccfg/grammar.h +++ b/lib/isccfg/include/isccfg/grammar.h @@ -82,6 +82,9 @@ typedef struct cfg_printer cfg_printer_t; typedef ISC_LIST(cfg_listelt_t) cfg_list_t; typedef struct cfg_map cfg_map_t; typedef struct cfg_rep cfg_rep_t; +typedef struct cfg_duration cfg_duration_t; + +#define CFG_DURATION_MAXLEN 64 /* * Function types for configuration object methods @@ -154,6 +157,24 @@ struct cfg_netprefix { unsigned int prefixlen; }; +/*% + * A configuration object to store ISO 8601 durations. + */ +struct cfg_duration { + /* + * The duration is stored in multiple parts: + * [0] Years + * [1] Months + * [2] Weeks + * [3] Days + * [4] Hours + * [5] Minutes + * [6] Seconds + */ + uint32_t parts[7]; + bool iso8601; +}; + /*% * A configuration data representation. */ @@ -183,6 +204,7 @@ struct cfg_obj { isc_dscp_t dscp; } sockaddrdscp; cfg_netprefix_t netprefix; + cfg_duration_t duration; } value; isc_refcount_t references; /*%< reference counter */ const char * file; @@ -290,6 +312,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_netprefix; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_void; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_fixedpoint; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_percentage; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_duration; /*@}*/ /*@{*/ @@ -320,6 +343,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_token; 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; /*@}*/ isc_result_t @@ -504,6 +528,13 @@ cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type, void cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj); +isc_result_t +cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret); + +void +cfg_print_duration(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/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h new file mode 100644 index 0000000000..9d18f445da --- /dev/null +++ b/lib/isccfg/include/isccfg/kaspconf.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#ifndef ISCCFG_KASPCONF_H +#define ISCCFG_KASPCONF_H 1 + +#include + +#include + +#include + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, + dns_kasplist_t *kasplist, dns_kasp_t **kaspp); +/*%< + * Create and configure a KASP. If 'config' is NULL, the default configuration + * is used. If a 'kasplist' is provided, a lookup happens and if a KASP + * already exists with the same name, no new KASP is created, and no attach to + * 'kaspp' happens. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'name' is a valid C string. + * + *\li kaspp != NULL && *kaspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS If creating and configuring the KASP succeeds. + *\li #ISC_R_EXISTS If 'kasplist' already has a kasp structure with 'name'. + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISCCFG_KASPCONF_H */ diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h index f75e56191f..f4f2c39ba1 100644 --- a/lib/isccfg/include/isccfg/namedconf.h +++ b/lib/isccfg/include/isccfg/namedconf.h @@ -49,4 +49,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_keyref; /*%< Zone options */ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_zoneopts; +/*%< DNSSEC Key and Signing Policy options */ +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_dnssecpolicyopts; + #endif /* ISCCFG_NAMEDCONF_H */ diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c new file mode 100644 index 0000000000..75350ffa68 --- /dev/null +++ b/lib/isccfg/kaspconf.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + +/* + * Utility function for getting a configuration option. + */ +static isc_result_t +confget(cfg_obj_t const * const *maps, const char *name, const cfg_obj_t **obj) +{ + for (size_t i = 0;; i++) { + if (maps[i] == NULL) { + return (ISC_R_NOTFOUND); + } + if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + } +} + +/* + * Utility function for configuring durations. + */ +static uint32_t +get_duration(const cfg_obj_t **maps, const char* option, uint32_t dfl) +{ + const cfg_obj_t *obj; + isc_result_t result; + obj = NULL; + + result = confget(maps, option, &obj); + if (result == ISC_R_NOTFOUND) { + return (dfl); + } + INSIST(result == ISC_R_SUCCESS); + return (cfg_obj_asduration(obj)); +} + +/* + * Create a new kasp key derived from configuration. + */ +static isc_result_t +cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp) +{ + isc_result_t result; + dns_kasp_key_t *key = NULL; + + /* Create a new key reference. */ + result = dns_kasp_key_create(kasp, &key); + if (result != ISC_R_SUCCESS) { + return (result); + } + 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->algorithm = DNS_KEYALG_ECDSA256; + key->length = -1; + } else { + const char* rolestr; + const cfg_obj_t* obj; + + rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role")); + if (strcmp(rolestr, "ksk") == 0) { + key->role |= DNS_KASP_KEY_ROLE_KSK; + } else if (strcmp(rolestr, "zsk") == 0) { + key->role |= DNS_KASP_KEY_ROLE_ZSK; + } else if (strcmp(rolestr, "csk") == 0) { + 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")); + obj = cfg_tuple_get(config, "length"); + if (cfg_obj_isuint32(obj)) { + key->length = cfg_obj_asuint32(obj); + } + } + dns_kasp_addkey(kasp, key); + return (result); +} + +isc_result_t +cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, + dns_kasplist_t *kasplist, dns_kasp_t **kaspp) +{ + isc_result_t result; + const cfg_obj_t *maps[2]; + const cfg_obj_t *koptions = NULL; + const cfg_obj_t *keys = NULL; + const cfg_listelt_t *element = NULL; + const char *kaspname = NULL; + dns_kasp_t *kasp = NULL; + int i = 0; + + REQUIRE(kaspp != NULL && *kaspp == NULL); + + kaspname = (config != NULL) ? + cfg_obj_asstring(cfg_tuple_get(config, "name")) : + "default"; + + REQUIRE(strcmp(kaspname, "none") != 0); + + result = dns_kasplist_find(kasplist, kaspname, &kasp); + + if (result == ISC_R_SUCCESS) { + return (ISC_R_EXISTS); + } + if (result != ISC_R_NOTFOUND) { + return (result); + } + + /* No kasp with configured name was found in list, create new one. */ + INSIST(kasp == NULL); + result = dns_kasp_create(mctx, kaspname, &kasp); + if (result != ISC_R_SUCCESS) { + return (result); + } + INSIST(kasp != NULL); + + /* Now configure. */ + INSIST(DNS_KASP_VALID(kasp)); + + if (config != NULL) { + koptions = cfg_tuple_get(config, "options"); + maps[i++] = koptions; + } + maps[i] = NULL; + + /* Configuration: Signatures */ + dns_kasp_setsigrefresh(kasp, get_duration(maps, "signatures-refresh", + DNS_KASP_SIG_REFRESH)); + dns_kasp_setsigvalidity(kasp, get_duration(maps, "signatures-validity", + DNS_KASP_SIG_VALIDITY)); + dns_kasp_setsigvalidity_dnskey(kasp, get_duration(maps, + "signatures-validity-dnskey", + DNS_KASP_SIG_VALIDITY_DNSKEY)); + + /* Configuration: Keys */ + dns_kasp_setdnskeyttl(kasp, get_duration(maps, "dnskey-ttl", + DNS_KASP_KEY_TTL)); + dns_kasp_setpublishsafety(kasp, get_duration(maps, "publish-safety", + DNS_KASP_PUBLISH_SAFETY)); + dns_kasp_setretiresafety(kasp, get_duration(maps, "retire-safety", + DNS_KASP_RETIRE_SAFETY)); + + (void)confget(maps, "keys", &keys); + if (keys == NULL) { + result = cfg_kaspkey_fromconfig(NULL, kasp); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + for (element = cfg_list_first(keys); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kobj = cfg_listelt_value(element); + result = cfg_kaspkey_fromconfig(kobj, kasp); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + } + ISC_INSIST(!(dns_kasp_keylist_empty(kasp))); + + /* Configuration: Zone settings */ + dns_kasp_setzonemaxttl(kasp, get_duration(maps, "zone-max-ttl", + DNS_KASP_ZONE_MAXTTL)); + dns_kasp_setzonepropagationdelay(kasp, get_duration(maps, + "zone-propagation-delay", + DNS_KASP_ZONE_PROPDELAY)); + + /* Configuration: Parent settings */ + dns_kasp_setdsttl(kasp, get_duration(maps, "parent-ds-ttl", + DNS_KASP_DS_TTL)); + dns_kasp_setparentpropagationdelay(kasp, get_duration(maps, + "parent-propagation-delay", + DNS_KASP_PARENT_PROPDELAY)); + dns_kasp_setparentregistrationdelay(kasp, get_duration(maps, + "parent-registration-delay", + DNS_KASP_PARENT_REGDELAY)); + + // TODO: Rest of the configuration + + /* Append it to the list for future lookups. */ + ISC_LIST_APPEND(*kasplist, kasp, link); + ISC_INSIST(!(ISC_LIST_EMPTY(*kasplist))); + + /* Success: Attach the kasp to the pointer and return. */ + dns_kasp_attach(kasp, kaspp); + /* Don't detach as kasp is on '*kasplist' */ + return (ISC_R_SUCCESS); + +cleanup: + + /* Something bad happened, detach (destroys kasp) and return error. */ + dns_kasp_detach(&kasp); + return (result); +} diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index d427ec488d..e0e5217d55 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -82,6 +83,7 @@ static cfg_type_t cfg_type_controls_sockaddr; static cfg_type_t cfg_type_destinationlist; static cfg_type_t cfg_type_dialuptype; static cfg_type_t cfg_type_dlz; +static cfg_type_t cfg_type_dnssecpolicy; static cfg_type_t cfg_type_dnstap; static cfg_type_t cfg_type_dnstapoutput; static cfg_type_t cfg_type_dyndb; @@ -121,7 +123,6 @@ static cfg_type_t cfg_type_sizeval; static cfg_type_t cfg_type_sockaddr4wild; static cfg_type_t cfg_type_sockaddr6wild; static cfg_type_t cfg_type_statschannels; -static cfg_type_t cfg_type_ttlval; static cfg_type_t cfg_type_view; static cfg_type_t cfg_type_viewopts; static cfg_type_t cfg_type_zone; @@ -411,6 +412,20 @@ static cfg_type_t cfg_type_zone = { &cfg_rep_tuple, zone_fields }; +/*% + * A dnssec-policy statement. + */ +static cfg_tuplefielddef_t dnssecpolicy_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "options", &cfg_type_dnssecpolicyopts, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_dnssecpolicy = { + "dnssec-policy", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, dnssecpolicy_fields +}; + /*% * A "category" clause in the "logging" statement. */ @@ -466,6 +481,55 @@ static cfg_type_t cfg_type_managedkey = { &cfg_rep_tuple, managedkey_fields }; +/*% + * DNSSEC key roles. + */ +static const char *dnsseckeyrole_enums[] = { "csk", "ksk", "zsk", NULL }; +static cfg_type_t cfg_type_dnsseckeyrole = { + "dnssec-key-role", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &dnsseckeyrole_enums +}; + +/*% + * DNSSEC key storage types. + */ +static const char *dnsseckeystore_enums[] = { "key-directory", NULL }; +static cfg_type_t cfg_type_dnsseckeystore = { + "dnssec-key-storage", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &dnsseckeystore_enums +}; + +/*% + * A dnssec key, as used in the "keys" statement in a "dnssec-policy". + */ +static keyword_type_t algorithm_kw = { "algorithm", &cfg_type_uint32 }; +static cfg_type_t cfg_type_algorithm = { + "algorithm", parse_keyvalue, print_keyvalue, + doc_keyvalue, &cfg_rep_uint32, &algorithm_kw +}; + +static keyword_type_t lifetime_kw = { "lifetime", &cfg_type_duration }; +static cfg_type_t cfg_type_lifetime = { + "lifetime", parse_keyvalue, print_keyvalue, + doc_keyvalue, &cfg_rep_duration, &lifetime_kw +}; + +static cfg_tuplefielddef_t kaspkey_fields[] = { + { "role", &cfg_type_dnsseckeyrole, 0 }, + { "keystore-type", &cfg_type_dnsseckeystore, 0 }, + { "lifetime", &cfg_type_lifetime, 0 }, + { "algorithm", &cfg_type_algorithm, 0 }, + { "length", &cfg_type_optional_uint32, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_kaspkey = { + "kaspkey", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, kaspkey_fields +}; + +/*% + * Wild class, type, name. + */ static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring }; static cfg_type_t cfg_type_optional_wild_class = { @@ -638,6 +702,14 @@ static cfg_type_t cfg_type_dnsseckeys = { cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_managedkey }; +/*% + * A list of key entries, used in a DNSSEC Key and Signing Policy. + */ +static cfg_type_t cfg_type_kaspkeys = { + "kaspkeys", cfg_parse_bracketed_list, cfg_print_bracketed_list, + cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_kaspkey +}; + static const char *forwardtype_enums[] = { "first", "only", NULL }; static cfg_type_t cfg_type_forwardtype = { "forwardtype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, @@ -963,6 +1035,7 @@ static cfg_clausedef_t namedconf_clauses[] = { { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI }, { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI }, + { "dnssec-policy", &cfg_type_dnssecpolicy, CFG_CLAUSEFLAG_MULTI }, { "logging", &cfg_type_logging, 0 }, { "lwres", &cfg_type_bracketed_text, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE }, @@ -1054,7 +1127,7 @@ options_clauses[] = { { "fstrm-set-output-notify-threshold", &cfg_type_uint32, 0 }, { "fstrm-set-output-queue-model", &cfg_type_fstrm_model, 0 }, { "fstrm-set-output-queue-size", &cfg_type_uint32, 0 }, - { "fstrm-set-reopen-interval", &cfg_type_ttlval, 0 }, + { "fstrm-set-reopen-interval", &cfg_type_duration, 0 }, #else { "fstrm-set-buffer-hint", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, @@ -1068,7 +1141,7 @@ options_clauses[] = { CFG_CLAUSEFLAG_NOTCONFIGURED }, { "fstrm-set-output-queue-size", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, - { "fstrm-set-reopen-interval", &cfg_type_ttlval, + { "fstrm-set-reopen-interval", &cfg_type_duration, CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif /* HAVE_DNSTAP */ #if defined(HAVE_GEOIP2) @@ -1083,7 +1156,7 @@ options_clauses[] = { { "host-statistics", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT }, { "host-statistics-max", &cfg_type_uint32, CFG_CLAUSEFLAG_ANCIENT }, { "hostname", &cfg_type_qstringornone, 0 }, - { "interface-interval", &cfg_type_ttlval, 0 }, + { "interface-interval", &cfg_type_duration, 0 }, { "keep-response-order", &cfg_type_bracketed_aml, 0 }, { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, { "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, @@ -1611,8 +1684,8 @@ static cfg_tuplefielddef_t rpz_zone_fields[] = { { "zone name", &cfg_type_rpz_zone, 0 }, { "add-soa", &cfg_type_boolean, 0 }, { "log", &cfg_type_boolean, 0 }, - { "max-policy-ttl", &cfg_type_ttlval, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "max-policy-ttl", &cfg_type_duration, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { "policy", &cfg_type_rpz_policy, 0 }, { "recursive-only", &cfg_type_boolean, 0 }, { "nsip-enable", &cfg_type_boolean, 0 }, @@ -1633,8 +1706,8 @@ static cfg_tuplefielddef_t rpz_fields[] = { { "zone list", &cfg_type_rpz_list, 0 }, { "add-soa", &cfg_type_boolean, 0 }, { "break-dnssec", &cfg_type_boolean, 0 }, - { "max-policy-ttl", &cfg_type_ttlval, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "max-policy-ttl", &cfg_type_duration, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { "min-ns-dots", &cfg_type_uint32, 0 }, { "nsip-wait-recurse", &cfg_type_boolean, 0 }, { "qname-wait-recurse", &cfg_type_boolean, 0 }, @@ -1671,7 +1744,7 @@ static cfg_tuplefielddef_t catz_zone_fields[] = { { "default-masters", &cfg_type_namesockaddrkeylist, 0 }, { "zone-directory", &cfg_type_qstring, 0 }, { "in-memory", &cfg_type_boolean, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { NULL, NULL, 0 } }; static cfg_type_t cfg_type_catz_tuple = { @@ -1899,7 +1972,7 @@ view_clauses[] = { { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, { "glue-cache", &cfg_type_boolean, 0 }, { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 }, - { "lame-ttl", &cfg_type_ttlval, 0 }, + { "lame-ttl", &cfg_type_duration, 0 }, #ifdef HAVE_LMDB { "lmdb-mapsize", &cfg_type_sizeval, 0 }, #else @@ -1907,16 +1980,16 @@ view_clauses[] = { #endif { "max-acache-size", &cfg_type_sizenodefault, CFG_CLAUSEFLAG_OBSOLETE }, { "max-cache-size", &cfg_type_sizeorpercent, 0 }, - { "max-cache-ttl", &cfg_type_ttlval, 0 }, + { "max-cache-ttl", &cfg_type_duration, 0 }, { "max-clients-per-query", &cfg_type_uint32, 0 }, - { "max-ncache-ttl", &cfg_type_ttlval, 0 }, + { "max-ncache-ttl", &cfg_type_duration, 0 }, { "max-recursion-depth", &cfg_type_uint32, 0 }, { "max-recursion-queries", &cfg_type_uint32, 0 }, - { "max-stale-ttl", &cfg_type_ttlval, 0 }, + { "max-stale-ttl", &cfg_type_duration, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, { "message-compression", &cfg_type_boolean, 0 }, - { "min-cache-ttl", &cfg_type_ttlval, 0 }, - { "min-ncache-ttl", &cfg_type_ttlval, 0 }, + { "min-cache-ttl", &cfg_type_duration, 0 }, + { "min-ncache-ttl", &cfg_type_duration, 0 }, { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_ANCIENT }, { "minimal-any", &cfg_type_boolean, 0 }, { "minimal-responses", &cfg_type_minimal, 0 }, @@ -1924,8 +1997,8 @@ view_clauses[] = { { "no-case-compress", &cfg_type_bracketed_aml, 0 }, { "nocookie-udp-size", &cfg_type_uint32, 0 }, { "nosit-udp-size", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, - { "nta-lifetime", &cfg_type_ttlval, 0 }, - { "nta-recheck", &cfg_type_ttlval, 0 }, + { "nta-lifetime", &cfg_type_duration, 0 }, + { "nta-recheck", &cfg_type_duration, 0 }, { "nxdomain-redirect", &cfg_type_astring, 0 }, { "preferred-glue", &cfg_type_astring, 0 }, { "prefetch", &cfg_type_prefetch, 0 }, @@ -1955,10 +2028,10 @@ view_clauses[] = { { "root-key-sentinel", &cfg_type_boolean, 0 }, { "rrset-order", &cfg_type_rrsetorder, 0 }, { "send-cookie", &cfg_type_boolean, 0 }, - { "servfail-ttl", &cfg_type_ttlval, 0 }, + { "servfail-ttl", &cfg_type_duration, 0 }, { "sortlist", &cfg_type_bracketed_aml, 0 }, { "stale-answer-enable", &cfg_type_boolean, 0 }, - { "stale-answer-ttl", &cfg_type_ttlval, 0 }, + { "stale-answer-ttl", &cfg_type_duration, 0 }, { "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, { "synth-from-dnssec", &cfg_type_boolean, 0 }, { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT }, @@ -1998,6 +2071,26 @@ static cfg_type_t cfg_type_validityinterval = { &cfg_rep_tuple, validityinterval_fields }; +/*% + * Clauses that can be found in a 'dnssec-policy' statement. + */ +static cfg_clausedef_t +dnssecpolicy_clauses[] = { + { "dnskey-ttl", &cfg_type_duration, 0 }, + { "keys", &cfg_type_kaspkeys, 0 }, + { "publish-safety", &cfg_type_duration, 0 }, + { "retire-safety", &cfg_type_duration, 0 }, + { "signatures-refresh", &cfg_type_duration, 0 }, + { "signatures-validity", &cfg_type_duration, 0 }, + { "signatures-validity-dnskey", &cfg_type_duration, 0 }, + { "zone-max-ttl", &cfg_type_duration, 0 }, + { "zone-propagation-delay", &cfg_type_duration, 0 }, + { "parent-ds-ttl", &cfg_type_duration, 0 }, + { "parent-propagation-delay", &cfg_type_duration, 0 }, + { "parent-registration-delay", &cfg_type_duration, 0 }, + { NULL, NULL, 0 } +}; + /*% * Clauses that can be found in a 'zone' statement, * with defaults in the 'view' or 'options' statement. @@ -2072,6 +2165,9 @@ zone_clauses[] = { { "dnssec-loadkeys-interval", &cfg_type_uint32, CFG_ZONE_MASTER | CFG_ZONE_SLAVE }, + { "dnssec-policy", &cfg_type_astring, + CFG_ZONE_MASTER | CFG_ZONE_SLAVE + }, { "dnssec-secure-to-insecure", &cfg_type_boolean, CFG_ZONE_MASTER }, @@ -2346,6 +2442,16 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_zoneopts = { "zoneopts", cfg_parse_map, cfg_print_map, cfg_doc_map, &cfg_rep_map, zone_clausesets }; +/*% The "dnssec-policy" statement syntax. */ +static cfg_clausedef_t * +dnssecpolicy_clausesets[] = { + dnssecpolicy_clauses, + NULL +}; +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_dnssecpolicyopts = { + "dnssecpolicyopts", cfg_parse_map, cfg_print_map, + cfg_doc_map, &cfg_rep_map, dnssecpolicy_clausesets }; + /*% The "dynamically loadable zones" statement syntax. */ static cfg_clausedef_t @@ -3761,54 +3867,14 @@ static cfg_type_t cfg_type_masterselement = { doc_masterselement, NULL, NULL }; -static isc_result_t -parse_ttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { - isc_result_t result; - cfg_obj_t *obj = NULL; - uint32_t ttl; - - UNUSED(type); - - CHECK(cfg_gettoken(pctx, 0)); - if (pctx->token.type != isc_tokentype_string) { - result = ISC_R_UNEXPECTEDTOKEN; - goto cleanup; - } - - result = dns_ttl_fromtext(&pctx->token.value.as_textregion, &ttl); - if (result == ISC_R_RANGE ) { - cfg_parser_error(pctx, CFG_LOG_NEAR, "TTL out of range "); - return (result); - } else if (result != ISC_R_SUCCESS) - goto cleanup; - - CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj)); - obj->value.uint32 = ttl; - *ret = obj; - return (ISC_R_SUCCESS); - - cleanup: - cfg_parser_error(pctx, CFG_LOG_NEAR, - "expected integer and optional unit"); - return (result); -} - -/*% - * A TTL value (number + optional unit). - */ -static cfg_type_t cfg_type_ttlval = { - "ttlval", parse_ttlval, cfg_print_uint64, cfg_doc_terminal, - &cfg_rep_uint64, 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_ttlval, 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_ttlval); + cfg_doc_enum_or_other(pctx, type, &cfg_type_duration); } /*% diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c index 0c7987b881..849478d624 100644 --- a/lib/isccfg/parser.c +++ b/lib/isccfg/parser.c @@ -34,6 +34,8 @@ #include #include +#include + /* Shorthand */ #define CAT CFG_LOGCATEGORY_CONFIG #define MOD CFG_LOGMODULE_PARSER @@ -132,6 +134,7 @@ LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop }; LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_percentage = { "percentage", free_noop }; +LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_duration = { "duration", free_noop }; /* * Configuration type definitions. @@ -977,9 +980,353 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_uint64 = { &cfg_rep_uint64, NULL }; +/* + * Get the number of digits in a number. + */ +static size_t +numlen(uint32_t num) { + uint32_t period = num; + size_t count = 0; + + if (period == 0) { + return 1; + } + while (period > 0) { + count++; + period /= 10; + } + return (count); +} + +/* + * duration + */ +void +cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) { + char buf[CFG_DURATION_MAXLEN]; + char *str; + const char *indicators = "YMWDHMS"; + int count, i; + int durationlen[7]; + cfg_duration_t duration; + /* + * D ? The duration has a date part. + * T ? The duration has a time part. + */ + bool D = false, T = false; + + REQUIRE(pctx != NULL); + REQUIRE(obj != NULL); + + duration = obj->value.duration; + + /* If this is not an ISO 8601 duration, just print it as a number. */ + if (!duration.iso8601) { + return (cfg_print_rawuint(pctx, duration.parts[6])); + } + + /* Calculate length of string. */ + buf[0] = 'P'; + buf[1] = '\0'; + str = &buf[1]; + count = 2; + for (i = 0; i < 6; i++) { + if (duration.parts[i] > 0) { + durationlen[i] = 1 + numlen(duration.parts[i]); + if (i < 4) { + D = true; + } else { + T = true; + } + } else { + durationlen[i] = 0; + } + count += durationlen[i]; + } + /* + * Special case for seconds which is not taken into account in the + * above for loop: Count the length of the seconds part if it is + * non-zero, or if all the other parts are also zero. In the latter + * case this function will print "PT0S". + */ + if (duration.parts[6] > 0 || + (!D && !duration.parts[4] && !duration.parts[5])) { + durationlen[6] = 1 + numlen(duration.parts[6]); + T = true; + count += durationlen[6]; + } + /* Add one character for the time indicator. */ + if (T) { + count++; + } + INSIST(count < CFG_DURATION_MAXLEN); + + /* Now print the duration. */ + for (i = 0; i < 6; i++) { + /* + * We don't check here if weeks and other time indicator are + * used mutually exclusively. + */ + if (duration.parts[i] > 0) { + snprintf(str, durationlen[i]+2, "%u%c", + (uint32_t) duration.parts[i], indicators[i]); + str += durationlen[i]+1; + } + if (i == 3 && T) { + snprintf(str, 2, "T"); + str += 1; + } + + } + /* Special case for seconds. */ + if (duration.parts[6] > 0 || + (!D && !duration.parts[4] && !duration.parts[3])) { + snprintf(str, durationlen[6]+2, "%u%c", + (uint32_t) duration.parts[6], indicators[6]); + } + cfg_print_chars(pctx, buf, strlen(buf)); +} + +bool +cfg_obj_isduration(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (obj->type->rep == &cfg_rep_duration); +} + +uint32_t +cfg_obj_asduration(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_duration); + uint32_t duration = 0; + duration += obj->value.duration.parts[6]; // Seconds + duration += obj->value.duration.parts[5]*60; // Minutes + duration += obj->value.duration.parts[4]*3600; // Hours + duration += obj->value.duration.parts[3]*86400; // Days + duration += obj->value.duration.parts[2]*86400*7; // Weaks + /* + * The below additions are not entirely correct + * because days may very per month and per year. + */ + duration += obj->value.duration.parts[1]*86400*31; // Months + duration += obj->value.duration.parts[0]*86400*365; // Years + return (duration); +} + +/* + * duration_fromtext initially taken from OpenDNSSEC code base. + * Modified to fit the BIND 9 code. + * + * Copyright (c) 2009-2018 NLNet Labs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +static isc_result_t +duration_fromtext(isc_textregion_t *source, cfg_duration_t *duration) { + char buf[CFG_DURATION_MAXLEN]; + char *P, *X, *T, *W, *str; + bool not_weeks = false; + int i; + + /* + * Copy the buffer as it may not be NULL terminated. + * Anyone having a duration longer than 63 characters is crazy. + */ + if (source->length > sizeof(buf) - 1) { + return (ISC_R_BADNUMBER); + } + /* Copy source->length bytes and NULL terminate. */ + snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base); + str = buf; + + /* Clear out duration. */ + for (i = 0; i < 7; i++) { + duration->parts[i] = 0; + } + + /* Every duration starts with 'P' */ + P = strchr(str, 'P'); + if (!P) { + return (ISC_R_BADNUMBER); + } + + /* Record the time indicator. */ + T = strchr(str, 'T'); + + /* Record years. */ + X = strchr(str, 'Y'); + if (X) { + duration->parts[0] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record months. */ + X = strchr(str, 'M'); + /* + * M could be months or minutes. This is months if there is no time + * part, or this M indicator is before the time indicator. + */ + if (X && (!T || (size_t) (X-P) < (size_t) (T-P))) { + duration->parts[1] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record days. */ + X = strchr(str, 'D'); + if (X) { + duration->parts[3] = atoi(str+1); + str = X; + not_weeks = true; + } + + /* Time part? */ + if (T) { + str = T; + not_weeks = true; + } + + /* Record hours. */ + X = strchr(str, 'H'); + if (X && T) { + duration->parts[4] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record minutes. */ + X = strrchr(str, 'M'); + /* + * M could be months or minutes. This is minutes if there is a time + * part and the M indicator is behind the time indicator. + */ + if (X && T && (size_t) (X-P) > (size_t) (T-P)) { + duration->parts[5] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record seconds. */ + X = strchr(str, 'S'); + if (X && T) { + duration->parts[6] = atoi(str+1); + str = X; + not_weeks = true; + } + + /* Or is the duration configured in weeks? */ + W = strchr(buf, 'W'); + if (W) { + if (not_weeks) { + /* Mix of weeks and other indicators is not allowed */ + return (ISC_R_BADNUMBER); + } else { + duration->parts[2] = atoi(str+1); + str = W; + } + } + + /* Deal with trailing garbage. */ + if (str[1] != '\0') { + return (ISC_R_BADNUMBER); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +cfg_parse_duration(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 (TOKEN_STRING(pctx)[0] == 'P') { + result = duration_fromtext(&pctx->token.value.as_textregion, + &duration); + duration.iso8601 = true; + } else { + uint32_t ttl; + result = dns_ttl_fromtext(&pctx->token.value.as_textregion, + &ttl); + /* + * 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; + duration.parts[6] = ttl; + duration.iso8601 = false; + } + if (result == ISC_R_RANGE) { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "duration or TTL out of range"); + return (result); + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj)); + obj->value.duration = duration; + *ret = obj; + return (ISC_R_SUCCESS); + +cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected ISO 8601 duration or TTL value"); + 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. + * - Y is the year indicator that follows the value for the number of years. + * - M is the month indicator that follows the value for the number of months. + * - D is the day indicator that follows the value for the number of days. + * - T is the time indicator that precedes the time components. + * - H is the hour indicator that follows the value for the number of hours. + * - M is the minute indicator that follows the value for the number of + * minutes. + * - S is the second indicator that follows the value for the number of + * seconds. + * + * A duration can also be a TTL value (number + optional unit). + */ +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration = { + "duration", cfg_parse_duration, cfg_print_duration, cfg_doc_terminal, + &cfg_rep_duration, NULL +}; + /* * qstring (quoted string), ustring (unquoted string), astring - * (any string) + * (any string), sstring (secret string) */ /* Create a string object from a null-terminated C string. */ diff --git a/lib/isccfg/tests/Kyuafile b/lib/isccfg/tests/Kyuafile index dc14aace41..89d50352f2 100644 --- a/lib/isccfg/tests/Kyuafile +++ b/lib/isccfg/tests/Kyuafile @@ -1,4 +1,5 @@ syntax(2) test_suite('bind9') +tap_test_program{name='duration_test'} tap_test_program{name='parser_test'} diff --git a/lib/isccfg/tests/Makefile.in b/lib/isccfg/tests/Makefile.in index 9e65962098..4517992e03 100644 --- a/lib/isccfg/tests/Makefile.in +++ b/lib/isccfg/tests/Makefile.in @@ -30,13 +30,18 @@ ISCCFGDEPLIBS = ../libisccfg.@A@ LIBS = @LIBS@ @CMOCKA_LIBS@ OBJS = -SRCS = parser_test.c +SRCS = duration_test.c parser_test.c SUBDIRS = -TARGETS = parser_test@EXEEXT@ +TARGETS = duration_test@EXEEXT@ parser_test@EXEEXT@ @BIND9_MAKE_RULES@ +duration_test@EXEEXT@: duration_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ duration_test.@O@ \ + ${ISCCFGLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + parser_test@EXEEXT@: parser_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ ${LDFLAGS} -o $@ parser_test.@O@ \ diff --git a/lib/isccfg/tests/duration_test.c b/lib/isccfg/tests/duration_test.c new file mode 100644 index 0000000000..2e0249bb07 --- /dev/null +++ b/lib/isccfg/tests/duration_test.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include +#include + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +isc_mem_t *mctx = NULL; +isc_log_t *lctx = NULL; +static isc_logcategory_t categories[] = { + { "", 0 }, + { "client", 0 }, + { "network", 0 }, + { "update", 0 }, + { "queries", 0 }, + { "unmatched", 0 }, + { "update-security", 0 }, + { "query-errors", 0 }, + { NULL, 0 } +}; + +static void +cleanup() { + if (lctx != NULL) { + isc_log_destroy(&lctx); + } + if (mctx != NULL) { + isc_mem_destroy(&mctx); + } +} + +static isc_result_t +setup() { + isc_result_t result; + + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + isc_mem_create(&mctx); + + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + CHECK(isc_log_create(mctx, &lctx, &logconfig)); + isc_log_registercategories(lctx, categories); + isc_log_setcontext(lctx); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + CHECK(isc_log_createchannel(logconfig, "stderr", + ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, + &destination, 0)); + CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL)); + + return (ISC_R_SUCCESS); + + cleanup: + cleanup(); + return (result); +} + +struct duration_conf { + const char* string; + uint32_t time; +}; +typedef struct duration_conf duration_conf_t; + +/* test cfg_obj_asduration() */ +static void +cfg_obj_asduration_test(void **state) { + isc_result_t result; + duration_conf_t durations[] = { + { .string = "PT0S", .time = 0 }, + { .string = "PT42S", .time = 42 }, + { .string = "PT10M", .time = 600 }, + { .string = "PT10M4S", .time = 604 }, + { .string = "PT2H", .time = 7200 }, + { .string = "PT2H3S", .time = 7203 }, + { .string = "PT2H1M3S", .time = 7263 }, + { .string = "P7D", .time = 604800 }, + { .string = "P7DT2H", .time = 612000 }, + { .string = "P2W", .time = 1209600 }, + { .string = "P3M", .time = 8035200 }, + { .string = "P3MT10M", .time = 8035800 }, + { .string = "P5Y", .time = 157680000 }, + { .string = "P5YT2H", .time = 157687200 }, + { .string = "P1Y1M1DT1H1M1S", .time = 34304461 }, + { .string = "0", .time = 0 }, + { .string = "30", .time = 30 }, + { .string = "42s", .time = 42 }, + { .string = "10m", .time = 600 }, + { .string = "2h", .time = 7200 }, + { .string = "7d", .time = 604800 }, + { .string = "2w", .time = 1209600 }, + }; + int num = 22; + isc_buffer_t buf1; + cfg_parser_t *p1 = NULL; + cfg_obj_t *c1 = NULL; + + UNUSED(state); + + setup(); + + for (int i = 0; i < num; i++) { + const cfg_listelt_t *element; + const cfg_obj_t *kasps = NULL; + char conf[64]; + sprintf(&conf[0], + "dnssec-policy \"dp\"\n{\nsignatures-refresh %s;\n};\n", + durations[i].string); + + isc_buffer_init(&buf1, conf, strlen(conf) - 1); + isc_buffer_add(&buf1, strlen(conf) - 1); + + /* Parse with default line numbering */ + result = cfg_parser_create(mctx, lctx, &p1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = cfg_parse_buffer(p1, &buf1, "text1", 0, + &cfg_type_namedconf, 0, &c1); + assert_int_equal(result, ISC_R_SUCCESS); + + (void)cfg_map_get(c1, "dnssec-policy", &kasps); + assert_non_null(kasps); + for (element = cfg_list_first(kasps); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *d1 = NULL; + const cfg_obj_t *kopts = NULL; + cfg_obj_t *kconf = cfg_listelt_value(element); + assert_non_null(kconf); + + kopts = cfg_tuple_get(kconf, "options"); + result = cfg_map_get(kopts, "signatures-refresh", &d1); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(durations[i].time, + cfg_obj_asduration(d1)); + } + + cfg_obj_destroy(p1, &c1); + cfg_parser_destroy(&p1); + } + + cleanup(); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(cfg_obj_asduration_test), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif diff --git a/lib/isccfg/win32/libisccfg.def b/lib/isccfg/win32/libisccfg.def index 96039fbf04..1c40b3c34b 100644 --- a/lib/isccfg/win32/libisccfg.def +++ b/lib/isccfg/win32/libisccfg.def @@ -24,6 +24,7 @@ cfg_doc_tuple cfg_doc_void cfg_gettoken cfg_is_enum +cfg_kasp_fromconfig cfg_list_first cfg_list_length cfg_list_next @@ -36,6 +37,7 @@ cfg_map_get cfg_map_getname cfg_map_nextclause cfg_obj_asboolean +cfg_obj_asduration cfg_obj_asfixedpoint cfg_obj_asnetprefix cfg_obj_aspercentage @@ -48,6 +50,7 @@ cfg_obj_destroy cfg_obj_file cfg_obj_getdscp cfg_obj_isboolean +cfg_obj_isduration cfg_obj_isfixedpoint cfg_obj_islist cfg_obj_ismap @@ -68,6 +71,7 @@ cfg_parse_boolean cfg_parse_bracketed_list cfg_parse_buffer cfg_parse_dscp +cfg_parse_duration cfg_parse_enum cfg_parse_enum_or_other cfg_parse_file @@ -107,6 +111,7 @@ cfg_print_bracketed_list cfg_print_chars cfg_print_clauseflags cfg_print_cstr +cfg_print_duration cfg_print_fixedpoint cfg_print_grammar cfg_print_indent @@ -131,6 +136,7 @@ cfg_ungettoken ; Exported Data ;cfg_rep_boolean +;cfg_rep_duration ;cfg_rep_fixedpoint ;cfg_rep_list ;cfg_rep_map diff --git a/lib/isccfg/win32/libisccfg.vcxproj.filters.in b/lib/isccfg/win32/libisccfg.vcxproj.filters.in index 46b4e54bc5..91d4202d7e 100644 --- a/lib/isccfg/win32/libisccfg.vcxproj.filters.in +++ b/lib/isccfg/win32/libisccfg.vcxproj.filters.in @@ -30,6 +30,9 @@ Source Files + + Source Files + Source Files @@ -53,6 +56,9 @@ Header Files + + Header Files + Header Files @@ -63,4 +69,4 @@ Header Files - \ No newline at end of file + diff --git a/lib/isccfg/win32/libisccfg.vcxproj.in b/lib/isccfg/win32/libisccfg.vcxproj.in index a900a11e96..584341f82f 100644 --- a/lib/isccfg/win32/libisccfg.vcxproj.in +++ b/lib/isccfg/win32/libisccfg.vcxproj.in @@ -116,6 +116,7 @@ + @@ -127,6 +128,7 @@ + diff --git a/util/copyrights b/util/copyrights index 5671c1c42a..9d5aa28c46 100644 --- a/util/copyrights +++ b/util/copyrights @@ -694,6 +694,14 @@ ./bin/tests/system/ixfr/prereq.sh SH 2001,2004,2007,2012,2014,2016,2018,2019 ./bin/tests/system/ixfr/setup.sh SH 2001,2004,2007,2011,2012,2013,2014,2016,2018,2019 ./bin/tests/system/ixfr/tests.sh SH 2001,2004,2007,2011,2012,2014,2016,2018,2019 +./bin/tests/system/kasp/README TXT.BRIEF 2019 +./bin/tests/system/kasp/clean.sh SH 2019 +./bin/tests/system/kasp/ns2/setup.sh SH 2019 +./bin/tests/system/kasp/ns3/setup.sh SH 2019 +./bin/tests/system/kasp/ns4/setup.sh SH 2019 +./bin/tests/system/kasp/ns5/setup.sh SH 2019 +./bin/tests/system/kasp/setup.sh SH 2019 +./bin/tests/system/kasp/tests.sh SH 2019 ./bin/tests/system/keepalive/clean.sh SH 2017,2018,2019 ./bin/tests/system/keepalive/expected X 2017,2018,2019 ./bin/tests/system/keepalive/setup.sh SH 2017,2018,2019 @@ -1410,6 +1418,7 @@ ./doc/arm/delegation-only.zoneopt.xml SGML 2018,2019 ./doc/arm/dlz.xml SGML 2012,2013,2014,2015,2016,2018,2019 ./doc/arm/dnssec-keys.grammar.xml SGML 2019 +./doc/arm/dnssec-policy.grammar.xml SGML 2019 ./doc/arm/dnssec.xml SGML 2010,2011,2015,2016,2017,2018,2019 ./doc/arm/dyndb.xml SGML 2015,2016,2018,2019 ./doc/arm/forward.zoneopt.xml SGML 2018,2019 @@ -1499,6 +1508,7 @@ ./doc/design/db_rules TXT.BRIEF 1999,2000,2001,2004,2016,2018,2019 ./doc/design/decompression TXT.BRIEF 1999,2000,2001,2004,2016,2018,2019 ./doc/design/dispatch TXT.BRIEF 2000,2001,2004,2016,2018,2019 +./doc/design/dnssec-policy TXT.BRIEF 2019 ./doc/design/dscp TXT.BRIEF 2013,2016,2018,2019 ./doc/design/keydone TXT.BRIEF 2011,2016,2018,2019 ./doc/design/logging TXT.BRIEF 1999,2000,2001,2004,2016,2018,2019 @@ -1675,8 +1685,10 @@ ./lib/dns/include/dns/ipkeylist.h C 2016,2018,2019 ./lib/dns/include/dns/iptable.h C 2007,2012,2014,2016,2018,2019 ./lib/dns/include/dns/journal.h C 1999,2000,2001,2004,2005,2006,2007,2008,2009,2011,2013,2016,2017,2018,2019 +./lib/dns/include/dns/kasp.h C 2019 ./lib/dns/include/dns/keydata.h C 2009,2016,2018,2019 ./lib/dns/include/dns/keyflags.h C 1999,2000,2001,2004,2005,2006,2007,2016,2018,2019 +./lib/dns/include/dns/keymgr.h C 2019 ./lib/dns/include/dns/keytable.h C 2000,2001,2004,2005,2007,2009,2010,2014,2015,2016,2017,2018,2019 ./lib/dns/include/dns/keyvalues.h C 1999,2000,2001,2003,2004,2005,2006,2007,2008,2009,2010,2012,2016,2017,2018,2019 ./lib/dns/include/dns/lib.h C 1999,2000,2001,2004,2005,2006,2007,2009,2016,2017,2018,2019 @@ -1742,8 +1754,10 @@ ./lib/dns/ipkeylist.c C 2016,2018,2019 ./lib/dns/iptable.c C 2007,2008,2009,2013,2014,2016,2017,2018,2019 ./lib/dns/journal.c C 1999,2000,2001,2002,2004,2005,2007,2008,2009,2010,2011,2013,2014,2015,2016,2017,2018,2019 +./lib/dns/kasp.c C 2019 ./lib/dns/key.c C 2001,2004,2005,2006,2007,2011,2016,2018,2019 ./lib/dns/keydata.c C 2009,2014,2016,2018,2019 +./lib/dns/keymgr.c C 2019 ./lib/dns/keytable.c C 2000,2001,2004,2005,2007,2009,2010,2013,2014,2015,2016,2017,2018,2019 ./lib/dns/lib.c C 1999,2000,2001,2004,2005,2007,2009,2013,2014,2015,2016,2017,2018,2019 ./lib/dns/log.c C 1999,2000,2001,2003,2004,2005,2006,2007,2009,2011,2012,2013,2014,2015,2016,2017,2018,2019 @@ -2433,13 +2447,16 @@ ./lib/isccfg/include/isccfg/cfg.h C 2000,2001,2002,2004,2005,2006,2007,2010,2013,2014,2015,2016,2018,2019 ./lib/isccfg/include/isccfg/dnsconf.h C 2009,2016,2018,2019 ./lib/isccfg/include/isccfg/grammar.h C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2013,2014,2015,2016,2017,2018,2019 +./lib/isccfg/include/isccfg/kaspconf.h C 2019 ./lib/isccfg/include/isccfg/log.h C 2001,2004,2005,2006,2007,2009,2016,2018,2019 ./lib/isccfg/include/isccfg/namedconf.h C 2002,2004,2005,2006,2007,2009,2010,2014,2016,2018,2019 ./lib/isccfg/include/isccfg/version.h C 2001,2004,2005,2006,2007,2016,2018,2019 +./lib/isccfg/kaspconf.c C 2019 ./lib/isccfg/log.c C 2001,2004,2005,2006,2007,2016,2018,2019 ./lib/isccfg/namedconf.c C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/parser.c C 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/tests/Kyuafile X 2017,2018,2019 +./lib/isccfg/tests/duration_test.c C 2019 ./lib/isccfg/tests/parser_test.c C 2016,2018,2019 ./lib/isccfg/version.c C 1998,1999,2000,2001,2004,2005,2007,2016,2018,2019 ./lib/isccfg/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019