diff --git a/CHANGES b/CHANGES index 2d7fbb2679..a783585db6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +2943. [func] Add support to load new keys into managed zones + without signing immediately with "rndc loadkeys". + Add support to link keys with "dnssec-keygen -S" + and "dnssec-settime -S". [RT #21351] + 2942. [contrib] zone2sqlite failed to setup the entropy sources. [RT #21610] diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 28ed05adea..5a7aeb688f 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -29,7 +29,7 @@ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: dnssec-keygen.c,v 1.112 2010/01/19 23:48:55 tbox Exp $ */ +/* $Id: dnssec-keygen.c,v 1.113 2010/08/16 22:21:06 marka Exp $ */ /*! \file */ @@ -92,27 +92,27 @@ usage(void) { "NSEC3RSASHA1 if using -3)\n"); fprintf(stderr, " -3: use NSEC3-capable algorithm\n"); fprintf(stderr, " -b :\n"); - fprintf(stderr, " RSAMD5:\t[512..%d]\n", MAX_RSA); - fprintf(stderr, " RSASHA1:\t[512..%d]\n", MAX_RSA); - fprintf(stderr, " NSEC3RSASHA1:\t[512..%d]\n", MAX_RSA); - fprintf(stderr, " RSASHA256:\t[512..%d]\n", MAX_RSA); - fprintf(stderr, " RSASHA512:\t[1024..%d]\n", MAX_RSA); - fprintf(stderr, " DH:\t\t[128..4096]\n"); - fprintf(stderr, " DSA:\t\t[512..1024] and divisible by 64\n"); - fprintf(stderr, " NSEC3DSA:\t[512..1024] and divisible " + fprintf(stderr, " RSAMD5:\t[512..%d]\n", MAX_RSA); + fprintf(stderr, " RSASHA1:\t[512..%d]\n", MAX_RSA); + fprintf(stderr, " NSEC3RSASHA1:\t[512..%d]\n", MAX_RSA); + fprintf(stderr, " RSASHA256:\t[512..%d]\n", MAX_RSA); + fprintf(stderr, " RSASHA512:\t[1024..%d]\n", MAX_RSA); + fprintf(stderr, " DH:\t\t[128..4096]\n"); + fprintf(stderr, " DSA:\t\t[512..1024] and divisible by 64\n"); + fprintf(stderr, " NSEC3DSA:\t[512..1024] and divisible " "by 64\n"); - fprintf(stderr, " HMAC-MD5:\t[1..512]\n"); - fprintf(stderr, " HMAC-SHA1:\t[1..160]\n"); - fprintf(stderr, " HMAC-SHA224:\t[1..224]\n"); - fprintf(stderr, " HMAC-SHA256:\t[1..256]\n"); - fprintf(stderr, " HMAC-SHA384:\t[1..384]\n"); - fprintf(stderr, " HMAC-SHA512:\t[1..512]\n"); + fprintf(stderr, " HMAC-MD5:\t[1..512]\n"); + fprintf(stderr, " HMAC-SHA1:\t[1..160]\n"); + fprintf(stderr, " HMAC-SHA224:\t[1..224]\n"); + fprintf(stderr, " HMAC-SHA256:\t[1..256]\n"); + fprintf(stderr, " HMAC-SHA384:\t[1..384]\n"); + fprintf(stderr, " HMAC-SHA512:\t[1..512]\n"); fprintf(stderr, " (if using the default algorithm, key size\n" " defaults to 2048 for KSK, or 1024 for all " "others)\n"); fprintf(stderr, " -n : ZONE | HOST | ENTITY | " "USER | OTHER\n"); - fprintf(stderr, " (DNSKEY generation defaults to ZONE)\n"); + fprintf(stderr, " (DNSKEY generation defaults to ZONE)\n"); fprintf(stderr, " -c : (default: IN)\n"); fprintf(stderr, " -d (0 => max, default)\n"); #ifdef USE_PKCS11 @@ -136,7 +136,7 @@ usage(void) { fprintf(stderr, " -h: print usage and exit\n"); fprintf(stderr, " -m :\n"); - fprintf(stderr, " usage | trace | record | size | mctx\n"); + fprintf(stderr, " usage | trace | record | size | mctx\n"); fprintf(stderr, " -v : set verbosity level (0 - 10)\n"); fprintf(stderr, "Timing options:\n"); fprintf(stderr, " -P date/[+-]offset/none: set key publication date " @@ -151,6 +151,11 @@ usage(void) { fprintf(stderr, " -G: generate key only; do not set -P or -A\n"); fprintf(stderr, " -C: generate a backward-compatible key, omitting " "all dates\n"); + fprintf(stderr, " -S : generate a successor to an existing " + "key\n"); + fprintf(stderr, " -i : prepublication interval for " + "successor key " + "(default: 30 days)\n"); fprintf(stderr, "Output:\n"); fprintf(stderr, " K++.key, " "K++.private\n"); @@ -190,7 +195,7 @@ progress(int p) int main(int argc, char **argv) { - char *algname = NULL, *nametype = NULL, *type = NULL; + char *algname = NULL, *nametype = NULL, *type = NULL; char *classname = NULL; char *endp; dst_key_t *key = NULL; @@ -207,6 +212,8 @@ main(int argc, char **argv) { 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; isc_entropy_t *ectx = NULL; @@ -222,6 +229,7 @@ main(int argc, char **argv) { isc_stdtime_t publish = 0, activate = 0, revoke = 0; isc_stdtime_t inactive = 0, delete = 0; isc_stdtime_t now; + int prepub = -1; isc_boolean_t setpub = ISC_FALSE, setact = ISC_FALSE; isc_boolean_t setrev = ISC_FALSE, setinact = ISC_FALSE; isc_boolean_t setdel = ISC_FALSE; @@ -243,7 +251,7 @@ main(int argc, char **argv) { /* * Process memory debugging argument first. */ -#define CMDLINE_FLAGS "3a:b:Cc:d:E:eFf:g:K:km:n:p:qr:s:T:t:v:hGP:A:R:I:D:" +#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:km:n:P:p:qR:r:S:s:T:t:v:" while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case 'm': @@ -436,6 +444,12 @@ main(int argc, char **argv) { unsetdel = ISC_TRUE; } break; + case 'S': + predecessor = isc_commandline_argument; + break; + case 'i': + prepub = strtottl(isc_commandline_argument); + break; case 'F': /* Reserved for FIPS mode */ /* FALLTHROUGH */ @@ -467,87 +481,205 @@ main(int argc, char **argv) { setup_logging(verbose, mctx, &log); - if (argc < isc_commandline_index + 1) - fatal("the key name was not specified"); - if (argc > isc_commandline_index + 1) - fatal("extraneous arguments"); + if (predecessor == NULL) { + if (prepub == -1) + prepub = 0; - if (algname == NULL) { - use_default = ISC_TRUE; - if (use_nsec3) - algname = strdup(DEFAULT_NSEC3_ALGORITHM); - else - algname = strdup(DEFAULT_ALGORITHM); - if (verbose > 0) - fprintf(stderr, "no algorithm specified; " - "defaulting to %s\n", algname); - } + if (argc < isc_commandline_index + 1) + fatal("the key name was not specified"); + if (argc > isc_commandline_index + 1) + fatal("extraneous arguments"); - if (strcasecmp(algname, "RSA") == 0) { - fprintf(stderr, "The use of RSA (RSAMD5) is not recommended.\n" - "If you still wish to use RSA (RSAMD5) please " - "specify \"-a RSAMD5\"\n"); - return (1); - } else if (strcasecmp(algname, "HMAC-MD5") == 0) { - options |= DST_TYPE_KEY; - alg = DST_ALG_HMACMD5; - } else if (strcasecmp(algname, "HMAC-SHA1") == 0) { - options |= DST_TYPE_KEY; - alg = DST_ALG_HMACSHA1; - } else if (strcasecmp(algname, "HMAC-SHA224") == 0) { - options |= DST_TYPE_KEY; - alg = DST_ALG_HMACSHA224; - } else if (strcasecmp(algname, "HMAC-SHA256") == 0) { - options |= DST_TYPE_KEY; - alg = DST_ALG_HMACSHA256; - } else if (strcasecmp(algname, "HMAC-SHA384") == 0) { - options |= DST_TYPE_KEY; - alg = DST_ALG_HMACSHA384; - } else if (strcasecmp(algname, "HMAC-SHA512") == 0) { - options |= DST_TYPE_KEY; - alg = DST_ALG_HMACSHA512; - } else { - r.base = algname; - r.length = strlen(algname); - ret = dns_secalg_fromtext(&alg, &r); + dns_fixedname_init(&fname); + name = dns_fixedname_name(&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("unknown algorithm %s", algname); - if (alg == DST_ALG_DH) - options |= DST_TYPE_KEY; - } + fatal("invalid key name %s: %s", + argv[isc_commandline_index], + isc_result_totext(ret)); - if (use_nsec3 && - alg != DST_ALG_NSEC3DSA && alg != DST_ALG_NSEC3RSASHA1 && - alg != DST_ALG_RSASHA256 && alg!= DST_ALG_RSASHA512) { - fatal("%s is incompatible with NSEC3; " - "do not use the -3 option", algname); - } - - 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 (size < 0) { - if (use_default) { - size = ((kskflag & DNS_KEYFLAG_KSK) != 0) ? 2048 : 1024; + if (algname == NULL) { + use_default = ISC_TRUE; + if (use_nsec3) + algname = strdup(DEFAULT_NSEC3_ALGORITHM); + else + algname = strdup(DEFAULT_ALGORITHM); if (verbose > 0) - fprintf(stderr, "key size not specified; " - "defaulting to %d\n", size); - } else { - fatal("key size not specified (-b option)"); + fprintf(stderr, "no algorithm specified; " + "defaulting to %s\n", algname); } + + if (strcasecmp(algname, "RSA") == 0) { + fprintf(stderr, "The use of RSA (RSAMD5) is not " + "recommended.\nIf you still wish to " + "use RSA (RSAMD5) please specify " + "\"-a RSAMD5\"\n"); + return (1); + } else if (strcasecmp(algname, "HMAC-MD5") == 0) + alg = DST_ALG_HMACMD5; + else if (strcasecmp(algname, "HMAC-SHA1") == 0) + alg = DST_ALG_HMACSHA1; + else if (strcasecmp(algname, "HMAC-SHA224") == 0) + alg = DST_ALG_HMACSHA224; + else if (strcasecmp(algname, "HMAC-SHA256") == 0) + alg = DST_ALG_HMACSHA256; + else if (strcasecmp(algname, "HMAC-SHA384") == 0) + alg = DST_ALG_HMACSHA384; + else if (strcasecmp(algname, "HMAC-SHA512") == 0) + alg = DST_ALG_HMACSHA512; + else { + r.base = algname; + r.length = strlen(algname); + ret = dns_secalg_fromtext(&alg, &r); + if (ret != ISC_R_SUCCESS) + fatal("unknown algorithm %s", algname); + if (alg == DST_ALG_DH) + options |= DST_TYPE_KEY; + } + + if (use_nsec3 && + alg != DST_ALG_NSEC3DSA && alg != DST_ALG_NSEC3RSASHA1 && + alg != DST_ALG_RSASHA256 && alg!= DST_ALG_RSASHA512) { + fatal("%s is incompatible with NSEC3; " + "do not use the -3 option", algname); + } + + 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 (size < 0) { + if (use_default) { + if ((kskflag & DNS_KEYFLAG_KSK) != 0) + size = 2048; + else + size = 1024; + if (verbose > 0) + fprintf(stderr, "key size not " + "specified; defaulting " + "to %d\n", size); + } else { + fatal("key size not specified (-b option)"); + } + } + + 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 = ISC_TRUE; + publish = now; + activate = now + prepub; + } else if (setpub && !setact) { + setact = ISC_TRUE; + activate = publish + prepub; + } else if (setact && !setpub) { + setpub = ISC_TRUE; + publish = activate - prepub; + } + + if ((activate - prepub) < 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 (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", + filename, isc_result_totext(ret)); + if (!dst_key_isprivate(prevkey)) + fatal("%s is not a private key", filename); + + 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 = ISC_TRUE; } switch (alg) { @@ -572,6 +704,7 @@ main(int argc, char **argv) { fatal("invalid DSS key size: %d", size); break; case DST_ALG_HMACMD5: + options |= DST_TYPE_KEY; if (size < 1 || size > 512) fatal("HMAC-MD5 key size %d out of range", size); if (dbits != 0 && (dbits < 80 || dbits > 128)) @@ -581,6 +714,7 @@ main(int argc, char **argv) { dbits); break; case DST_ALG_HMACSHA1: + options |= DST_TYPE_KEY; if (size < 1 || size > 160) fatal("HMAC-SHA1 key size %d out of range", size); if (dbits != 0 && (dbits < 80 || dbits > 160)) @@ -590,6 +724,7 @@ main(int argc, char **argv) { dbits); break; case DST_ALG_HMACSHA224: + options |= DST_TYPE_KEY; if (size < 1 || size > 224) fatal("HMAC-SHA224 key size %d out of range", size); if (dbits != 0 && (dbits < 112 || dbits > 224)) @@ -599,6 +734,7 @@ main(int argc, char **argv) { dbits); break; case DST_ALG_HMACSHA256: + options |= DST_TYPE_KEY; if (size < 1 || size > 256) fatal("HMAC-SHA256 key size %d out of range", size); if (dbits != 0 && (dbits < 128 || dbits > 256)) @@ -608,6 +744,7 @@ main(int argc, char **argv) { dbits); break; case DST_ALG_HMACSHA384: + options |= DST_TYPE_KEY; if (size < 1 || size > 384) fatal("HMAC-384 key size %d out of range", size); if (dbits != 0 && (dbits < 192 || dbits > 384)) @@ -617,6 +754,7 @@ main(int argc, char **argv) { dbits); break; case DST_ALG_HMACSHA512: + options |= DST_TYPE_KEY; if (size < 1 || size > 512) fatal("HMAC-SHA512 key size %d out of range", size); if (dbits != 0 && (dbits < 256 || dbits > 512)) @@ -685,16 +823,6 @@ main(int argc, char **argv) { fatal("a key with algorithm '%s' cannot be a zone key", algname); - dns_fixedname_init(&fname); - name = dns_fixedname_name(&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)); - switch(alg) { case DNS_KEYALG_RSAMD5: case DNS_KEYALG_RSASHA1: @@ -763,9 +891,18 @@ main(int argc, char **argv) { /* * Set key timing metadata (unless using -C) * - * Publish and activation dates are set to "now" by default, - * but can be overridden. Creation date is always set to - * "now". + * 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); @@ -860,6 +997,8 @@ main(int argc, char **argv) { ret = dst_key_buildfilename(key, 0, NULL, &buf); printf("%s\n", filename); dst_key_free(&key); + if (prevkey != NULL) + dst_key_free(&prevkey); cleanup_logging(&log); cleanup_entropy(&ectx); diff --git a/bin/dnssec/dnssec-keygen.docbook b/bin/dnssec/dnssec-keygen.docbook index a246de2c2c..0701234777 100644 --- a/bin/dnssec/dnssec-keygen.docbook +++ b/bin/dnssec/dnssec-keygen.docbook @@ -18,7 +18,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + June 30, 2000 @@ -71,6 +71,7 @@ + @@ -78,6 +79,7 @@ + @@ -341,6 +343,21 @@ + + -S key + + + Create a new key which is an explicit successor to an + existing key. The name, algorithm, size, and type of the + key will be set to match the existing key. The activation + date of the new key will be set to the inactivation date of + the existing one. The publication date will be set to the + activation date minus the prepublication interval, which + defaults to 30 days. + + + + -s strength @@ -463,6 +480,34 @@ + + + -i interval + + + Sets the prepublication interval for a key. If set, then + the publication and activation dates must be separated by at least + this much time. If the activation date is specified but the + publication date isn't, then the publication date will default + to this much time before the activation date; conversely, if + the publication date is specified but activation date isn't, + then activation will be set to this much time after publication. + + + If the key is being created as an explicit successor to another + key, then the default prepublication interval is 30 days; + otherwise it is zero. + + + As with date offsets, if the argument is followed by one of + the suffixes 'y', 'mo', 'w', 'd', 'h', or 'mi', then the + interval is measured in years, months, weeks, days, hours, + or minutes, respectively. Without a suffix, the interval is + measured in seconds. + + + + diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c index fb98dda9d7..1e74a51878 100644 --- a/bin/dnssec/dnssec-settime.c +++ b/bin/dnssec/dnssec-settime.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: dnssec-settime.c,v 1.25 2010/02/03 01:02:37 each Exp $ */ +/* $Id: dnssec-settime.c,v 1.26 2010/08/16 22:21:06 marka Exp $ */ /*! \file */ @@ -117,20 +117,27 @@ printtime(dst_key_t *key, int type, const char *tag, isc_boolean_t epoch, int main(int argc, char **argv) { - isc_result_t result; + isc_result_t result; #ifdef USE_PKCS11 - const char *engine = "pkcs11"; + const char *engine = "pkcs11"; #else - const char *engine = NULL; + const char *engine = NULL; #endif - char *filename = NULL, *directory = NULL; - char newname[1024]; - char keystr[DST_KEY_FORMATSIZE]; - char *endp, *p; - int ch; - isc_entropy_t *ectx = NULL; - dst_key_t *key = NULL; - isc_buffer_t buf; + char *filename = NULL, *directory = NULL; + char newname[1024]; + char keystr[DST_KEY_FORMATSIZE]; + char *endp, *p; + int ch; + isc_entropy_t *ectx = NULL; + 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; + isc_uint16_t flags = 0; + int prepub = -1; isc_stdtime_t now; isc_stdtime_t pub = 0, act = 0, rev = 0, inact = 0, del = 0; isc_boolean_t setpub = ISC_FALSE, setact = ISC_FALSE; @@ -159,8 +166,8 @@ main(int argc, char **argv) { isc_stdtime_get(&now); - while ((ch = isc_commandline_parse(argc, argv, - "E:fK:uhp:v:P:A:R:I:D:")) != -1) { +#define CMDLINE_FLAGS "A:D:E:fhI:i:K:P:p:R:S:uv:" + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case 'E': engine = isc_commandline_argument; @@ -293,6 +300,12 @@ main(int argc, char **argv) { now, now); } break; + case 'S': + predecessor = isc_commandline_argument; + break; + case 'i': + prepub = strtottl(isc_commandline_argument); + break; case '?': if (isc_commandline_option != '?') fprintf(stderr, "%s: invalid argument -%c\n", @@ -314,17 +327,6 @@ main(int argc, char **argv) { if (argc > isc_commandline_index + 1) fatal("Extraneous arguments"); - if (directory != NULL) { - filename = argv[isc_commandline_index]; - } else { - result = isc_file_splitpath(mctx, argv[isc_commandline_index], - &directory, &filename); - if (result != ISC_R_SUCCESS) - fatal("cannot process filename %s: %s", - argv[isc_commandline_index], - isc_result_totext(result)); - } - if (ectx == NULL) setup_entropy(mctx, NULL, &ectx); result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE); @@ -337,6 +339,105 @@ main(int argc, char **argv) { isc_result_totext(result)); isc_entropy_stopcallbacksources(ectx); + if (predecessor != NULL) { + char keystr[DST_KEY_FORMATSIZE]; + isc_stdtime_t when; + int major, minor; + + if (prepub == -1) + prepub = (30 * 86400); + + if (setpub || unsetpub) + fatal("-S and -P cannot be used together"); + if (setact || unsetact) + fatal("-S and -A cannot be used together"); + + result = dst_key_fromnamedfile(predecessor, directory, + DST_TYPE_PUBLIC | + DST_TYPE_PRIVATE, + mctx, &prevkey); + if (result != ISC_R_SUCCESS) + fatal("Invalid keyfile %s: %s", + filename, isc_result_totext(result)); + if (!dst_key_isprivate(prevkey)) + fatal("%s is not a private key", filename); + + 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("Predecessor has incompatible format " + "version %d.%d\n\t", major, minor); + + result = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &when); + if (result != ISC_R_SUCCESS) + fatal("Predecessor has no activation date. " + "You must set one before\n\t" + "generating a successor."); + + result = dst_key_gettime(prevkey, DST_TIME_INACTIVE, &act); + if (result != ISC_R_SUCCESS) + fatal("Predecessor has no inactivation date. " + "You must set one before\n\t" + "generating a successor."); + + pub = act - prepub; + if (pub < now) + fatal("Predecessor will become inactive before the\n\t" + "prepublication period ends. Either change " + "its inactivation date,\n\t" + "or use the -i option to set a shorter " + "prepublication interval."); + + result = dst_key_gettime(prevkey, DST_TIME_DELETE, &when); + if (result != ISC_R_SUCCESS) + fprintf(stderr, "%s: WARNING: Predecessor has no " + "removal date;\n\t" + "it will remain in the zone " + "indefinitely after rollover.\n", + program); + + changed = setpub = setact = ISC_TRUE; + dst_key_free(&prevkey); + } else { + if (prepub < 0) + prepub = 0; + + if (prepub > 0) { + if (setpub && setact && (act - prepub) < pub) + fatal("Activation and publication dates " + "are closer together than the\n\t" + "prepublication interval."); + + if (setpub && !setact) { + setact = ISC_TRUE; + act = pub + prepub; + } else if (setact && !setpub) { + setpub = ISC_TRUE; + pub = act - prepub; + } + + if ((act - prepub) < now) + fatal("Time until activation is shorter " + "than the\n\tprepublication interval."); + } + } + + if (directory != NULL) { + filename = argv[isc_commandline_index]; + } else { + result = isc_file_splitpath(mctx, argv[isc_commandline_index], + &directory, &filename); + if (result != ISC_R_SUCCESS) + fatal("cannot process filename %s: %s", + argv[isc_commandline_index], + isc_result_totext(result)); + } + result = dst_key_fromnamedfile(filename, directory, DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, mctx, &key); @@ -349,6 +450,17 @@ main(int argc, char **argv) { dst_key_format(key, keystr, sizeof(keystr)); + if (predecessor != NULL) { + if (!dns_name_equal(name, dst_key_name(key))) + fatal("Key name mismatch"); + if (alg != dst_key_alg(key)) + fatal("Key algorithm mismatch"); + if (size != dst_key_size(key)) + fatal("Key size mismatch"); + if (flags != dst_key_flags(key)) + fatal("Key flags mismatch"); + } + if (force) set_keyversion(key); else diff --git a/bin/dnssec/dnssec-settime.docbook b/bin/dnssec/dnssec-settime.docbook index 9746b4588d..44cd9de1de 100644 --- a/bin/dnssec/dnssec-settime.docbook +++ b/bin/dnssec/dnssec-settime.docbook @@ -17,7 +17,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + July 15, 2009 @@ -211,6 +211,47 @@ + + -S predecessor key + + + Select a key for which the key being modified will be an + explicit successor. The name, algorithm, size, and type of the + predecessor key must exactly match those of the key being + modified. The activation date of the successor key will be set + to the inactivation date of the predecessor. The publication + date will be set to the activation date minus the prepublication + interval, which defaults to 30 days. + + + + + + -i interval + + + Sets the prepublication interval for a key. If set, then + the publication and activation dates must be separated by at least + this much time. If the activation date is specified but the + publication date isn't, then the publication date will default + to this much time before the activation date; conversely, if + the publication date is specified but activation date isn't, + then activation will be set to this much time after publication. + + + If the key is being set to be an explicit successor to another + key, then the default prepublication interval is 30 days; + otherwise it is zero. + + + As with date offsets, if the argument is followed by one of + the suffixes 'y', 'mo', 'w', 'd', 'h', or 'mi', then the + interval is measured in years, months, weeks, days, hours, + or minutes, respectively. Without a suffix, the interval is + measured in seconds. + + + diff --git a/bin/named/control.c b/bin/named/control.c index c34323f6de..e55135dc3f 100644 --- a/bin/named/control.c +++ b/bin/named/control.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: control.c,v 1.39 2010/07/11 00:12:57 each Exp $ */ +/* $Id: control.c,v 1.40 2010/08/16 22:21:06 marka Exp $ */ /*! \file */ @@ -189,8 +189,9 @@ ns_control_docommand(isccc_sexpr_t *message, isc_buffer_t *text) { result = ns_server_notifycommand(ns_g_server, command, text); } else if (command_compare(command, NS_COMMAND_VALIDATION)) { result = ns_server_validation(ns_g_server, command); - } else if (command_compare(command, NS_COMMAND_SIGN)) { - result = ns_server_sign(ns_g_server, command); + } else if (command_compare(command, NS_COMMAND_SIGN) || + command_compare(command, NS_COMMAND_LOADKEYS)) { + result = ns_server_rekey(ns_g_server, command); } else if (command_compare(command, NS_COMMAND_ADDZONE)) { result = ns_server_add_zone(ns_g_server, command); } else if (command_compare(command, NS_COMMAND_DELZONE)) { diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h index 378c78fd2e..24e59093b4 100644 --- a/bin/named/include/named/control.h +++ b/bin/named/include/named/control.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: control.h,v 1.30 2010/07/11 00:12:57 each Exp $ */ +/* $Id: control.h,v 1.31 2010/08/16 22:21:06 marka Exp $ */ #ifndef NAMED_CONTROL_H #define NAMED_CONTROL_H 1 @@ -59,6 +59,7 @@ #define NS_COMMAND_NOTIFY "notify" #define NS_COMMAND_VALIDATION "validation" #define NS_COMMAND_SIGN "sign" +#define NS_COMMAND_LOADKEYS "loadkeys" #define NS_COMMAND_ADDZONE "addzone" #define NS_COMMAND_DELZONE "delzone" diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 4a29e47f72..3da11a0847 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.h,v 1.108 2010/07/11 00:12:57 each Exp $ */ +/* $Id: server.h,v 1.109 2010/08/16 22:21:06 marka Exp $ */ #ifndef NAMED_SERVER_H #define NAMED_SERVER_H 1 @@ -295,11 +295,14 @@ ns_server_freeze(ns_server_t *server, isc_boolean_t freeze, char *args, isc_buffer_t *text); /*% - * Update a zone's DNSKEY set from the key repository, and re-sign the - * zone if there were any changes. + * Update a zone's DNSKEY set from the key repository. If + * the command that triggered the call to this function was "sign", + * then force a full signing of the zone. If it was "loadkeys", + * then don't sign the zone; any needed changes to signatures can + * take place incrementally. */ isc_result_t -ns_server_sign(ns_server_t *server, char *args); +ns_server_rekey(ns_server_t *server, char *args); /*% * Dump the current recursive queries. diff --git a/bin/named/server.c b/bin/named/server.c index ebed1194df..dc7a898a62 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.c,v 1.579 2010/08/13 14:33:31 fdupont Exp $ */ +/* $Id: server.c,v 1.580 2010/08/16 22:21:06 marka Exp $ */ /*! \file */ @@ -6466,14 +6466,18 @@ ns_server_tsiglist(ns_server_t *server, isc_buffer_t *text) { } /* - * Act on a "sign" command from the command channel. + * Act on a "sign" or "loadkeys" command from the command channel. */ isc_result_t -ns_server_sign(ns_server_t *server, char *args) { +ns_server_rekey(ns_server_t *server, char *args) { isc_result_t result; dns_zone_t *zone = NULL; dns_zonetype_t type; isc_uint16_t keyopts; + isc_boolean_t fullsign = ISC_FALSE; + + if (strncasecmp(args, NS_COMMAND_SIGN, strlen(NS_COMMAND_SIGN)) == 0) + fullsign = ISC_TRUE; result = zone_from_args(server, args, &zone, NULL); if (result != ISC_R_SUCCESS) @@ -6488,10 +6492,14 @@ ns_server_sign(ns_server_t *server, char *args) { } keyopts = dns_zone_getkeyopts(zone); - if ((keyopts & DNS_ZONEKEY_ALLOW) != 0) - dns_zone_rekey(zone); - else + + /* "rndc loadkeys" requires "auto-dnssec maintain". */ + if ((keyopts & DNS_ZONEKEY_ALLOW) == 0) result = ISC_R_NOPERM; + else if ((keyopts & DNS_ZONEKEY_MAINTAIN) == 0 && !fullsign) + result = ISC_R_NOPERM; + else + dns_zone_rekey(zone, fullsign); dns_zone_detach(&zone); return (result); diff --git a/bin/tests/system/autosign/ns1/root.db.in b/bin/tests/system/autosign/ns1/root.db.in index dae5ab8958..d94b3bb845 100644 --- a/bin/tests/system/autosign/ns1/root.db.in +++ b/bin/tests/system/autosign/ns1/root.db.in @@ -12,7 +12,7 @@ ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ; PERFORMANCE OF THIS SOFTWARE. -; $Id: root.db.in,v 1.5 2010/01/18 23:48:40 tbox Exp $ +; $Id: root.db.in,v 1.6 2010/08/16 22:21:06 marka Exp $ $TTL 30 . IN SOA a.root.servers.nil. each.isc.org. ( @@ -23,6 +23,7 @@ $TTL 30 600 ; minimum ) . NS a.root-servers.nil. +. TXT "root zone" a.root-servers.nil. A 10.53.0.1 example. NS ns2.example. diff --git a/bin/tests/system/autosign/tests.sh b/bin/tests/system/autosign/tests.sh index c441fdddd9..6da87b9e17 100644 --- a/bin/tests/system/autosign/tests.sh +++ b/bin/tests/system/autosign/tests.sh @@ -14,7 +14,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: tests.sh,v 1.10 2010/06/07 04:45:43 marka Exp $ +# $Id: tests.sh,v 1.11 2010/08/16 22:21:06 marka Exp $ SYSTEMTESTTOP=.. . $SYSTEMTESTTOP/conf.sh @@ -666,19 +666,19 @@ file="ns1/`cat vanishing.key`.private" rm -f $file echo "I:preparing ZSK roll" -newid=`sed 's/^K.+007+0*//' < standby.key` -file="ns1/`cat standby.key`.key" -$SETTIME -A now $file > /dev/null +oldfile=`cat active.key` oldid=`sed 's/^K.+007+0*//' < active.key` -file="ns1/`cat active.key`.key" -$SETTIME -I now -D now+10 $file > /dev/null +newfile=`cat standby.key` +newid=`sed 's/^K.+007+0*//' < standby.key` +$SETTIME -K ns1 -I now -D now+15 $oldfile > /dev/null +$SETTIME -K ns1 -i 0 -S $oldfile $newfile > /dev/null -$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 sign . 2>&1 | sed 's/^/I:ns1 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 loadkeys . 2>&1 | sed 's/^/I:ns1 /' echo "I:revoking key to duplicated key ID" -$SETTIME -R now ns2/Kbar.+005+30676.key > /dev/null +$SETTIME -R now -K ns2 Kbar.+005+30676.key > /dev/null -$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 sign bar. 2>&1 | sed 's/^/I:ns2 /' +$RNDC -c ../common/rndc.conf -s 10.53.0.2 -p 9953 loadkeys bar. 2>&1 | sed 's/^/I:ns2 /' echo "I:waiting for changes to take effect" sleep 5 @@ -691,6 +691,30 @@ n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +echo "I:checking former standby key has only signed incrementally ($n)" +ret=0 +$DIG $DIGOPTS txt . @10.53.0.1 > dig.out.ns1.test$n || ret=1 +grep 'RRSIG.*'" $newid "'\. ' dig.out.ns1.test$n > /dev/null && ret=1 +grep 'RRSIG.*'" $oldid "'\. ' dig.out.ns1.test$n > /dev/null || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:forcing full sign" +$RNDC -c ../common/rndc.conf -s 10.53.0.1 -p 9953 sign . 2>&1 | sed 's/^/I:ns1 /' + +echo "I:waiting for change to take effect" +sleep 5 + +echo "I:checking former standby key has now signed fully ($n)" +ret=0 +$DIG $DIGOPTS txt . @10.53.0.1 > dig.out.ns1.test$n || ret=1 +grep 'RRSIG.*'" $newid "'\. ' dig.out.ns1.test$n > /dev/null || ret=1 +grep 'RRSIG.*'" $oldid "'\. ' dig.out.ns1.test$n > /dev/null || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + echo "I:waiting for former active key to be removed" sleep 10 diff --git a/bin/tests/system/smartsign/tests.sh b/bin/tests/system/smartsign/tests.sh index 160fd84f89..923836c713 100644 --- a/bin/tests/system/smartsign/tests.sh +++ b/bin/tests/system/smartsign/tests.sh @@ -14,7 +14,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: tests.sh,v 1.5 2010/05/06 11:28:20 marka Exp $ +# $Id: tests.sh,v 1.6 2010/08/16 22:21:06 marka Exp $ SYSTEMTESTTOP=.. . $SYSTEMTESTTOP/conf.sh @@ -42,6 +42,13 @@ czsk3=`$KEYGEN -q -r $RANDFILE -A none $czone` # inactive czsk4=`$KEYGEN -q -r $RANDFILE -P now-24h -A now-24h -I now $czone` +# active in 12 hours, inactive 12 hours after that... +czsk5=`$KEYGEN -q -r $RANDFILE -P now+12h -A now+12h -I now+24h $czone` + +# explicit successor to czk5 +# (suppressing warning about lack of removal date) +czsk6=`$KEYGEN -q -r $RANDFILE -S $czsk5 -i 6h 2>&-` + # active ksk cksk1=`$KEYGEN -q -r $RANDFILE -fk $czone` @@ -69,6 +76,8 @@ czactive=`echo $czsk1 | sed 's/^K.*+005+0*//'` czgenerated=`echo $czsk2 | sed 's/^K.*+005+0*//'` czpublished=`echo $czsk3 | sed 's/^K.*+005+0*//'` czinactive=`echo $czsk4 | sed 's/^K.*+005+0*//'` +czpredecessor=`echo $czsk5 | sed 's/^K.*+005+0*//'` +czsuccessor=`echo $czsk6 | sed 's/^K.*+005+0*//'` ckactive=`echo $cksk1 | sed 's/^K.*+005+0*//'` ckpublished=`echo $cksk2 | sed 's/^K.*+005+0*//'` ckprerevoke=`echo $cksk3 | sed 's/^K.*+005+0*//'` @@ -115,6 +124,10 @@ grep "key id = $czinactive" $cfile.signed > /dev/null || ret=1 # should not be there, hence the && grep "key id = $ckprerevoke" $cfile.signed > /dev/null && ret=1 grep "key id = $czgenerated" $cfile.signed > /dev/null && ret=1 +grep "key id = $czpredecessor" $cfile.signed && echo pred is there +grep "key id = $czsuccessor" $cfile.signed && echo succ is there +#grep "key id = $czpredecessor" $cfile.signed > /dev/null && ret=1 +#grep "key id = $czsuccessor" $cfile.signed > /dev/null && ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` @@ -141,6 +154,8 @@ grep "$ckrevoked" other.sigs > /dev/null && ret=1 grep "$czpublished" other.sigs > /dev/null && ret=1 grep "$czinactive" other.sigs > /dev/null && ret=1 grep "$czgenerated" other.sigs > /dev/null && ret=1 +grep "$czpredecessor" other.sigs > /dev/null && ret=1 +grep "$czsuccessor" other.sigs > /dev/null && ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index f689b7a4a5..459b8d7c09 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -18,7 +18,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + BIND 9 Administrator Reference Manual @@ -1179,10 +1179,11 @@ zone "eng.example.com" { Fetch all DNSSEC keys for the given zone from the key directory (see key-directory in - ), and merge them - into the zone's DNSKEY RRset. If the DNSKEY RRset - is changed as a result of this, then the zone is - automatically re-signed with the new key set. + ). If they are within + their publication period, merge them into the + zone's DNSKEY RRset. If the DNSKEY RRset + is changed, then the zone is automatically + re-signed with the new key set. This command requires that the @@ -1197,6 +1198,34 @@ zone "eng.example.com" { + + loadkeys zone + class + view + + + Fetch all DNSSEC keys for the given zone + from the key directory (see + key-directory in + ). If they are within + their publication period, merge them into the + zone's DNSKEY RRset. Unlike rndc + sign, however, the zone is not + immediately re-signed by the new keys, but is + allowed to incrementally re-sign over time. + + + This command requires that the + auto-dnssec zone option to + be set to maintain or + create, and also requires + the zone to be configured to allow dynamic DNS. + See for + more details. + + + + freeze zone @@ -10614,16 +10643,27 @@ zone zone_name class auto-dnssec allow; permits - keys to be updated and the zone re-signed whenever the - user issues the command rndc sign + keys to be updated and the zone fully re-signed + whenever the user issues the command rndc sign zonename. auto-dnssec maintain; includes the above, but also automatically adjusts the zone's DNSSEC keys on schedule, according to the keys' timing metadata - (see and - ). + (see and + ). The command + rndc sign + zonename causes + named to load keys from the key + repository and sign the zone with all keys that are + active. + rndc loadkeys + zonename causes + named to load keys from the key + repository and schedule key maintenance events to occur + in the future, but it does not sign the full zone + immediately. auto-dnssec create; includes the diff --git a/doc/arm/dnssec.xml b/doc/arm/dnssec.xml index ad29fd31cb..c7d78be1eb 100644 --- a/doc/arm/dnssec.xml +++ b/doc/arm/dnssec.xml @@ -15,7 +15,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + DNSSEC, Dynamic Zones, and Automatic Signing @@ -100,7 +100,8 @@ 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> command. + rndc sign <zonename> or + rndc loadkeys <zonename> command. auto-dnssec maintain includes the above @@ -110,8 +111,10 @@ for more information.) If keys are present in the key directory the first time the zone is loaded, it will be signed immediately, without waiting for an - rndc sign command. (This command can still be - used for unscheduled key changes, however.) + rndc sign or rndc loadkeys + command. (Those commands can still be used when there are unscheduled + key changes, however.) + Using the auto-dnssec option requires the zone to be configured to allow dynamic updates, by adding an @@ -158,11 +161,16 @@ - DNSKEY rollovers via UPDATE + DNSKEY rollovers - It is possible to perform key rollovers via dynamic update. - You need to add the - K* files for the new keys so that + 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 @@ -181,6 +189,22 @@ 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. NSEC3PARAM rollovers via UPDATE diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 889a0d7a7c..842370d04f 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.h,v 1.177 2010/08/13 23:47:03 tbox Exp $ */ +/* $Id: zone.h,v 1.178 2010/08/16 22:21:07 marka Exp $ */ #ifndef DNS_ZONE_H #define DNS_ZONE_H 1 @@ -89,6 +89,7 @@ typedef enum { #define DNS_ZONEKEY_ALLOW 0x00000001U /*%< fetch keys on command */ #define DNS_ZONEKEY_MAINTAIN 0x00000002U /*%< publish/sign on schedule */ #define DNS_ZONEKEY_CREATE 0x00000004U /*%< make keys when needed */ +#define DNS_ZONEKEY_FULLSIGN 0x00000008U /*%< roll to new keys immediately */ #ifndef DNS_ZONE_MINREFRESH #define DNS_ZONE_MINREFRESH 300 /*%< 5 minutes */ @@ -1779,9 +1780,14 @@ dns_zone_getprivatetype(dns_zone_t *zone); */ void -dns_zone_rekey(dns_zone_t *zone); +dns_zone_rekey(dns_zone_t *zone, isc_boolean_t fullsign); /*%< * Update the zone's DNSKEY set from the key repository. + * + * If 'fullsign' is true, trigger an immediate full signing of + * the zone with the new key. Otherwise, if there are no keys or + * if the new keys are for algorithms that have already signed the + * zone, then the zone can be re-signed incrementally. */ isc_result_t diff --git a/lib/dns/zone.c b/lib/dns/zone.c index d8d78b84f8..2834e102f2 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.c,v 1.570 2010/08/11 18:14:19 each Exp $ */ +/* $Id: zone.c,v 1.571 2010/08/16 22:21:07 marka Exp $ */ /*! \file */ @@ -13730,6 +13730,36 @@ clean_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, return (result); } +/* + * Given an RRSIG rdataset and an algorithm, determine whether there + * are any signatures using that algorithm. + */ +static isc_boolean_t +signed_with_alg(dns_rdataset_t *rdataset, dns_secalg_t alg) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t rrsig; + isc_result_t result; + + REQUIRE(rdataset == NULL || rdataset->type == dns_rdatatype_rrsig); + if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) { + return (ISC_FALSE); + } + + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + if (rrsig.algorithm == alg) + return (ISC_TRUE); + } + + return (ISC_FALSE); +} + static void zone_rekey(dns_zone_t *zone) { isc_result_t result; @@ -13741,12 +13771,14 @@ zone_rekey(dns_zone_t *zone) { dns_dnsseckey_t *key; dns_diff_t diff; isc_boolean_t commit = ISC_FALSE, newactive = ISC_FALSE; + isc_boolean_t fullsign; dns_ttl_t ttl = 3600; const char *dir; isc_mem_t *mctx; isc_stdtime_t now; isc_time_t timenow; isc_interval_t ival; + char timebuf[80]; REQUIRE(DNS_ZONE_VALID(zone)); @@ -13789,6 +13821,12 @@ zone_rekey(dns_zone_t *zone) { } else if (result != ISC_R_NOTFOUND) goto failure; + /* + * True when called from "rndc sign". Indicates the zone should be + * fully signed now. + */ + fullsign = ISC_TF(DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN) != 0); + result = dns_dnssec_findmatchingkeys(&zone->origin, dir, mctx, &keys); if (result == ISC_R_SUCCESS) { isc_boolean_t check_ksk; @@ -13818,7 +13856,7 @@ zone_rekey(dns_zone_t *zone) { } } - if ((newactive || !ISC_LIST_EMPTY(diff.tuples)) && + if ((newactive || fullsign || !ISC_LIST_EMPTY(diff.tuples)) && dnskey_sane(zone, db, ver, &diff)) { CHECK(dns_diff_apply(&diff, db, ver)); CHECK(clean_nsec3param(zone, db, ver, &diff)); @@ -13839,6 +13877,8 @@ zone_rekey(dns_zone_t *zone) { if (commit) { isc_time_t timenow; dns_difftuple_t *tuple; + isc_boolean_t newkey = ISC_FALSE; + isc_boolean_t newalg = ISC_FALSE; LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); @@ -13848,33 +13888,126 @@ zone_rekey(dns_zone_t *zone) { TIME_NOW(&timenow); zone_settimer(zone, &timenow); + /* + * Has a new key become active? If so, is it for + * a new algorithm? + */ for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) { dns_rdata_dnskey_t dnskey; - dns_secalg_t algorithm; - isc_region_t r; - isc_uint16_t keyid; if (tuple->rdata.type != dns_rdatatype_dnskey) continue; - result = dns_rdata_tostruct(&tuple->rdata, &dnskey, - NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_rdata_toregion(&tuple->rdata, &r); - algorithm = dnskey.algorithm; - keyid = dst_region_computeid(&r, algorithm); + newkey = ISC_TRUE; + if (!dns_rdataset_isassociated(&keysigs)) { + newalg = ISC_TRUE; + break; + } - result = zone_signwithkey(zone, algorithm, keyid, - ISC_TF(tuple->op == DNS_DIFFOP_DEL)); - if (result != ISC_R_SUCCESS) { - dns_zone_log(zone, ISC_LOG_ERROR, - "zone_signwithkey failed: %s", - dns_result_totext(result)); + result = dns_rdata_tostruct(&tuple->rdata, + &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (!signed_with_alg(&keysigs, + dnskey.algorithm)) { + newalg = ISC_TRUE; + break; } } + /* + * If we found a new algorithm, we need to sign the + * zone fully. If there's a new key, but it's for an + * already-existing algorithm, then the zone signing + * can be handled incrementally. + */ + if (newkey && !newalg) + set_resigntime(zone); + + /* Remove any signatures from removed keys. */ + if (!ISC_LIST_EMPTY(rmkeys)) { + for (key = ISC_LIST_HEAD(rmkeys); + key != NULL; + key = ISC_LIST_NEXT(key, link)) { + result = zone_signwithkey(zone, + dst_key_alg(key->key), + dst_key_id(key->key), + ISC_TRUE); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: %s", + dns_result_totext(result)); + } + } + } + + + if (fullsign) { + /* + * "rndc sign" was called, so we now sign the zone + * with all active keys, whether they're new or not. + */ + for (key = ISC_LIST_HEAD(dnskeys); + key != NULL; + key = ISC_LIST_NEXT(key, link)) { + if (!key->force_sign && !key->hint_sign) + continue; + + result = zone_signwithkey(zone, + dst_key_alg(key->key), + dst_key_id(key->key), + ISC_FALSE); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: %s", + dns_result_totext(result)); + } + } + } else if (newalg) { + /* + * We haven't been told to sign fully, but a new + * algorithm was added to the DNSKEY. We sign + * the full zone, but only with the newly-added + * keys. + */ + for (tuple = ISC_LIST_HEAD(diff.tuples); + tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) { + dns_rdata_dnskey_t dnskey; + dns_secalg_t algorithm; + isc_region_t r; + isc_uint16_t keyid; + + if (tuple->rdata.type != dns_rdatatype_dnskey || + tuple->op == DNS_DIFFOP_DEL) + continue; + + result = dns_rdata_tostruct(&tuple->rdata, + &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_toregion(&tuple->rdata, &r); + algorithm = dnskey.algorithm; + keyid = dst_region_computeid(&r, algorithm); + + result = zone_signwithkey(zone, algorithm, + keyid, + ISC_TF(tuple->op == + DNS_DIFFOP_DEL)); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: %s", + dns_result_totext(result)); + } + } + } + + /* + * Clear fullsign flag, if it was set, so we don't do + * another full signing next time + */ + zone->keyopts &= ~DNS_ZONEKEY_FULLSIGN; + /* * Cause the zone to add/delete NSEC3 chains for the * deferred NSEC3PARAM changes. @@ -13939,6 +14072,17 @@ zone_rekey(dns_zone_t *zone) { UNLOCK_ZONE(zone); } + /* + * If no key event is scheduled, we should still check the key + * repository for updates every so often. (Currently this is + * hard-coded to 12 hours, but it could be configurable.) + */ + if (isc_time_isepoch(&zone->refreshkeytime)) + DNS_ZONE_TIME_ADD(&timenow, (3600 * 12), &zone->refreshkeytime); + + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + dns_zone_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); + failure: dns_diff_clear(&diff); @@ -13967,15 +14111,19 @@ zone_rekey(dns_zone_t *zone) { } void -dns_zone_rekey(dns_zone_t *zone) { +dns_zone_rekey(dns_zone_t *zone, isc_boolean_t fullsign) { isc_time_t now; if (zone->type == dns_zone_master && zone->task != NULL) { LOCK_ZONE(zone); + if (fullsign) + zone->keyopts |= DNS_ZONEKEY_FULLSIGN; + TIME_NOW(&now); zone->refreshkeytime = now; zone_settimer(zone, &now); + UNLOCK_ZONE(zone); } }