/* $NetBSD: keymgr.c,v 1.8.2.2 2024/02/25 15:46:50 martin Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * 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 https://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 `target` state and change last changed * to `time`, only if key state has not been set before. */ #define INITIALIZE_STATE(key, state, timing, target, time) \ do { \ dst_key_state_t s; \ char keystr[DST_KEY_FORMATSIZE]; \ if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \ dst_key_setstate((key), (state), (target)); \ dst_key_settime((key), (timing), time); \ \ if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { \ dst_key_format((key), keystr, sizeof(keystr)); \ isc_log_write( \ dns_lctx, DNS_LOGCATEGORY_DNSSEC, \ DNS_LOGMODULE_DNSSEC, \ ISC_LOG_DEBUG(3), \ "keymgr: DNSKEY %s (%s) initialize " \ "%s state to %s (policy %s)", \ keystr, keymgr_keyrole((key)), \ keystatetags[state], \ keystatestrings[target], \ dns_kasp_getname(kasp)); \ } \ } \ } 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 = false, zsk = false; isc_result_t ret; ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk); if (ret != ISC_R_SUCCESS) { return ("UNKNOWN"); } ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk); if (ret != ISC_R_SUCCESS) { return ("UNKNOWN"); } if (ksk && zsk) { return ("CSK"); } else if (ksk) { return ("KSK"); } else if (zsk) { return ("ZSK"); } return ("NOSIGN"); } /* * Set the remove time on key given its retire time. * */ static void keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) { isc_stdtime_t retire = 0, remove = 0, ksk_remove = 0, zsk_remove = 0; bool zsk = false, ksk = false; isc_result_t ret; REQUIRE(key != NULL); REQUIRE(key->key != NULL); ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); if (ret != ISC_R_SUCCESS) { return; } ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); if (ret == ISC_R_SUCCESS && zsk) { dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); /* ZSK: Iret = Dsgn + Dprp + TTLsig */ zsk_remove = retire + ttlsig + dns_kasp_zonepropagationdelay(kasp) + dns_kasp_retiresafety(kasp) + dns_kasp_signdelay(kasp); } ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); if (ret == ISC_R_SUCCESS && ksk) { /* KSK: Iret = DprpP + TTLds */ ksk_remove = retire + dns_kasp_dsttl(kasp) + dns_kasp_parentpropagationdelay(kasp) + dns_kasp_retiresafety(kasp); } remove = ISC_MAX(ksk_remove, zsk_remove); dst_key_settime(key->key, DST_TIME_DELETE, remove); } /* * Set the SyncPublish time (when the DS may be submitted to the parent). * */ static void keymgr_settime_syncpublish(dns_dnsseckey_t *key, dns_kasp_t *kasp, bool first) { isc_stdtime_t published, syncpublish; bool ksk = false; isc_result_t ret; REQUIRE(key != NULL); REQUIRE(key->key != NULL); ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &published); if (ret != ISC_R_SUCCESS) { return; } ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); if (ret != ISC_R_SUCCESS || !ksk) { return; } syncpublish = published + dst_key_getttl(key->key) + dns_kasp_zonepropagationdelay(kasp) + dns_kasp_publishsafety(kasp); if (first) { /* Also need to wait until the signatures are omnipresent. */ isc_stdtime_t zrrsig_present; dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); zrrsig_present = published + ttlsig + dns_kasp_zonepropagationdelay(kasp) + dns_kasp_publishsafety(kasp); if (zrrsig_present > syncpublish) { syncpublish = zrrsig_present; } } dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpublish); } /* * Calculate prepublication time of a successor key of 'key'. * This function can have side effects: * 1. If there is no active time set, which would be super weird, set it now. * 2. If there is no published time set, also super weird, set it now. * 3. If there is no syncpublished time set, set it now. * 4. If the lifetime is not set, it will be set now. * 5. If there should be a retire time and it is not set, it will be set now. * 6. The removed time is adjusted accordingly. * * 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, pub, prepub; bool zsk = false, ksk = false; REQUIRE(key != NULL); REQUIRE(key->key != NULL); active = 0; pub = 0; retire = 0; /* * An active key must have publish and 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_gettime(key->key, DST_TIME_PUBLISH, &pub); if (ret != ISC_R_SUCCESS) { /* Super weird, but if it happens, set it to now. */ dst_key_settime(key->key, DST_TIME_PUBLISH, now); pub = now; } /* * Calculate prepublication time. */ 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) { isc_stdtime_t syncpub; /* * Set PublishCDS if not set. */ ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub); if (ret != ISC_R_SUCCESS) { uint32_t tag; isc_stdtime_t syncpub1, syncpub2; syncpub1 = pub + prepub; syncpub2 = 0; ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR, &tag); if (ret != ISC_R_SUCCESS) { /* * No predecessor, wait for zone to be * completely signed. */ dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); syncpub2 = pub + ttlsig + dns_kasp_publishsafety(kasp) + dns_kasp_zonepropagationdelay(kasp); } syncpub = ISC_MAX(syncpub1, syncpub2); dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpub); } } /* * Not sure what to do when dst_key_getbool() fails here. Extending * the prepublication time anyway is arguably the safest thing to do, * so ignore the result code. */ (void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); if (ret != ISC_R_SUCCESS) { uint32_t klifetime = 0; 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); } /* * Update remove time. */ keymgr_settime_remove(key, kasp); /* * Publish successor 'prepub' time before the 'retire' time of 'key'. */ if (prepub > retire) { /* We should have already prepublished the new key. */ return (now); } return (retire - prepub); } static void keymgr_key_retire(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now) { char keystr[DST_KEY_FORMATSIZE]; isc_result_t ret; isc_stdtime_t retire; dst_key_state_t s; bool ksk = false, zsk = false; REQUIRE(key != NULL); REQUIRE(key->key != NULL); /* This key wants to retire and hide in a corner. */ ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); if (ret != ISC_R_SUCCESS || (retire > now)) { dst_key_settime(key->key, DST_TIME_INACTIVE, now); } dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN); keymgr_settime_remove(key, kasp); /* 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); } ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); if (ret == ISC_R_SUCCESS && 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); } } ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); if (ret == ISC_R_SUCCESS && 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); } static bool keymgr_keyid_conflict(dst_key_t *newkey, dns_dnsseckeylist_t *keys) { uint16_t id = dst_key_id(newkey); uint32_t rid = dst_key_rid(newkey); uint32_t alg = dst_key_alg(newkey); for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keys); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { if (dst_key_alg(dkey->key) != alg) { 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) { return (true); } } return (false); } /* * 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, dns_dnsseckeylist_t *newkeys, dst_key_t **dst_key) { bool conflict = false; int keyflags = DNS_KEYOWNER_ZONE; isc_result_t result = ISC_R_SUCCESS; dst_key_t *newkey = NULL; do { uint32_t algo = dns_kasp_key_algorithm(kkey); int size = dns_kasp_key_size(kkey); 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? */ conflict = keymgr_keyid_conflict(newkey, keylist); if (!conflict) { conflict = keymgr_keyid_conflict(newkey, newkeys); } if (conflict) { /* Try again. */ 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); 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[NUM_KEYSTATES]) { REQUIRE(key != NULL); for (int i = 0; i < NUM_KEYSTATES; 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); } /* * Key d directly depends on k if d is the direct predecessor of k. */ static bool keymgr_direct_dep(dst_key_t *d, dst_key_t *k) { uint32_t s, p; if (dst_key_getnum(d, DST_NUM_SUCCESSOR, &s) != ISC_R_SUCCESS) { return (false); } if (dst_key_getnum(k, DST_NUM_PREDECESSOR, &p) != ISC_R_SUCCESS) { return (false); } return (dst_key_id(d) == p && dst_key_id(k) == s); } /* * Determine which key (if any) has a dependency on k. */ static bool keymgr_dep(dst_key_t *k, dns_dnsseckeylist_t *keyring, uint32_t *dep) { for (dns_dnsseckey_t *d = ISC_LIST_HEAD(*keyring); d != NULL; d = ISC_LIST_NEXT(d, link)) { /* * Check if k is a direct successor of d, e.g. d depends on k. */ if (keymgr_direct_dep(d->key, k)) { if (dep != NULL) { *dep = dst_key_id(d->key); } return (true); } } return (false); } /* * Check if a 'z' is a successor of 'x'. * This implements Equation(2) of "Flexible and Robust Key Rollover". */ static bool keymgr_key_is_successor(dst_key_t *x, dst_key_t *z, dst_key_t *key, int type, dst_key_state_t next_state, dns_dnsseckeylist_t *keyring) { uint32_t dep_x; uint32_t dep_z; /* * The successor relation requires that the predecessor key must not * have any other keys relying on it. In other words, there must be * nothing depending on x. */ if (keymgr_dep(x, keyring, &dep_x)) { return (false); } /* * If there is no keys relying on key z, then z is not a successor. */ if (!keymgr_dep(z, keyring, &dep_z)) { return (false); } /* * x depends on z, thus key z is a direct successor of key x. */ if (dst_key_id(x) == dep_z) { return (true); } /* * It is possible to roll keys faster than the time required to finish * the rollover procedure. For example, consider the keys x, y, z. * Key x is currently published and is going to be replaced by y. The * DNSKEY for x is removed from the zone and at the same moment the * DNSKEY for y is introduced. Key y is a direct dependency for key x * and is therefore the successor of x. However, before the new DNSKEY * has been propagated, key z will replace key y. The DNSKEY for y is * removed and moves into the same state as key x. Key y now directly * depends on key z, and key z will be a new successor key for x. */ dst_key_state_t zst[NUM_KEYSTATES] = { NA, NA, NA, NA }; for (int i = 0; i < NUM_KEYSTATES; i++) { dst_key_state_t state; if (dst_key_getstate(z, i, &state) != ISC_R_SUCCESS) { continue; } zst[i] = state; } for (dns_dnsseckey_t *y = ISC_LIST_HEAD(*keyring); y != NULL; y = ISC_LIST_NEXT(y, link)) { if (dst_key_id(y->key) == dst_key_id(z)) { continue; } if (dst_key_id(y->key) != dep_z) { continue; } /* * This is another key y, that depends on key z. It may be * part of the successor relation if the key states match * those of key z. */ if (keymgr_key_match_state(y->key, key, type, next_state, zst)) { /* * If y is a successor of x, then z is also a * successor of x. */ return (keymgr_key_is_successor(x, y->key, key, type, next_state, keyring)); } } return (false); } /* * 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[NUM_KEYSTATES], dst_key_state_t states2[NUM_KEYSTATES], 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 (!keymgr_key_match_state(dkey->key, key->key, type, next_state, states)) { continue; } /* Found a match. */ if (!check_successor) { return (true); } /* * We have to make sure that the key we are checking, also * has a successor relationship with another key. */ for (dns_dnsseckey_t *skey = ISC_LIST_HEAD(*keyring); skey != NULL; skey = ISC_LIST_NEXT(skey, link)) { if (skey == dkey) { continue; } if (!keymgr_key_match_state(skey->key, key->key, type, next_state, states2)) { continue; } /* * Found a possible successor, check. */ if (keymgr_key_is_successor(dkey->key, skey->key, key->key, type, next_state, keyring)) { return (true); } } } /* No match. */ return (false); } /* * Check if a key has a successor. */ static bool keymgr_key_has_successor(dns_dnsseckey_t *predecessor, dns_dnsseckeylist_t *keyring) { for (dns_dnsseckey_t *successor = ISC_LIST_HEAD(*keyring); successor != NULL; successor = ISC_LIST_NEXT(successor, link)) { if (keymgr_direct_dep(predecessor->key, successor->key)) { return (true); } } return (false); } /* * 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) { /* (3e) */ dst_key_state_t dnskey_chained[NUM_KEYSTATES] = { OMNIPRESENT, NA, OMNIPRESENT, NA }; dst_key_state_t ds_hidden[NUM_KEYSTATES] = { NA, NA, NA, HIDDEN }; /* successor n/a */ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; 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, 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). */ if (keymgr_key_match_state(dkey->key, key->key, type, next_state, dnskey_chained)) { /* This DNSKEY and KRRSIG are OMNIPRESENT. */ continue; } /* * Perhaps another key provides a chain of trust. */ dnskey_chained[DST_KEY_DS] = OMNIPRESENT; if (!keymgr_key_exists_with_state(keyring, key, type, next_state, dnskey_chained, 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) { /* (3i) */ dst_key_state_t rrsig_chained[NUM_KEYSTATES] = { OMNIPRESENT, OMNIPRESENT, NA, NA }; dst_key_state_t dnskey_hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA }; /* successor n/a */ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; 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. */ (void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY, &rrsig_chained[DST_KEY_DNSKEY]); if (!keymgr_key_exists_with_state(keyring, key, type, next_state, rrsig_chained, 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, bool secure_to_insecure) { /* (3a) */ dst_key_state_t states[2][NUM_KEYSTATES] = { /* DNSKEY, ZRRSIG, KRRSIG, DS */ { NA, NA, NA, OMNIPRESENT }, /* DS present */ { NA, NA, NA, RUMOURED } /* DS introducing */ }; /* successor n/a */ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; /* * 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) || (secure_to_insecure && keymgr_key_exists_with_state(keyring, key, type, next_state, na, 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][NUM_KEYSTATES] = { /* 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 */ }; /* successor n/a */ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; 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][NUM_KEYSTATES] = { /* 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 */ }; /* successor n/a */ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; 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[NUM_KEYSTATES] = { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }; dst_key_state_t ds_rumoured[NUM_KEYSTATES] = { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }; dst_key_state_t ds_retired[NUM_KEYSTATES] = { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }; dst_key_state_t ksk_rumoured[NUM_KEYSTATES] = { RUMOURED, NA, NA, OMNIPRESENT }; dst_key_state_t ksk_retired[NUM_KEYSTATES] = { UNRETENTIVE, NA, NA, OMNIPRESENT }; /* successor n/a */ dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; 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, bool secure_to_insecure) { /* 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, secure_to_insecure); rule1b = keymgr_have_ds(keyring, key, type, next_state, secure_to_insecure); 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, secure_to_insecure) || keymgr_have_ds(keyring, key, type, next_state, secure_to_insecure)) && /* * 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, dstime, nexttime = now; dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); /* * 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-propagation-delay + max-zone-ttl. * * We will also add the retire-safety interval. */ nexttime = lastchange + ttlsig + dns_kasp_zonepropagationdelay(kasp) + dns_kasp_retiresafety(kasp); /* * Only add the sign delay Dsgn if there is an actual * predecessor or successor key. */ uint32_t tag; ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR, &tag); if (ret != ISC_R_SUCCESS) { ret = dst_key_getnum(key->key, DST_NUM_SUCCESSOR, &tag); } if (ret == ISC_R_SUCCESS) { nexttime += dns_kasp_signdelay(kasp); } break; default: nexttime = now; break; } break; case DST_KEY_DS: switch (next_state) { /* * 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 * * This translates to: * * parent-propagation-delay + parent-ds-ttl. * * We will also add the retire-safety interval. */ case OMNIPRESENT: /* Make sure DS has been seen in the parent. */ ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &dstime); if (ret != ISC_R_SUCCESS || dstime > now) { /* Not yet, try again in an hour. */ nexttime = now + 3600; } else { nexttime = dstime + dns_kasp_dsttl(kasp) + dns_kasp_parentpropagationdelay(kasp) + dns_kasp_retiresafety(kasp); } break; case HIDDEN: /* Make sure DS has been withdrawn from the parent. */ ret = dst_key_gettime(key->key, DST_TIME_DSDELETE, &dstime); if (ret != ISC_R_SUCCESS || dstime > now) { /* Not yet, try again in an hour. */ nexttime = now + 3600; } else { nexttime = dstime + dns_kasp_dsttl(kasp) + dns_kasp_parentpropagationdelay(kasp) + dns_kasp_retiresafety(kasp); } break; default: nexttime = now; break; } break; default: 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 secure_to_insecure) { 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, secure_to_insecure)) { /* 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); INSIST(dst_key_ismodified(dkey->key)); changed = true; } } /* We changed something, continue processing. */ if (changed) { goto transition; } return (ISC_R_SUCCESS); } /* * See if this key needs to be initialized with properties. 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. The key states * will be initialized according to existing timing metadata. * */ static void keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now, bool csk) { bool ksk, zsk; isc_result_t ret; isc_stdtime_t active = 0, pub = 0, syncpub = 0, retire = 0, remove = 0; dst_key_state_t dnskey_state = HIDDEN; dst_key_state_t ds_state = HIDDEN; dst_key_state_t zrrsig_state = HIDDEN; dst_key_state_t goal_state = HIDDEN; REQUIRE(key != NULL); REQUIRE(key->key != NULL); /* Initialize role. */ 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 || csk)); } 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 || csk)); } /* Get time metadata. */ ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); if (active <= now && ret == ISC_R_SUCCESS) { dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); ttlsig += dns_kasp_zonepropagationdelay(kasp); if ((active + ttlsig) <= now) { zrrsig_state = OMNIPRESENT; } else { zrrsig_state = RUMOURED; } goal_state = OMNIPRESENT; } ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub); if (pub <= now && ret == ISC_R_SUCCESS) { dns_ttl_t key_ttl = dst_key_getttl(key->key); key_ttl += dns_kasp_zonepropagationdelay(kasp); if ((pub + key_ttl) <= now) { dnskey_state = OMNIPRESENT; } else { dnskey_state = RUMOURED; } goal_state = OMNIPRESENT; } ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub); if (syncpub <= now && ret == ISC_R_SUCCESS) { dns_ttl_t ds_ttl = dns_kasp_dsttl(kasp); ds_ttl += dns_kasp_parentpropagationdelay(kasp); if ((syncpub + ds_ttl) <= now) { ds_state = OMNIPRESENT; } else { ds_state = RUMOURED; } goal_state = OMNIPRESENT; } ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); if (retire <= now && ret == ISC_R_SUCCESS) { dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); ttlsig += dns_kasp_zonepropagationdelay(kasp); if ((retire + ttlsig) <= now) { zrrsig_state = HIDDEN; } else { zrrsig_state = UNRETENTIVE; } ds_state = UNRETENTIVE; goal_state = HIDDEN; } ret = dst_key_gettime(key->key, DST_TIME_DELETE, &remove); if (remove <= now && ret == ISC_R_SUCCESS) { dns_ttl_t key_ttl = dst_key_getttl(key->key); key_ttl += dns_kasp_zonepropagationdelay(kasp); if ((remove + key_ttl) <= now) { dnskey_state = HIDDEN; } else { dnskey_state = UNRETENTIVE; } zrrsig_state = HIDDEN; ds_state = HIDDEN; goal_state = HIDDEN; } /* Set goal if not already set. */ if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal_state) != ISC_R_SUCCESS) { dst_key_setstate(key->key, DST_KEY_GOAL, goal_state); } /* Set key states for all keys that do not have them. */ INITIALIZE_STATE(key->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY, dnskey_state, now); if (ksk || csk) { INITIALIZE_STATE(key->key, DST_KEY_KRRSIG, DST_TIME_KRRSIG, dnskey_state, now); INITIALIZE_STATE(key->key, DST_KEY_DS, DST_TIME_DS, ds_state, now); } if (zsk || csk) { INITIALIZE_STATE(key->key, DST_KEY_ZRRSIG, DST_TIME_ZRRSIG, zrrsig_state, now); } } static isc_result_t keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key, dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *newkeys, const dns_name_t *origin, dns_rdataclass_t rdclass, dns_kasp_t *kasp, uint32_t lifetime, bool rollover, isc_stdtime_t now, isc_stdtime_t *nexttime, isc_mem_t *mctx) { char keystr[DST_KEY_FORMATSIZE]; isc_stdtime_t retire = 0, active = 0, prepub = 0; dns_dnsseckey_t *new_key = NULL; dns_dnsseckey_t *candidate = NULL; dst_key_t *dst_key = NULL; /* 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) is active in 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 > now) { 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: new successor needed for " "DNSKEY %s (%s) (policy %s) in %u " "seconds", keystr, keymgr_keyrole(active_key->key), dns_kasp_getname(kasp), (prepub - now)); } } if (prepub == 0 || prepub > now) { /* No need to start rollover now. */ if (*nexttime == 0 || prepub < *nexttime) { *nexttime = prepub; } return (ISC_R_SUCCESS); } if (keymgr_key_has_successor(active_key, keyring)) { /* Key already has successor. */ 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: key DNSKEY %s (%s) (policy " "%s) already has successor", keystr, keymgr_keyrole(active_key->key), dns_kasp_getname(kasp)); } return (ISC_R_SUCCESS); } 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)); } /* * If rollover is not allowed, warn. */ if (!rollover) { dst_key_format(active_key->key, keystr, sizeof(keystr)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, "keymgr: DNSKEY %s (%s) is offline in " "policy %s, cannot start rollover", keystr, keymgr_keyrole(active_key->key), dns_kasp_getname(kasp)); return (ISC_R_SUCCESS); } } 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, kaspkey) && dst_key_is_unused(candidate->key)) { /* Found a candidate in keyring. */ break; } } if (candidate == NULL) { /* No key available in keyring, create a new one. */ bool csk = (dns_kasp_key_ksk(kaspkey) && dns_kasp_key_zsk(kaspkey)); isc_result_t result = keymgr_createkey(kaspkey, origin, rdclass, mctx, keyring, newkeys, &dst_key); if (result != ISC_R_SUCCESS) { return (result); } dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp)); dst_key_settime(dst_key, DST_TIME_CREATED, now); result = dns_dnsseckey_create(mctx, &dst_key, &new_key); if (result != ISC_R_SUCCESS) { return (result); } keymgr_key_init(new_key, kasp, now, csk); } else { new_key = candidate; } dst_key_setnum(new_key->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(new_key->key, DST_TIME_PUBLISH, now); dst_key_settime(new_key->key, DST_TIME_ACTIVATE, now); keymgr_settime_syncpublish(new_key, kasp, true); active = now; } else { /* * This is a successor. Mark the relationship. */ isc_stdtime_t created; (void)dst_key_gettime(new_key->key, DST_TIME_CREATED, &created); dst_key_setnum(new_key->key, DST_NUM_PREDECESSOR, dst_key_id(active_key->key)); dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR, dst_key_id(new_key->key)); (void)dst_key_gettime(active_key->key, DST_TIME_INACTIVE, &retire); active = retire; /* * If prepublication time and/or retire time are * in the past (before the new key was created), use * creation time as published and active time, * effectively immediately making the key active. */ if (prepub < created) { active += (created - prepub); prepub = created; } if (active < created) { active = created; } dst_key_settime(new_key->key, DST_TIME_PUBLISH, prepub); dst_key_settime(new_key->key, DST_TIME_ACTIVATE, active); keymgr_settime_syncpublish(new_key, kasp, false); /* * Retire predecessor. */ dst_key_setstate(active_key->key, DST_KEY_GOAL, HIDDEN); } /* This key wants to be present. */ dst_key_setstate(new_key->key, DST_KEY_GOAL, OMNIPRESENT); /* Do we need to set retire time? */ if (lifetime > 0) { dst_key_settime(new_key->key, DST_TIME_INACTIVE, (active + lifetime)); keymgr_settime_remove(new_key, kasp); } /* Append dnsseckey to list of new keys. */ dns_dnssec_get_hints(new_key, now); new_key->source = dns_keysource_repository; INSIST(!new_key->legacy); if (candidate == NULL) { ISC_LIST_APPEND(*newkeys, new_key, link); } /* Logging. */ dst_key_format(new_key->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(new_key->key), (candidate != NULL) ? "selected" : "created", dns_kasp_getname(kasp)); return (ISC_R_SUCCESS); } static bool keymgr_key_may_be_purged(dst_key_t *key, uint32_t after, isc_stdtime_t now) { bool ksk = false; bool zsk = false; dst_key_state_t hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA }; isc_stdtime_t lastchange = 0; char keystr[DST_KEY_FORMATSIZE]; dst_key_format(key, keystr, sizeof(keystr)); /* If 'purge-keys' is disabled, always retain keys. */ if (after == 0) { return (false); } /* Don't purge keys with goal OMNIPRESENT */ if (dst_key_goal(key) == OMNIPRESENT) { return (false); } /* Don't purge unused keys. */ if (dst_key_is_unused(key)) { return (false); } /* If this key is completely HIDDEN it may be purged. */ (void)dst_key_getbool(key, DST_BOOL_KSK, &ksk); (void)dst_key_getbool(key, DST_BOOL_ZSK, &zsk); if (ksk) { hidden[DST_KEY_KRRSIG] = HIDDEN; hidden[DST_KEY_DS] = HIDDEN; } if (zsk) { hidden[DST_KEY_ZRRSIG] = HIDDEN; } if (!keymgr_key_match_state(key, key, 0, NA, hidden)) { return (false); } /* * Check 'purge-keys' interval. If the interval has passed since * the last key change, it may be purged. */ for (int i = 0; i < NUM_KEYSTATES; i++) { isc_stdtime_t change = 0; (void)dst_key_gettime(key, keystatetimes[i], &change); if (change > lastchange) { lastchange = change; } } return ((lastchange + after) < now); } static void keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) { isc_result_t ret; isc_buffer_t fileb; char filename[NAME_MAX]; /* * Make the filename. */ isc_buffer_init(&fileb, filename, sizeof(filename)); ret = dst_key_buildfilename(key, type, dir, &fileb); if (ret != ISC_R_SUCCESS) { char keystr[DST_KEY_FORMATSIZE]; dst_key_format(key, keystr, sizeof(keystr)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, "keymgr: failed to purge DNSKEY %s (%s): cannot " "build filename (%s)", keystr, keymgr_keyrole(key), isc_result_totext(ret)); return; } if (unlink(filename) < 0) { char keystr[DST_KEY_FORMATSIZE]; dst_key_format(key, keystr, sizeof(keystr)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, "keymgr: failed to purge DNSKEY %s (%s): unlink " "'%s' failed", keystr, keymgr_keyrole(key), filename); } } /* * 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_dnsseckeylist_t *dnskeys, 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 *newkey = NULL; isc_dir_t dir; bool dir_open = false; bool secure_to_insecure = false; int numkeys = 0; 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: %s (policy %s)", keystr, dns_kasp_getname(kasp)); } for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys); 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: dnskeys: %s (policy %s)", keystr, dns_kasp_getname(kasp)); } } for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { numkeys++; } /* Do we need to remove keys? */ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { bool found_match = false; keymgr_key_init(dkey, kasp, now, (numkeys == 1)); for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) { if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { found_match = true; break; } } /* No match, so retire unwanted retire key. */ if (!found_match) { keymgr_key_retire(dkey, kasp, now); } /* Check purge-keys interval. */ if (keymgr_key_may_be_purged(dkey->key, dns_kasp_purgekeys(kasp), now)) { dst_key_format(dkey->key, keystr, sizeof(keystr)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "keymgr: purge DNSKEY %s (%s) according " "to policy %s", keystr, keymgr_keyrole(dkey->key), dns_kasp_getname(kasp)); keymgr_purge_keyfile(dkey->key, directory, DST_TYPE_PUBLIC); keymgr_purge_keyfile(dkey->key, directory, DST_TYPE_PRIVATE); keymgr_purge_keyfile(dkey->key, directory, DST_TYPE_STATE); dkey->purge = true; } } /* 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)) { uint32_t lifetime = dns_kasp_key_lifetime(kkey); dns_dnsseckey_t *active_key = NULL; bool rollover_allowed = true; /* 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)) { if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { /* Found a match. */ dst_key_format(dkey->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(dkey->key), dns_kasp_getname(kasp)); /* Initialize lifetime if not set. */ 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 (active_key) { /* We already have an active key that * matches the kasp policy. */ if (!dst_key_is_unused(dkey->key) && (dst_key_goal(dkey->key) == OMNIPRESENT) && !keymgr_dep(dkey->key, keyring, NULL) && !keymgr_dep(active_key->key, keyring, NULL)) { /* * Multiple signing keys match * the kasp key configuration. * Retire excess keys in use. */ keymgr_key_retire(dkey, kasp, now); } continue; } /* * Save the matched key only if it is active * or desires to be active. */ if (dst_key_goal(dkey->key) == OMNIPRESENT || dst_key_is_active(dkey->key, now)) { active_key = dkey; } } } if (active_key == NULL) { /* * We didn't found an active key, perhaps the .private * key file is offline. If so, we don't want to create * a successor key. Check if we have an appropriate * state file. */ for (dns_dnsseckey_t *dnskey = ISC_LIST_HEAD(*dnskeys); dnskey != NULL; dnskey = ISC_LIST_NEXT(dnskey, link)) { if (keymgr_dnsseckey_kaspkey_match(dnskey, kkey)) { /* Found a match. */ dst_key_format(dnskey->key, keystr, sizeof(keystr)); isc_log_write( dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), "keymgr: DNSKEY %s (%s) " "offline, policy %s", keystr, keymgr_keyrole(dnskey->key), dns_kasp_getname(kasp)); rollover_allowed = false; active_key = dnskey; break; } } } /* See if this key requires a rollover. */ RETERR(keymgr_key_rollover( kkey, active_key, keyring, &newkeys, origin, rdclass, kasp, lifetime, rollover_allowed, now, nexttime, mctx)); } /* Walked all kasp key configurations. Append new keys. */ if (!ISC_LIST_EMPTY(newkeys)) { ISC_LIST_APPENDLIST(*keyring, newkeys, link); } /* * If the policy has an empty key list, this means the zone is going * back to unsigned. */ secure_to_insecure = dns_kasp_keylist_empty(kasp); /* Read to update key states. */ keymgr_update(keyring, kasp, now, nexttime, secure_to_insecure); /* Store key states and update hints. */ for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { if (dst_key_ismodified(dkey->key) && !dkey->purge) { dns_dnssec_get_hints(dkey, now); RETERR(dst_key_tofile(dkey->key, options, directory)); dst_key_setmodified(dkey->key, false); } } result = ISC_R_SUCCESS; failure: if (dir_open) { isc_dir_close(&dir); } 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); } } if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { 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(3), "keymgr: %s done", namebuf); } return (result); } static isc_result_t keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, const char *directory, isc_stdtime_t now, isc_stdtime_t when, bool dspublish, dns_keytag_t id, unsigned int alg, bool check_id) { int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); isc_dir_t dir; isc_result_t result; dns_dnsseckey_t *ksk_key = NULL; REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(keyring != NULL); for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { isc_result_t ret; bool ksk = false; ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); if (ret == ISC_R_SUCCESS && ksk) { if (check_id && dst_key_id(dkey->key) != id) { continue; } if (alg > 0 && dst_key_alg(dkey->key) != alg) { continue; } if (ksk_key != NULL) { /* * Only checkds for one key at a time. */ return (DNS_R_TOOMANYKEYS); } ksk_key = dkey; } } if (ksk_key == NULL) { return (DNS_R_NOKEYMATCH); } if (dspublish) { dst_key_state_t s; dst_key_settime(ksk_key->key, DST_TIME_DSPUBLISH, when); result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s); if (result != ISC_R_SUCCESS || s != RUMOURED) { dst_key_setstate(ksk_key->key, DST_KEY_DS, RUMOURED); } } else { dst_key_state_t s; dst_key_settime(ksk_key->key, DST_TIME_DSDELETE, when); result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s); if (result != ISC_R_SUCCESS || s != UNRETENTIVE) { dst_key_setstate(ksk_key->key, DST_KEY_DS, UNRETENTIVE); } } if (isc_log_wouldlog(dns_lctx, ISC_LOG_NOTICE)) { char keystr[DST_KEY_FORMATSIZE]; char timestr[26]; /* Minimal buf as per ctime_r() spec. */ dst_key_format(ksk_key->key, keystr, sizeof(keystr)); isc_stdtime_tostring(when, timestr, sizeof(timestr)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_NOTICE, "keymgr: checkds DS for key %s seen %s at %s", keystr, dspublish ? "published" : "withdrawn", timestr); } /* Store key state and update hints. */ isc_dir_init(&dir); if (directory == NULL) { directory = "."; } result = isc_dir_open(&dir, directory); if (result != ISC_R_SUCCESS) { return (result); } dns_dnssec_get_hints(ksk_key, now); result = dst_key_tofile(ksk_key->key, options, directory); if (result == ISC_R_SUCCESS) { dst_key_setmodified(ksk_key->key, false); } isc_dir_close(&dir); return (result); } isc_result_t dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, const char *directory, isc_stdtime_t now, isc_stdtime_t when, bool dspublish) { return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish, 0, 0, false)); } isc_result_t dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, const char *directory, isc_stdtime_t now, isc_stdtime_t when, bool dspublish, dns_keytag_t id, unsigned int alg) { return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish, id, alg, true)); } static void keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf, const char *pre, int ks, int kt) { char timestr[26]; /* Minimal buf as per ctime_r() spec. */ isc_result_t ret; isc_stdtime_t when = 0; dst_key_state_t state = NA; isc_buffer_printf(buf, "%s", pre); (void)dst_key_getstate(key, ks, &state); ret = dst_key_gettime(key, kt, &when); if (state == RUMOURED || state == OMNIPRESENT) { isc_buffer_printf(buf, "yes - since "); } else if (now < when) { isc_buffer_printf(buf, "no - scheduled "); } else { isc_buffer_printf(buf, "no\n"); return; } if (ret == ISC_R_SUCCESS) { isc_stdtime_tostring(when, timestr, sizeof(timestr)); isc_buffer_printf(buf, "%s\n", timestr); } } static void rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, isc_stdtime_t now, isc_buffer_t *buf, bool zsk) { char timestr[26]; /* Minimal buf as per ctime_r() spec. */ isc_result_t ret = ISC_R_SUCCESS; isc_stdtime_t active_time = 0; dst_key_state_t state = NA, goal = NA; int rrsig, active, retire; dst_key_t *key = dkey->key; if (zsk) { rrsig = DST_KEY_ZRRSIG; active = DST_TIME_ACTIVATE; retire = DST_TIME_INACTIVE; } else { rrsig = DST_KEY_KRRSIG; active = DST_TIME_PUBLISH; retire = DST_TIME_DELETE; } isc_buffer_printf(buf, "\n"); (void)dst_key_getstate(key, DST_KEY_GOAL, &goal); (void)dst_key_getstate(key, rrsig, &state); (void)dst_key_gettime(key, active, &active_time); if (active_time == 0) { // only interested in keys that were once active. return; } if (goal == HIDDEN && (state == UNRETENTIVE || state == HIDDEN)) { isc_stdtime_t remove_time = 0; // is the key removed yet? state = NA; (void)dst_key_getstate(key, DST_KEY_DNSKEY, &state); if (state == RUMOURED || state == OMNIPRESENT) { ret = dst_key_gettime(key, DST_TIME_DELETE, &remove_time); if (ret == ISC_R_SUCCESS) { isc_buffer_printf(buf, " Key is retired, will " "be removed on "); isc_stdtime_tostring(remove_time, timestr, sizeof(timestr)); isc_buffer_printf(buf, "%s", timestr); } } else { isc_buffer_printf( buf, " Key has been removed from the zone"); } } else { isc_stdtime_t retire_time = 0; uint32_t lifetime = 0; (void)dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime); ret = dst_key_gettime(key, retire, &retire_time); if (ret == ISC_R_SUCCESS) { if (now < retire_time) { if (goal == OMNIPRESENT) { isc_buffer_printf(buf, " Next rollover " "scheduled on "); retire_time = keymgr_prepublication_time( dkey, kasp, lifetime, now); } else { isc_buffer_printf( buf, " Key will retire on "); } } else { isc_buffer_printf(buf, " Rollover is due since "); } isc_stdtime_tostring(retire_time, timestr, sizeof(timestr)); isc_buffer_printf(buf, "%s", timestr); } else { isc_buffer_printf(buf, " No rollover scheduled"); } } isc_buffer_printf(buf, "\n"); } static void keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) { dst_key_state_t state = NA; (void)dst_key_getstate(key, ks, &state); switch (state) { case HIDDEN: isc_buffer_printf(buf, " - %shidden\n", pre); break; case RUMOURED: isc_buffer_printf(buf, " - %srumoured\n", pre); break; case OMNIPRESENT: isc_buffer_printf(buf, " - %somnipresent\n", pre); break; case UNRETENTIVE: isc_buffer_printf(buf, " - %sunretentive\n", pre); break; case NA: default: /* print nothing */ break; } } void dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, isc_stdtime_t now, char *out, size_t out_len) { isc_buffer_t buf; char timestr[26]; /* Minimal buf as per ctime_r() spec. */ REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(keyring != NULL); REQUIRE(out != NULL); isc_buffer_init(&buf, out, out_len); // policy name isc_buffer_printf(&buf, "dnssec-policy: %s\n", dns_kasp_getname(kasp)); isc_buffer_printf(&buf, "current time: "); isc_stdtime_tostring(now, timestr, sizeof(timestr)); isc_buffer_printf(&buf, "%s\n", timestr); for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { char algstr[DNS_NAME_FORMATSIZE]; bool ksk = false, zsk = false; isc_result_t ret; if (dst_key_is_unused(dkey->key)) { continue; } // key data dns_secalg_format((dns_secalg_t)dst_key_alg(dkey->key), algstr, sizeof(algstr)); isc_buffer_printf(&buf, "\nkey: %d (%s), %s\n", dst_key_id(dkey->key), algstr, keymgr_keyrole(dkey->key)); // publish status keytime_status(dkey->key, now, &buf, " published: ", DST_KEY_DNSKEY, DST_TIME_PUBLISH); // signing status ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); if (ret == ISC_R_SUCCESS && ksk) { keytime_status(dkey->key, now, &buf, " key signing: ", DST_KEY_KRRSIG, DST_TIME_PUBLISH); } ret = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk); if (ret == ISC_R_SUCCESS && zsk) { keytime_status(dkey->key, now, &buf, " zone signing: ", DST_KEY_ZRRSIG, DST_TIME_ACTIVATE); } // rollover status rollover_status(dkey, kasp, now, &buf, zsk); // key states keystate_status(dkey->key, &buf, "goal: ", DST_KEY_GOAL); keystate_status(dkey->key, &buf, "dnskey: ", DST_KEY_DNSKEY); keystate_status(dkey->key, &buf, "ds: ", DST_KEY_DS); keystate_status(dkey->key, &buf, "zone rrsig: ", DST_KEY_ZRRSIG); keystate_status(dkey->key, &buf, "key rrsig: ", DST_KEY_KRRSIG); } } isc_result_t dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, const char *directory, isc_stdtime_t now, isc_stdtime_t when, dns_keytag_t id, unsigned int algorithm) { int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); isc_dir_t dir; isc_result_t result; dns_dnsseckey_t *key = NULL; isc_stdtime_t active, retire, prepub; REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(keyring != NULL); for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { if (dst_key_id(dkey->key) != id) { continue; } if (algorithm > 0 && dst_key_alg(dkey->key) != algorithm) { continue; } if (key != NULL) { /* * Only rollover for one key at a time. */ return (DNS_R_TOOMANYKEYS); } key = dkey; } if (key == NULL) { return (DNS_R_NOKEYMATCH); } result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); if (result != ISC_R_SUCCESS || active > now) { return (DNS_R_KEYNOTACTIVE); } result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); if (result != ISC_R_SUCCESS) { /** * Default to as if this key was not scheduled to * become retired, as if it had unlimited lifetime. */ retire = 0; } /** * Usually when is set to now, which is before the scheduled * prepublication time, meaning we reduce the lifetime of the * key. But in some cases, the lifetime can also be extended. * We accept it, but we can return an error here if that * turns out to be unintuitive behavior. */ prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) + dns_kasp_zonepropagationdelay(kasp); retire = when + prepub; dst_key_settime(key->key, DST_TIME_INACTIVE, retire); dst_key_setnum(key->key, DST_NUM_LIFETIME, (retire - active)); /* Store key state and update hints. */ isc_dir_init(&dir); if (directory == NULL) { directory = "."; } result = isc_dir_open(&dir, directory); if (result != ISC_R_SUCCESS) { return (result); } dns_dnssec_get_hints(key, now); result = dst_key_tofile(key->key, options, directory); if (result == ISC_R_SUCCESS) { dst_key_setmodified(key->key, false); } isc_dir_close(&dir); return (result); }