From 72042a06d662742f04bbeb83aade6a94a8b9968e Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 12 Sep 2019 11:57:55 +0200 Subject: [PATCH] dnssec-settime: Allow manipulating state files Introduce a new option '-s' for dnssec-settime that when manipulating timing metadata, it also updates the key state file. For testing purposes, add options to dnssec-settime to set key states and when they last changed. The dst code adds ways to write and read the new key states and timing metadata. It updates the parsing code for private key files to not parse the newly introduced metadata (these are for state files only). Introduce key goal (the state the key wants to be in). --- bin/dnssec/dnssec-settime.c | 243 +++++++++++++++++++++++++----- bin/dnssec/dnssec-settime.docbook | 93 ++++++++++++ bin/dnssec/dnssectool.c | 20 +++ bin/dnssec/dnssectool.h | 2 + bin/tests/system/kasp/tests.sh | 76 +++++++--- lib/dns/dst_api.c | 179 +++++++++++++++++----- lib/dns/dst_internal.h | 5 + lib/dns/dst_parse.c | 11 +- lib/dns/include/dst/dst.h | 80 +++++++++- lib/dns/win32/libdns.def.in | 3 + 10 files changed, 618 insertions(+), 94 deletions(-) diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c index bcf32c72b7..cc72e55662 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)); @@ -588,6 +725,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 +815,7 @@ 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)); - } - - 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); + writekey(key, directory, write_state); } 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/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/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 92de7567d8..d7353ab9fa 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -17,7 +17,6 @@ set -e status=0 n=0 -log=1 ################################################################################ # Utilities # @@ -35,9 +34,14 @@ get_keyids() { ls ${start}*${end} | sed "s/$dir\/K${zone}.+${algorithm}+\([0-9]\{5\}\)${end}/\1/" } +# By default log errors and don't quit immediately. +_log=1 +_continue=1 log_error() { - test $log -eq 1 && echo_i "error: $1" + test $_log -eq 1 && echo_i "error: $1" ret=$((ret+1)) + + test $_continue -eq 1 || exit 1 } # Check the created key in directory $1 for zone $2. @@ -115,18 +119,6 @@ check_created_key() { # # dnssec-keygen # -n=$((n+1)) -echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" -ret=0 -$KEYGEN -k _default kasp > keygen.out._default.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" -KEY_ID=$(get_keyids "." "kasp" "13") -echo_i "check key $KEY_ID..." -check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" -test "$ret" -eq 0 || echo_i "failed" -status=$((status+ret)) - n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)" ret=0 @@ -138,7 +130,7 @@ KEY_ID=$(get_keyids "keys" "kasp" "13") echo_i "check key $KEY_ID..." check_created_key "keys" "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "200" "31536000" # Temporarily don't log errors because we are searching multiple files. -log=0 +_log=0 # Check the other algorithm. KEY_IDS=$(get_keyids "keys" "kasp" "8") for KEY_ID in $KEY_IDS; do @@ -151,10 +143,21 @@ for KEY_ID in $KEY_IDS; do # If ret is 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 +_log=1 + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" +ret=0 +$KEYGEN -k _default kasp > keygen.out._default.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" +KEY_ID=$(get_keyids "." "kasp" "13") +echo_i "check key $KEY_ID..." +check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" +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)" @@ -171,6 +174,45 @@ status=$((status+ret)) # # dnssec-settime # +BASE_FILE="K${zone}.+${alg_numpad}+${key_idpad}" +KEY_FILE="${BASE_FILE}.key" +PRIVATE_FILE="${BASE_FILE}.private" +STATE_FILE="${BASE_FILE}.state" +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 time metadata in key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +now=$(date +%Y%m%d%H%M%S) +$SETTIME -s -P $now $BASE_FILE >/dev/null || log_error "settime failed" +grep "; Publish: $now" $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE" +grep "Publish: $now" $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE" +grep "Published: $now" $STATE_FILE > /dev/null || log_error "mismatch published in $STATE_FILE" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also unsets time metadata in key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +$SETTIME -s -P none $BASE_FILE >/dev/null || log_error "settime failed" +grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected published in $KEY_FILE" +grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected published in $PRIVATE_FILE" +grep "Published:" $STATE_FILE > /dev/null && log_error "unexpected published in $STATE_FILE" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) # # named diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index eaacd89209..978f8839c4 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -70,20 +70,21 @@ goto cleanup; \ } -#define NEXTTOKEN_OR_EOF(lex, opt, token) { \ - ret = isc_lex_gettoken(lex, opt, token); \ - if (ret == ISC_R_EOF) \ - break; \ - 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; \ - else if (ret != ISC_R_SUCCESS) \ + if (ret != ISC_R_SUCCESS) \ goto cleanup; \ } while ((*token).type != isc_tokentype_eol) @@ -94,6 +95,10 @@ #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) static const char *numerictags[NUMERIC_NTAGS] = { + "Predecessor:", + "Successor:", + "MaxTTL:", + "RollPeriod:", "Lifetime:" }; @@ -108,18 +113,38 @@ static const char *timingtags[TIMING_NTAGS] = { "Generated:", "Published:", "Active:", - "Retired:", "Revoked:", + "Retired:", "Removed:", "DSPublish:", "SyncPublish:", - "SyncDelete:" + "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) +#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]; @@ -604,10 +629,23 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, return (result); } + key = get_key_struct(pubkey->key_name, pubkey->key_alg, + 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); + } + + 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; @@ -617,23 +655,17 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, dirname, filename, ".state"); INSIST(result == ISC_R_SUCCESS); - result = dst_key_read_state(newfilename, mctx, pubkey); + 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); } - key = get_key_struct(pubkey->key_name, pubkey->key_alg, - pubkey->key_flags, pubkey->key_proto, 0, - pubkey->key_class, pubkey->key_ttl, mctx); - if (key == NULL) { - dst_key_free(&pubkey); - return (ISC_R_NOMEMORY); - } - - if (key->func->parse == NULL) - RETERR(DST_R_UNSUPPORTEDALG); - newfilenamelen = strlen(filename) + 9; if (dirname != NULL) newfilenamelen += strlen(dirname) + 1; @@ -1611,17 +1643,32 @@ 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 *key) +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_DNSMULTILINE; + unsigned int opt = ISC_LEXOPT_EOL; ret = isc_lex_create(mctx, 1500, &lex); if (ret != ISC_R_SUCCESS) { @@ -1634,6 +1681,11 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) goto cleanup; } + /* + * Read the comment line. + */ + READLINE(lex, opt, &token); + /* * Read the algorithm line. */ @@ -1646,11 +1698,13 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); if (token.type != isc_tokentype_number || - token.value.as_ulong != (unsigned long) dst_key_alg(key)) + token.value.as_ulong != (unsigned long) dst_key_alg(*keyp)) { BADTOKEN(); } + READLINE(lex, opt, &token); + /* * Read the length line. */ @@ -1663,11 +1717,13 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); if (token.type != isc_tokentype_number || - token.value.as_ulong != (unsigned long) dst_key_size(key)) + token.value.as_ulong != (unsigned long) dst_key_size(*keyp)) { BADTOKEN(); } + READLINE(lex, opt, &token); + /* * Read the metadata. */ @@ -1675,6 +1731,9 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) int tag; NEXTTOKEN_OR_EOF(lex, opt, &token); + if (ret == ISC_R_EOF) { + break; + } if (token.type != isc_tokentype_string) { BADTOKEN(); } @@ -1682,12 +1741,14 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) /* 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(key, tag, token.value.as_ulong); + dst_key_setnum(*keyp, tag, token.value.as_ulong); goto next; } @@ -1700,14 +1761,14 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) if (token.type != isc_tokentype_string) { BADTOKEN(); } + if (strcmp(DST_AS_STR(token), "yes") == 0) { - dst_key_setbool(key, tag, true); + dst_key_setbool(*keyp, tag, true); } else if (strcmp(DST_AS_STR(token), "no") == 0) { - dst_key_setbool(key, tag, false); + dst_key_setbool(*keyp, tag, false); } else { BADTOKEN(); } - goto next; } @@ -1727,13 +1788,35 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) if (ret != ISC_R_SUCCESS) { goto cleanup; } - dst_key_settime(key, tag, when); + 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. */ @@ -1847,6 +1930,21 @@ 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. */ @@ -1898,6 +1996,11 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { fprintf(fp, "Algorithm: %u\n", key->key_alg); fprintf(fp, "Length: %u\n", key->key_size); + printnum(key, DST_NUM_LIFETIME, "Lifetime", 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); @@ -1905,10 +2008,16 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { printtime(key, DST_TIME_REVOKE, "Revoked", fp); printtime(key, DST_TIME_DELETE, "Removed", fp); - printnum(key, DST_NUM_LIFETIME, "Lifetime", 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); - printbool(key, DST_BOOL_KSK, "KSK", fp); - printbool(key, DST_BOOL_ZSK, "ZSK", 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); diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index 463954fa53..0d8be243d8 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -108,11 +108,16 @@ struct dst_key { isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */ 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 */ diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c index 7130c29501..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 { @@ -754,7 +759,7 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, if (timetags[i] != NULL) { fprintf(fp, "%s %.*s\n", timetags[i], - (int)r.length, r.base); + (int)r.length, r.base); } } } diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index e92de43417..7f8517c4a9 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 */ @@ -97,7 +129,11 @@ 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 @@ -112,6 +148,14 @@ typedef struct dst_context dst_context_t; #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. * @@ -391,7 +435,7 @@ dst_key_read_public(const char *filename, int type, */ isc_result_t -dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *keyp); +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key state from permanent storage. * @@ -929,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); /*%< diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index a5c7949118..153efc2e7b 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1411,6 +1411,7 @@ dst_key_getfilename dst_key_getgssctx dst_key_getnum dst_key_getprivateformat +dst_key_getstate dst_key_gettime dst_key_getttl dst_key_id @@ -1436,6 +1437,7 @@ dst_key_setflags dst_key_setinactive dst_key_setnum dst_key_setprivateformat +dst_key_setstate dst_key_settime dst_key_setttl dst_key_sigsize @@ -1446,6 +1448,7 @@ dst_key_todns dst_key_tofile dst_key_unsetbool dst_key_unsetnum +dst_key_unsetstate dst_key_unsettime dst_lib_destroy dst_lib_init