2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*
|
|
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
|
|
* information regarding copyright ownership.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*! \file */
|
|
|
|
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <isc/buffer.h>
|
|
|
|
#include <isc/dir.h>
|
|
|
|
#include <isc/mem.h>
|
|
|
|
#include <isc/print.h>
|
|
|
|
#include <isc/string.h>
|
|
|
|
#include <isc/util.h>
|
|
|
|
|
|
|
|
#include <dns/dnssec.h>
|
|
|
|
#include <dns/kasp.h>
|
|
|
|
#include <dns/keymgr.h>
|
|
|
|
#include <dns/keyvalues.h>
|
|
|
|
#include <dns/log.h>
|
|
|
|
#include <dns/result.h>
|
|
|
|
|
|
|
|
#include <dst/dst.h>
|
|
|
|
#include <dst/result.h>
|
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
#define RETERR(x) \
|
|
|
|
do { \
|
|
|
|
result = (x); \
|
|
|
|
if (result != ISC_R_SUCCESS) \
|
|
|
|
goto failure; \
|
2019-10-17 11:19:35 +02:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set key state to HIDDEN and change last changed to now,
|
|
|
|
* only if key state has not been set before.
|
|
|
|
*/
|
2020-03-27 10:28:22 +01:00
|
|
|
#define INITIALIZE_STATE(key, state, time, target) \
|
2020-02-12 13:59:18 +01:00
|
|
|
do { \
|
|
|
|
dst_key_state_t s; \
|
|
|
|
if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \
|
|
|
|
isc_stdtime_t t; \
|
|
|
|
dst_key_gettime((key), DST_TIME_CREATED, &t); \
|
2020-03-27 10:28:22 +01:00
|
|
|
dst_key_setstate((key), (state), target); \
|
2020-02-12 13:59:18 +01:00
|
|
|
dst_key_settime((key), (time), t); \
|
|
|
|
} \
|
2019-10-17 11:19:35 +02:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* Shorter keywords for better readability. */
|
2020-02-13 14:44:37 -08:00
|
|
|
#define HIDDEN DST_KEY_STATE_HIDDEN
|
|
|
|
#define RUMOURED DST_KEY_STATE_RUMOURED
|
2019-10-17 11:19:35 +02:00
|
|
|
#define OMNIPRESENT DST_KEY_STATE_OMNIPRESENT
|
|
|
|
#define UNRETENTIVE DST_KEY_STATE_UNRETENTIVE
|
2020-02-13 14:44:37 -08:00
|
|
|
#define NA DST_KEY_STATE_NA
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
/* Quickly get key state timing metadata. */
|
|
|
|
#define NUM_KEYSTATES (DST_MAX_KEYSTATES)
|
2020-02-12 13:59:18 +01:00
|
|
|
static int keystatetimes[NUM_KEYSTATES] = { DST_TIME_DNSKEY, DST_TIME_ZRRSIG,
|
|
|
|
DST_TIME_KRRSIG, DST_TIME_DS };
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Readable key state types and values. */
|
2020-02-12 13:59:18 +01:00
|
|
|
static const char *keystatetags[NUM_KEYSTATES] = { "DNSKEY", "ZRRSIG", "KRRSIG",
|
|
|
|
"DS" };
|
|
|
|
static const char *keystatestrings[4] = { "HIDDEN", "RUMOURED", "OMNIPRESENT",
|
|
|
|
"UNRETENTIVE" };
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Print key role.
|
|
|
|
*
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
static const char *
|
2020-02-13 14:44:37 -08:00
|
|
|
keymgr_keyrole(dst_key_t *key) {
|
2019-10-17 11:19:35 +02:00
|
|
|
bool ksk, zsk;
|
|
|
|
dst_key_getbool(key, DST_BOOL_KSK, &ksk);
|
|
|
|
dst_key_getbool(key, DST_BOOL_ZSK, &zsk);
|
|
|
|
if (ksk && zsk) {
|
|
|
|
return ("CSK");
|
|
|
|
} else if (ksk) {
|
|
|
|
return ("KSK");
|
2020-02-05 16:04:09 +11:00
|
|
|
} else if (zsk) {
|
2019-10-17 11:19:35 +02:00
|
|
|
return ("ZSK");
|
|
|
|
}
|
|
|
|
return ("NOSIGN");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate prepublication time of a successor key of 'key'.
|
|
|
|
* This function can have side effects:
|
|
|
|
* If the lifetime is not set, it will be set now.
|
|
|
|
* If there should be a retire time and it is not set, it will be set now.
|
|
|
|
* If there is no active time set, which would be super weird, set it now.
|
|
|
|
*
|
|
|
|
* This returns when the successor key needs to be published in the zone.
|
|
|
|
* A special value of 0 means there is no need for a successor.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static isc_stdtime_t
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp,
|
2020-02-13 14:44:37 -08:00
|
|
|
uint32_t lifetime, isc_stdtime_t now) {
|
|
|
|
isc_result_t ret;
|
2020-03-27 10:28:22 +01:00
|
|
|
isc_stdtime_t active, retire, pub, prepub;
|
2020-02-13 14:44:37 -08:00
|
|
|
bool ksk = false;
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
REQUIRE(key != NULL);
|
|
|
|
REQUIRE(key->key != NULL);
|
|
|
|
|
|
|
|
active = 0;
|
|
|
|
retire = 0;
|
|
|
|
prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) +
|
|
|
|
dns_kasp_zonepropagationdelay(kasp);
|
|
|
|
ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
|
|
|
|
if (ret == ISC_R_SUCCESS && ksk) {
|
|
|
|
/* Add registration delay to the prepublication time. */
|
|
|
|
prepub += dns_kasp_parentregistrationdelay(kasp);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire);
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
|
|
uint32_t klifetime = 0;
|
|
|
|
/*
|
2020-03-27 10:28:22 +01:00
|
|
|
* An active key must have publish and activate timing
|
|
|
|
* metadata.
|
2019-10-17 11:19:35 +02:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
2020-03-27 10:28:22 +01:00
|
|
|
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;
|
|
|
|
}
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime);
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
|
|
dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime);
|
|
|
|
klifetime = lifetime;
|
|
|
|
}
|
|
|
|
if (klifetime == 0) {
|
|
|
|
/*
|
|
|
|
* No inactive time and no lifetime,
|
|
|
|
* so no need to start a rollover.
|
|
|
|
*/
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
retire = active + klifetime;
|
|
|
|
dst_key_settime(key->key, DST_TIME_INACTIVE, retire);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Publish successor 'prepub' time before the 'retire' time of 'key'.
|
|
|
|
*/
|
|
|
|
return (retire - prepub);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2020-02-13 14:44:37 -08:00
|
|
|
keymgr_key_retire(dns_dnsseckey_t *key, isc_stdtime_t now) {
|
|
|
|
char keystr[DST_KEY_FORMATSIZE];
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t s;
|
2020-02-13 14:44:37 -08:00
|
|
|
bool ksk, zsk;
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
REQUIRE(key != NULL);
|
|
|
|
REQUIRE(key->key != NULL);
|
|
|
|
|
|
|
|
/* This key wants to retire and hide in a corner. */
|
|
|
|
dst_key_settime(key->key, DST_TIME_INACTIVE, now);
|
|
|
|
dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN);
|
|
|
|
|
|
|
|
/* This key may not have key states set yet. Pretend as if they are
|
|
|
|
* in the OMNIPRESENT state.
|
|
|
|
*/
|
|
|
|
if (dst_key_getstate(key->key, DST_KEY_DNSKEY, &s) != ISC_R_SUCCESS) {
|
|
|
|
dst_key_setstate(key->key, DST_KEY_DNSKEY, OMNIPRESENT);
|
|
|
|
dst_key_settime(key->key, DST_TIME_DNSKEY, now);
|
|
|
|
}
|
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
(void)dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
|
2019-10-17 11:19:35 +02:00
|
|
|
if (ksk) {
|
|
|
|
if (dst_key_getstate(key->key, DST_KEY_KRRSIG, &s) !=
|
2020-02-12 13:59:18 +01:00
|
|
|
ISC_R_SUCCESS) {
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_setstate(key->key, DST_KEY_KRRSIG, OMNIPRESENT);
|
|
|
|
dst_key_settime(key->key, DST_TIME_KRRSIG, now);
|
|
|
|
}
|
2020-02-13 14:44:37 -08:00
|
|
|
if (dst_key_getstate(key->key, DST_KEY_DS, &s) != ISC_R_SUCCESS)
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_setstate(key->key, DST_KEY_DS, OMNIPRESENT);
|
|
|
|
dst_key_settime(key->key, DST_TIME_DS, now);
|
|
|
|
}
|
|
|
|
}
|
2020-02-12 13:59:18 +01:00
|
|
|
(void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
|
2019-10-17 11:19:35 +02:00
|
|
|
if (zsk) {
|
|
|
|
if (dst_key_getstate(key->key, DST_KEY_ZRRSIG, &s) !=
|
2020-02-12 13:59:18 +01:00
|
|
|
ISC_R_SUCCESS) {
|
2019-10-17 11:19:35 +02:00
|
|
|
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,
|
2020-02-12 13:59:18 +01:00
|
|
|
ISC_LOG_INFO, "keymgr: retire DNSKEY %s (%s)", keystr,
|
|
|
|
keymgr_keyrole(key->key));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
2020-02-13 14:44:37 -08:00
|
|
|
keymgr_dnsseckey_kaspkey_match(dns_dnsseckey_t *dkey, dns_kasp_key_t *kkey) {
|
|
|
|
dst_key_t *key;
|
2019-10-17 11:19:35 +02:00
|
|
|
isc_result_t ret;
|
2020-02-13 14:44:37 -08:00
|
|
|
bool role = false;
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
REQUIRE(dkey != NULL);
|
|
|
|
REQUIRE(kkey != NULL);
|
|
|
|
|
|
|
|
key = dkey->key;
|
|
|
|
|
|
|
|
/* Matching algorithms? */
|
|
|
|
if (dst_key_alg(key) != dns_kasp_key_algorithm(kkey)) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
/* Matching length? */
|
|
|
|
if (dst_key_size(key) != dns_kasp_key_size(kkey)) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
/* Matching role? */
|
|
|
|
ret = dst_key_getbool(key, DST_BOOL_KSK, &role);
|
|
|
|
if (ret != ISC_R_SUCCESS || role != dns_kasp_key_ksk(kkey)) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
ret = dst_key_getbool(key, DST_BOOL_ZSK, &role);
|
|
|
|
if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(kkey)) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Found a match. */
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a new key for 'origin' given the kasp key configuration 'kkey'.
|
|
|
|
* This will check for key id collisions with keys in 'keylist'.
|
|
|
|
* The created key will be stored in 'dst_key'.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static isc_result_t
|
|
|
|
keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin,
|
|
|
|
dns_rdataclass_t rdclass, isc_mem_t *mctx,
|
2020-02-13 14:44:37 -08:00
|
|
|
dns_dnsseckeylist_t *keylist, dst_key_t **dst_key) {
|
|
|
|
bool conflict;
|
|
|
|
int keyflags = DNS_KEYOWNER_ZONE;
|
2019-10-17 11:19:35 +02:00
|
|
|
isc_result_t result = ISC_R_SUCCESS;
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_t *newkey = NULL;
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
do {
|
|
|
|
uint16_t id;
|
|
|
|
uint32_t rid;
|
|
|
|
uint32_t algo = dns_kasp_key_algorithm(kkey);
|
2020-02-13 14:44:37 -08:00
|
|
|
int size = dns_kasp_key_size(kkey);
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
conflict = false;
|
|
|
|
|
|
|
|
if (dns_kasp_key_ksk(kkey)) {
|
|
|
|
keyflags |= DNS_KEYFLAG_KSK;
|
|
|
|
}
|
|
|
|
RETERR(dst_key_generate(origin, algo, size, 0, keyflags,
|
|
|
|
DNS_KEYPROTO_DNSSEC, rdclass, mctx,
|
|
|
|
&newkey, NULL));
|
|
|
|
|
|
|
|
/* Key collision? */
|
|
|
|
id = dst_key_id(newkey);
|
|
|
|
rid = dst_key_rid(newkey);
|
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keylist);
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
if (dst_key_alg(dkey->key) != algo) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (dst_key_id(dkey->key) == id ||
|
|
|
|
dst_key_rid(dkey->key) == id ||
|
|
|
|
dst_key_id(dkey->key) == rid ||
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_rid(dkey->key) == rid)
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Try again. */
|
|
|
|
conflict = true;
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
|
|
|
|
DNS_LOGMODULE_DNSSEC,
|
|
|
|
ISC_LOG_WARNING,
|
|
|
|
"keymgr: key collision id %d",
|
|
|
|
dst_key_id(newkey));
|
|
|
|
dst_key_free(&newkey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (conflict == true);
|
|
|
|
|
|
|
|
INSIST(!conflict);
|
|
|
|
dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey));
|
|
|
|
dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey));
|
|
|
|
dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey));
|
|
|
|
*dst_key = newkey;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
|
|
|
|
failure:
|
|
|
|
return (result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the desired state for this record 'type'. The desired state depends
|
|
|
|
* on whether the key wants to be active, or wants to retire. This implements
|
|
|
|
* the edges of our state machine:
|
|
|
|
*
|
|
|
|
* ----> OMNIPRESENT ----
|
|
|
|
* | |
|
|
|
|
* | \|/
|
|
|
|
*
|
|
|
|
* RUMOURED <----> UNRETENTIVE
|
|
|
|
*
|
|
|
|
* /|\ |
|
|
|
|
* | |
|
|
|
|
* ---- HIDDEN <----
|
|
|
|
*
|
|
|
|
* A key that wants to be active eventually wants to have its record types
|
|
|
|
* in the OMNIPRESENT state (that is, all resolvers that know about these
|
|
|
|
* type of records know about these records specifically).
|
|
|
|
*
|
|
|
|
* A key that wants to be retired eventually wants to have its record types
|
|
|
|
* in the HIDDEN state (that is, all resolvers that know about these type
|
|
|
|
* of records specifically don't know about these records).
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static dst_key_state_t
|
2020-02-13 14:44:37 -08:00
|
|
|
keymgr_desiredstate(dns_dnsseckey_t *key, dst_key_state_t state) {
|
2019-10-17 11:19:35 +02:00
|
|
|
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
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_match_state(dst_key_t *key, dst_key_t *subject, int type,
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_state_t next_state, dst_key_state_t states[4]) {
|
2019-10-17 11:19:35 +02:00
|
|
|
REQUIRE(key != NULL);
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
dst_key_state_t state;
|
|
|
|
if (states[i] == NA) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (next_state != NA && i == type &&
|
|
|
|
dst_key_id(key) == dst_key_id(subject)) {
|
|
|
|
/* Check next state rather than current state. */
|
|
|
|
state = next_state;
|
|
|
|
} else if (dst_key_getstate(key, i, &state) != ISC_R_SUCCESS) {
|
|
|
|
/* This is fine only if expected state is HIDDEN. */
|
|
|
|
if (states[i] != HIDDEN) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (state != states[i]) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Match. */
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a 'k2' is a successor of 'k1'. This is a simplified version of
|
|
|
|
* Equation(2) of "Flexible and Robust Key Rollover" which defines a
|
|
|
|
* recursive relation.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-13 14:44:37 -08:00
|
|
|
keymgr_key_is_successor(dst_key_t *k1, dst_key_t *k2) {
|
2019-10-17 11:19:35 +02:00
|
|
|
uint32_t suc = 0, pre = 0;
|
|
|
|
if (dst_key_getnum(k1, DST_NUM_SUCCESSOR, &suc) != ISC_R_SUCCESS) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
if (dst_key_getnum(k2, DST_NUM_PREDECESSOR, &pre) != ISC_R_SUCCESS) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
return (dst_key_id(k1) == pre && dst_key_id(k2) == suc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a key exists in 'keyring' that matches 'states'.
|
|
|
|
*
|
|
|
|
* If 'match_algorithms', the key must also match the algorithm of 'key'.
|
|
|
|
* If 'next_state' is not NA, we are actually looking for a key as if
|
|
|
|
* 'key' already transitioned to the next state.
|
|
|
|
* If 'check_successor', we also want to make sure there is a successor
|
|
|
|
* relationship with the found key that matches 'states2'.
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_exists_with_state(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
|
|
|
|
int type, dst_key_state_t next_state,
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t states[4],
|
2020-02-12 13:59:18 +01:00
|
|
|
dst_key_state_t states2[4], bool check_successor,
|
2020-02-13 14:44:37 -08:00
|
|
|
bool match_algorithms) {
|
2020-02-12 13:59:18 +01:00
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
if (match_algorithms &&
|
2020-02-12 13:59:18 +01:00
|
|
|
(dst_key_alg(dkey->key) != dst_key_alg(key->key))) {
|
2019-10-17 11:19:35 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (check_successor &&
|
|
|
|
keymgr_key_match_state(dkey->key, key->key, type,
|
2020-02-13 14:44:37 -08:00
|
|
|
next_state, states2))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Found a possible successor, look for predecessor. */
|
|
|
|
for (dns_dnsseckey_t *pkey = ISC_LIST_HEAD(*keyring);
|
2020-02-13 14:44:37 -08:00
|
|
|
pkey != NULL; pkey = ISC_LIST_NEXT(pkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
if (pkey == dkey) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-02-12 13:59:18 +01:00
|
|
|
if (!keymgr_key_match_state(pkey->key, key->key,
|
|
|
|
type, next_state,
|
|
|
|
states)) {
|
2019-10-17 11:19:35 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Found a possible predecessor, check
|
|
|
|
* relationship.
|
|
|
|
*/
|
|
|
|
if (keymgr_key_is_successor(pkey->key,
|
2020-02-12 13:59:18 +01:00
|
|
|
dkey->key)) {
|
2019-10-17 11:19:35 +02:00
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!check_successor &&
|
|
|
|
keymgr_key_match_state(dkey->key, key->key, type,
|
2020-02-13 14:44:37 -08:00
|
|
|
next_state, states))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* No match. */
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a key has a successor.
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-13 14:44:37 -08:00
|
|
|
keymgr_key_has_successor(dns_dnsseckey_t *key, dns_dnsseckeylist_t *keyring) {
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Don't worry about key states. */
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA };
|
|
|
|
return (keymgr_key_exists_with_state(keyring, key, DST_KEY_DNSKEY, NA,
|
|
|
|
na, na, true, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if all keys have their DS hidden. If not, then there must be at
|
|
|
|
* least one key with an OMNIPRESENT DNSKEY.
|
|
|
|
*
|
|
|
|
* If 'next_state' is not NA, we are actually looking for a key as if
|
|
|
|
* 'key' already transitioned to the next state.
|
|
|
|
* If 'match_algorithms', only consider keys with same algorithm of 'key'.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key,
|
|
|
|
int type, dst_key_state_t next_state,
|
2020-02-13 14:44:37 -08:00
|
|
|
bool match_algorithms, bool must_be_hidden) {
|
2020-02-12 13:59:18 +01:00
|
|
|
dst_key_state_t dnskey_omnipresent[4] = { OMNIPRESENT, NA, OMNIPRESENT,
|
|
|
|
NA }; /* (3e) */
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t ds_hidden[4] = { NA, NA, NA, HIDDEN }; /* (3e) */
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
|
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
char keystr[DST_KEY_FORMATSIZE];
|
|
|
|
dst_key_format(dkey->key, keystr, sizeof(keystr));
|
|
|
|
|
|
|
|
if (match_algorithms &&
|
2020-02-12 13:59:18 +01:00
|
|
|
(dst_key_alg(dkey->key) != dst_key_alg(key->key))) {
|
2019-10-17 11:19:35 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keymgr_key_match_state(dkey->key, key->key, type,
|
2020-02-12 13:59:18 +01:00
|
|
|
next_state, ds_hidden)) {
|
2019-10-17 11:19:35 +02:00
|
|
|
/* This key has its DS hidden. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (must_be_hidden) {
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This key does not have its DS hidden. There must be at
|
|
|
|
* least one key with the same algorithm that provides a
|
|
|
|
* chain of trust (can be this key).
|
|
|
|
*/
|
|
|
|
dnskey_omnipresent[DST_KEY_DS] = NA;
|
2020-02-18 16:55:36 +01:00
|
|
|
if (next_state != NA &&
|
2020-02-20 11:04:08 +01:00
|
|
|
dst_key_id(dkey->key) == dst_key_id(key->key)) {
|
2020-02-18 16:55:36 +01:00
|
|
|
/* Check next state rather than current state. */
|
|
|
|
dnskey_omnipresent[DST_KEY_DS] = next_state;
|
|
|
|
} else {
|
|
|
|
(void)dst_key_getstate(dkey->key, DST_KEY_DS,
|
|
|
|
&dnskey_omnipresent[DST_KEY_DS]);
|
|
|
|
}
|
2020-02-12 13:59:18 +01:00
|
|
|
if (!keymgr_key_exists_with_state(
|
|
|
|
keyring, key, type, next_state, dnskey_omnipresent,
|
2020-02-13 14:44:37 -08:00
|
|
|
na, false, match_algorithms))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/* 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,
|
2020-02-13 14:44:37 -08:00
|
|
|
bool match_algorithms) {
|
2020-02-12 13:59:18 +01:00
|
|
|
dst_key_state_t rrsig_omnipresent[4] = { NA, OMNIPRESENT, NA,
|
|
|
|
NA }; /* (3i) */
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t dnskey_hidden[4] = { HIDDEN, NA, NA, NA }; /* (3i) */
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
|
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
if (match_algorithms &&
|
2020-02-12 13:59:18 +01:00
|
|
|
(dst_key_alg(dkey->key) != dst_key_alg(key->key))) {
|
2019-10-17 11:19:35 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keymgr_key_match_state(dkey->key, key->key, type,
|
2020-02-13 14:44:37 -08:00
|
|
|
next_state, dnskey_hidden))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/* This key has its DNSKEY hidden. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This key does not have its DNSKEY hidden. There must be at
|
|
|
|
* least one key with the same algorithm that has its RRSIG
|
|
|
|
* records OMNIPRESENT.
|
|
|
|
*/
|
|
|
|
rrsig_omnipresent[DST_KEY_DNSKEY] = NA;
|
|
|
|
(void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY,
|
|
|
|
&rrsig_omnipresent[DST_KEY_DNSKEY]);
|
2020-02-13 14:44:37 -08:00
|
|
|
if (!keymgr_key_exists_with_state(keyring, key, type,
|
|
|
|
next_state, rrsig_omnipresent,
|
|
|
|
na, false, match_algorithms))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/* There is no chain of trust. */
|
|
|
|
return (false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* All good. */
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for existence of DS.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_have_ds(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_state_t next_state) {
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t states[2][4] = {
|
|
|
|
/* DNSKEY, ZRRSIG, KRRSIG, DS */
|
2020-02-12 13:59:18 +01:00
|
|
|
{ NA, NA, NA, OMNIPRESENT }, /* DS present */
|
|
|
|
{ NA, NA, NA, RUMOURED } /* DS introducing */
|
2019-10-17 11:19:35 +02:00
|
|
|
};
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Equation (3a):
|
|
|
|
* There is a key with the DS in either RUMOURD or OMNIPRESENT state.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
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));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for existence of DNSKEY, or at least a good DNSKEY state.
|
|
|
|
* See equations what are good DNSKEY states.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_have_dnskey(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_state_t next_state) {
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t states[9][4] = {
|
|
|
|
/* DNSKEY, ZRRSIG, KRRSIG, DS */
|
2020-02-12 13:59:18 +01:00
|
|
|
{ OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }, /* (3b) */
|
2019-10-17 11:19:35 +02:00
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
{ OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }, /* (3c)p */
|
|
|
|
{ OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }, /* (3c)s */
|
2019-10-17 11:19:35 +02:00
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
{ 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 */
|
2019-10-17 11:19:35 +02:00
|
|
|
};
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
|
|
|
|
|
|
|
|
return (
|
|
|
|
/*
|
|
|
|
* Equation (3b):
|
|
|
|
* There is a key with the same algorithm with its DNSKEY,
|
|
|
|
* KRRSIG and DS records in OMNIPRESENT state.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_exists_with_state(keyring, key, type, next_state,
|
|
|
|
states[0], na, false, true) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_exists_with_state(keyring, key, type, next_state,
|
|
|
|
states[1], states[2], true,
|
|
|
|
true) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
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) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_ds_hidden_or_chained(keyring, key, type, next_state,
|
|
|
|
true, false));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for existence of RRSIG (zsk), or a good RRSIG state.
|
|
|
|
* See equations what are good RRSIG states.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static bool
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type,
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_state_t next_state) {
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t states[11][4] = {
|
|
|
|
/* DNSKEY, ZRRSIG, KRRSIG, DS */
|
2020-02-12 13:59:18 +01:00
|
|
|
{ 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 */
|
2019-10-17 11:19:35 +02:00
|
|
|
};
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
|
|
|
|
|
|
|
|
return (
|
|
|
|
/*
|
|
|
|
* If all DS records are hidden than this rule can be ignored.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_ds_hidden_or_chained(keyring, key, type, next_state,
|
|
|
|
true, true) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* Equation (3f):
|
|
|
|
* There is a key with the same algorithm with its DNSKEY and
|
|
|
|
* ZRRSIG records in OMNIPRESENT state.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_exists_with_state(keyring, key, type, next_state,
|
|
|
|
states[0], na, false, true) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_exists_with_state(keyring, key, type, next_state,
|
|
|
|
states[1], states[2], true,
|
|
|
|
true) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_key_exists_with_state(keyring, key, type, next_state,
|
|
|
|
states[3], states[4], true,
|
|
|
|
true) ||
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_dnskey_hidden_or_chained(keyring, key, type, next_state,
|
|
|
|
true));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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,
|
2020-02-13 14:44:37 -08:00
|
|
|
int type, dst_key_state_t next) {
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t dnskeystate = HIDDEN;
|
2020-02-12 13:59:18 +01:00
|
|
|
dst_key_state_t ksk_present[4] = { OMNIPRESENT, NA, OMNIPRESENT,
|
|
|
|
OMNIPRESENT };
|
|
|
|
dst_key_state_t ds_rumoured[4] = { OMNIPRESENT, NA, OMNIPRESENT,
|
|
|
|
RUMOURED };
|
|
|
|
dst_key_state_t ds_retired[4] = { OMNIPRESENT, NA, OMNIPRESENT,
|
|
|
|
UNRETENTIVE };
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_state_t ksk_rumoured[4] = { RUMOURED, NA, NA, OMNIPRESENT };
|
|
|
|
dst_key_state_t ksk_retired[4] = { UNRETENTIVE, NA, NA, OMNIPRESENT };
|
|
|
|
dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */
|
|
|
|
|
|
|
|
if (next != RUMOURED) {
|
|
|
|
/*
|
|
|
|
* Local policy only adds an extra barrier on transitions to
|
|
|
|
* the RUMOURED state.
|
|
|
|
*/
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case DST_KEY_DNSKEY:
|
|
|
|
/* No restrictions. */
|
|
|
|
return (true);
|
|
|
|
case DST_KEY_ZRRSIG:
|
|
|
|
/* Make sure the DNSKEY record is OMNIPRESENT. */
|
|
|
|
(void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate);
|
|
|
|
if (dnskeystate == OMNIPRESENT) {
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Or are we introducing a new key for this algorithm? Because
|
|
|
|
* in that case allow publishing the RRSIG records before the
|
|
|
|
* DNSKEY.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
return (!(keymgr_key_exists_with_state(keyring, key, type, next,
|
|
|
|
ksk_present, na, false,
|
2019-10-17 11:19:35 +02:00
|
|
|
true) ||
|
2020-02-12 13:59:18 +01:00
|
|
|
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)));
|
2019-10-17 11:19:35 +02:00
|
|
|
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,
|
2020-02-13 14:44:37 -08:00
|
|
|
int type, dst_key_state_t next_state) {
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Debug logging. */
|
|
|
|
if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
|
|
|
|
bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b;
|
|
|
|
char keystr[DST_KEY_FORMATSIZE];
|
|
|
|
dst_key_format(key->key, keystr, sizeof(keystr));
|
|
|
|
rule1a = keymgr_have_ds(keyring, key, type, NA);
|
|
|
|
rule1b = keymgr_have_ds(keyring, key, type, next_state);
|
|
|
|
rule2a = keymgr_have_dnskey(keyring, key, type, NA);
|
|
|
|
rule2b = keymgr_have_dnskey(keyring, key, type, next_state);
|
|
|
|
rule3a = keymgr_have_rrsig(keyring, key, type, NA);
|
|
|
|
rule3b = keymgr_have_rrsig(keyring, key, type, next_state);
|
2020-02-12 13:59:18 +01:00
|
|
|
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");
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
/*
|
|
|
|
* Rule 1: There must be a DS at all times.
|
|
|
|
* First check the current situation: if the rule check fails,
|
|
|
|
* we allow the transition to attempt to move us out of the
|
|
|
|
* invalid state. If the rule check passes, also check if
|
|
|
|
* the next state is also still a valid situation.
|
|
|
|
*/
|
|
|
|
(!keymgr_have_ds(keyring, key, type, NA) ||
|
|
|
|
keymgr_have_ds(keyring, key, type, next_state)) &&
|
|
|
|
/*
|
|
|
|
* Rule 2: There must be a DNSKEY at all times. Again, first
|
|
|
|
* check the current situation, then assess the next state.
|
|
|
|
*/
|
|
|
|
(!keymgr_have_dnskey(keyring, key, type, NA) ||
|
|
|
|
keymgr_have_dnskey(keyring, key, type, next_state)) &&
|
|
|
|
/*
|
|
|
|
* Rule 3: There must be RRSIG records at all times. Again,
|
|
|
|
* first check the current situation, then assess the next
|
|
|
|
* state.
|
|
|
|
*/
|
|
|
|
(!keymgr_have_rrsig(keyring, key, type, NA) ||
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_have_rrsig(keyring, key, type, next_state)));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate the time when it is safe to do the next transition.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_transition_time(dns_dnsseckey_t *key, int type,
|
|
|
|
dst_key_state_t next_state, dns_kasp_t *kasp,
|
2020-02-13 14:44:37 -08:00
|
|
|
isc_stdtime_t now, isc_stdtime_t *when) {
|
|
|
|
isc_result_t ret;
|
2019-10-17 11:19:35 +02:00
|
|
|
isc_stdtime_t lastchange, nexttime = now;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* No need to wait if we move things into an uncertain state.
|
|
|
|
*/
|
|
|
|
if (next_state == RUMOURED || next_state == UNRETENTIVE) {
|
|
|
|
*when = now;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = dst_key_gettime(key->key, keystatetimes[type], &lastchange);
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
|
|
/* No last change, for safety purposes let's set it to now. */
|
|
|
|
dst_key_settime(key->key, keystatetimes[type], now);
|
|
|
|
lastchange = now;
|
|
|
|
}
|
|
|
|
|
2020-02-12 13:59:18 +01:00
|
|
|
switch (type) {
|
2019-10-17 11:19:35 +02:00
|
|
|
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:
|
|
|
|
*
|
2020-02-18 16:57:37 +01:00
|
|
|
* Dsgn + zone-propagation-delay + max-zone-ttl.
|
2019-10-17 11:19:35 +02:00
|
|
|
*
|
|
|
|
* We will also add the retire-safety interval.
|
|
|
|
*/
|
2020-02-18 16:57:37 +01:00
|
|
|
nexttime = lastchange + dns_kasp_zonemaxttl(kasp) +
|
2019-10-17 11:19:35 +02:00
|
|
|
dns_kasp_zonepropagationdelay(kasp) +
|
|
|
|
dns_kasp_retiresafety(kasp);
|
2020-02-18 16:57:37 +01:00
|
|
|
/*
|
|
|
|
* Only add the sign delay Dsgn if there is an actual
|
|
|
|
* predecessor key.
|
|
|
|
*/
|
|
|
|
uint32_t pre;
|
|
|
|
if (dst_key_getnum(key->key, DST_NUM_PREDECESSOR,
|
|
|
|
&pre) == ISC_R_SUCCESS) {
|
|
|
|
nexttime += dns_kasp_signdelay(kasp);
|
|
|
|
}
|
2019-10-17 11:19:35 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
nexttime = now;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DST_KEY_DS:
|
|
|
|
switch (next_state) {
|
|
|
|
case OMNIPRESENT:
|
|
|
|
case HIDDEN:
|
|
|
|
/*
|
|
|
|
* RFC 7583: The successor DS record is published in
|
|
|
|
* the parent zone and after the registration delay
|
|
|
|
* (Dreg), the time taken after the DS record has been
|
|
|
|
* submitted to the parent zone manager for it to be
|
|
|
|
* placed in the zone. Key N (the predecessor) must
|
|
|
|
* remain in the zone until any caches that contain a
|
|
|
|
* copy of the DS RRset have a copy containing the new
|
|
|
|
* DS record. This interval is the retire interval
|
|
|
|
* (Iret), given by:
|
|
|
|
*
|
|
|
|
* Iret = DprpP + TTLds
|
|
|
|
*
|
|
|
|
* So we need to wait Dreg + Iret before the DS becomes
|
|
|
|
* OMNIPRESENT. This translates to:
|
|
|
|
*
|
|
|
|
* parent-registration-delay +
|
|
|
|
* parent-propagation-delay + parent-ds-ttl.
|
|
|
|
*
|
|
|
|
* We will also add the retire-safety interval.
|
|
|
|
*/
|
|
|
|
nexttime = lastchange + dns_kasp_dsttl(kasp) +
|
|
|
|
dns_kasp_parentregistrationdelay(kasp) +
|
|
|
|
dns_kasp_parentpropagationdelay(kasp) +
|
|
|
|
dns_kasp_retiresafety(kasp);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
nexttime = now;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
INSIST(0);
|
|
|
|
ISC_UNREACHABLE();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*when = nexttime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update keys.
|
|
|
|
* This implements Algorithm (1) of "Flexible and Robust Key Rollover".
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static isc_result_t
|
2020-02-12 13:59:18 +01:00
|
|
|
keymgr_update(dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, isc_stdtime_t now,
|
2020-02-13 14:44:37 -08:00
|
|
|
isc_stdtime_t *nexttime) {
|
2019-10-17 11:19:35 +02:00
|
|
|
bool changed;
|
|
|
|
|
|
|
|
/* Repeat until nothing changed. */
|
|
|
|
transition:
|
|
|
|
changed = false;
|
|
|
|
|
|
|
|
/* For all keys in the zone. */
|
2020-02-12 13:59:18 +01:00
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
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++) {
|
2020-02-13 14:44:37 -08:00
|
|
|
isc_result_t ret;
|
|
|
|
isc_stdtime_t when;
|
2019-10-17 11:19:35 +02:00
|
|
|
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,
|
2020-02-12 13:59:18 +01:00
|
|
|
next_state)) {
|
2019-10-17 11:19:35 +02:00
|
|
|
/* No, please respect rollover methods. */
|
2020-02-12 13:59:18 +01:00
|
|
|
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]);
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Is the transition DNSSEC safe? */
|
|
|
|
if (!keymgr_transition_allowed(keyring, dkey, i,
|
2020-02-12 13:59:18 +01:00
|
|
|
next_state)) {
|
2019-10-17 11:19:35 +02:00
|
|
|
/* No, this would make the zone bogus. */
|
2020-02-12 13:59:18 +01:00
|
|
|
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]);
|
2019-10-17 11:19:35 +02:00
|
|
|
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. */
|
2020-02-12 13:59:18 +01:00
|
|
|
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);
|
2019-10-17 11:19:35 +02:00
|
|
|
if (*nexttime == 0 || *nexttime > when) {
|
|
|
|
*nexttime = when;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
|
2020-02-12 13:59:18 +01:00
|
|
|
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]);
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
/* It is safe to make the transition. */
|
|
|
|
dst_key_setstate(dkey->key, i, next_state);
|
|
|
|
dst_key_settime(dkey->key, keystatetimes[i], now);
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We changed something, continue processing. */
|
|
|
|
if (changed) {
|
|
|
|
goto transition;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2020-03-27 10:28:22 +01:00
|
|
|
* 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.
|
2019-10-17 11:19:35 +02:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void
|
2020-03-27 10:28:22 +01:00
|
|
|
keymgr_key_init(dns_dnsseckey_t *key, isc_stdtime_t now) {
|
2020-02-13 14:44:37 -08:00
|
|
|
bool ksk, zsk;
|
2019-10-17 11:19:35 +02:00
|
|
|
isc_result_t ret;
|
2020-03-27 10:28:22 +01:00
|
|
|
isc_stdtime_t active = 0, pub = 0, syncpub = 0;
|
|
|
|
dst_key_state_t dnskey_state = HIDDEN;
|
|
|
|
dst_key_state_t ds_state = HIDDEN;
|
|
|
|
dst_key_state_t zrrsig_state = HIDDEN;
|
2019-10-17 11:19:35 +02:00
|
|
|
|
|
|
|
REQUIRE(key != NULL);
|
|
|
|
REQUIRE(key->key != NULL);
|
|
|
|
|
2020-03-27 10:28:22 +01:00
|
|
|
/* Initialize role. */
|
2019-10-17 11:19:35 +02:00
|
|
|
ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk);
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
|
|
ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0);
|
|
|
|
dst_key_setbool(key->key, DST_BOOL_KSK, ksk);
|
|
|
|
}
|
|
|
|
ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk);
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
|
|
zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0);
|
|
|
|
dst_key_setbool(key->key, DST_BOOL_ZSK, zsk);
|
|
|
|
}
|
2020-03-27 10:28:22 +01:00
|
|
|
|
|
|
|
/* Get time metadata. */
|
|
|
|
ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
|
|
|
|
if (active <= now && ret == ISC_R_SUCCESS) {
|
|
|
|
dnskey_state = RUMOURED;
|
|
|
|
}
|
|
|
|
ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
|
|
|
|
if (pub <= now && ret == ISC_R_SUCCESS) {
|
|
|
|
zrrsig_state = RUMOURED;
|
|
|
|
}
|
|
|
|
ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub);
|
|
|
|
if (syncpub <= now && ret == ISC_R_SUCCESS) {
|
|
|
|
ds_state = RUMOURED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set key states for all keys that do not have them. */
|
|
|
|
INITIALIZE_STATE(key->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY,
|
|
|
|
dnskey_state);
|
|
|
|
if (ksk) {
|
|
|
|
INITIALIZE_STATE(key->key, DST_KEY_KRRSIG, DST_TIME_KRRSIG,
|
|
|
|
dnskey_state);
|
|
|
|
INITIALIZE_STATE(key->key, DST_KEY_DS, DST_TIME_DS, ds_state);
|
|
|
|
}
|
|
|
|
if (zsk) {
|
|
|
|
INITIALIZE_STATE(key->key, DST_KEY_ZRRSIG, DST_TIME_ZRRSIG,
|
|
|
|
zrrsig_state);
|
|
|
|
}
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Examine 'keys' and match 'kasp' policy.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
isc_result_t
|
|
|
|
dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
|
|
|
|
const char *directory, isc_mem_t *mctx,
|
|
|
|
dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp,
|
2020-02-13 14:44:37 -08:00
|
|
|
isc_stdtime_t now, isc_stdtime_t *nexttime) {
|
|
|
|
isc_result_t result = ISC_R_SUCCESS;
|
2019-10-17 11:19:35 +02:00
|
|
|
dns_dnsseckeylist_t newkeys;
|
2020-02-13 14:44:37 -08:00
|
|
|
dns_kasp_key_t *kkey;
|
|
|
|
dns_dnsseckey_t *candidate = NULL;
|
|
|
|
dns_dnsseckey_t *newkey = NULL;
|
|
|
|
dst_key_t *dst_key = NULL;
|
|
|
|
isc_dir_t dir;
|
|
|
|
bool dir_open = false;
|
|
|
|
int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
|
2019-10-17 11:19:35 +02:00
|
|
|
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,
|
2020-02-12 13:59:18 +01:00
|
|
|
DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
|
|
|
|
"keymgr: keyring empty (zone %s policy "
|
|
|
|
"%s)",
|
|
|
|
namebuf, dns_kasp_getname(kasp));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring);
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_format(dkey->key, keystr, sizeof(keystr));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
|
|
|
|
DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
|
|
|
|
"keymgr: keyring: dnskey %s (policy %s)",
|
2020-02-12 13:59:18 +01:00
|
|
|
keystr, dns_kasp_getname(kasp));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 16:58:56 +01:00
|
|
|
/* Do we need to remove keys? */
|
2020-02-20 11:04:08 +01:00
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
|
|
|
|
dkey = ISC_LIST_NEXT(dkey, link))
|
2020-02-18 16:58:56 +01:00
|
|
|
{
|
|
|
|
bool found_match = false;
|
|
|
|
|
2020-03-27 10:28:22 +01:00
|
|
|
keymgr_key_init(dkey, now);
|
2020-02-18 16:58:56 +01:00
|
|
|
|
|
|
|
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, now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Create keys according to the policy, if come in short. */
|
2019-11-04 16:26:39 +01:00
|
|
|
for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
kkey = ISC_LIST_NEXT(kkey, link))
|
|
|
|
{
|
|
|
|
isc_stdtime_t retire = 0, active = 0, prepub = 0;
|
|
|
|
uint32_t lifetime = dns_kasp_key_lifetime(kkey);
|
2019-10-17 11:19:35 +02:00
|
|
|
dns_dnsseckey_t *active_key = NULL;
|
|
|
|
|
|
|
|
/* Do we have keys available for this kasp key? */
|
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring);
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) {
|
|
|
|
/* Found a match. */
|
2020-03-27 10:28:22 +01:00
|
|
|
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));
|
2019-10-17 11:19:35 +02:00
|
|
|
|
2020-03-27 10:28:22 +01:00
|
|
|
/* Initialize lifetime and goal, if not set. */
|
2019-10-17 11:19:35 +02:00
|
|
|
uint32_t l;
|
2020-02-12 13:59:18 +01:00
|
|
|
if (dst_key_getnum(dkey->key, DST_NUM_LIFETIME,
|
|
|
|
&l) != ISC_R_SUCCESS) {
|
2019-10-17 11:19:35 +02:00
|
|
|
dst_key_setnum(dkey->key,
|
|
|
|
DST_NUM_LIFETIME,
|
|
|
|
lifetime);
|
|
|
|
}
|
|
|
|
|
2020-03-27 10:28:22 +01:00
|
|
|
dst_key_state_t goal;
|
|
|
|
if (dst_key_getstate(dkey->key, DST_KEY_GOAL,
|
|
|
|
&goal) != ISC_R_SUCCESS) {
|
|
|
|
dst_key_setstate(dkey->key,
|
|
|
|
DST_KEY_GOAL,
|
|
|
|
OMNIPRESENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
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_key_is_successor(
|
|
|
|
dkey->key,
|
|
|
|
active_key->key) &&
|
|
|
|
!keymgr_key_is_successor(
|
|
|
|
active_key->key, dkey->key))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/*
|
|
|
|
* Multiple signing keys match
|
|
|
|
* the kasp key configuration.
|
2020-03-27 10:28:22 +01:00
|
|
|
* Retire excess keys in use.
|
2019-10-17 11:19:35 +02:00
|
|
|
*/
|
|
|
|
keymgr_key_retire(dkey, now);
|
|
|
|
}
|
2020-03-27 10:28:22 +01:00
|
|
|
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;
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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));
|
2020-02-12 13:59:18 +01:00
|
|
|
isc_log_write(
|
|
|
|
dns_lctx, DNS_LOGCATEGORY_DNSSEC,
|
|
|
|
DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
|
2020-03-27 10:28:22 +01:00
|
|
|
"keymgr: DNSKEY %s (%s) is active in "
|
2020-02-12 13:59:18 +01:00
|
|
|
"policy %s",
|
|
|
|
keystr, keymgr_keyrole(active_key->key),
|
|
|
|
dns_kasp_getname(kasp));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate when the successor needs to be published
|
|
|
|
* in the zone.
|
|
|
|
*/
|
|
|
|
prepub = keymgr_prepublication_time(active_key, kasp,
|
|
|
|
lifetime, now);
|
|
|
|
if (prepub == 0 || prepub > now) {
|
|
|
|
/* No need to start rollover now. */
|
|
|
|
if (*nexttime == 0 || prepub < *nexttime) {
|
|
|
|
*nexttime = prepub;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (keymgr_key_has_successor(active_key, keyring)) {
|
|
|
|
/* Key already has successor. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) {
|
|
|
|
dst_key_format(active_key->key, keystr,
|
|
|
|
sizeof(keystr));
|
2020-02-12 13:59:18 +01:00
|
|
|
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));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
} 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,
|
2020-02-12 13:59:18 +01:00
|
|
|
DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1),
|
2019-10-17 11:19:35 +02:00
|
|
|
"keymgr: no active key found for %s "
|
2020-02-12 13:59:18 +01:00
|
|
|
"(policy %s)",
|
|
|
|
namestr, dns_kasp_getname(kasp));
|
2019-10-17 11:19:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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.
|
|
|
|
*/
|
2020-02-12 13:59:18 +01:00
|
|
|
for (candidate = ISC_LIST_HEAD(*keyring); candidate != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
candidate = ISC_LIST_NEXT(candidate, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
if (keymgr_dnsseckey_kaspkey_match(candidate, kkey) &&
|
2020-02-13 14:44:37 -08:00
|
|
|
dst_key_is_unused(candidate->key))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
/* Found a candidate in keyring. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (candidate == NULL) {
|
|
|
|
/* No key available in keyring, create a new one. */
|
|
|
|
RETERR(keymgr_createkey(kkey, origin, rdclass, mctx,
|
|
|
|
keyring, &dst_key));
|
|
|
|
dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp));
|
|
|
|
dst_key_settime(dst_key, DST_TIME_CREATED, now);
|
|
|
|
RETERR(dns_dnsseckey_create(mctx, &dst_key, &newkey));
|
2020-03-27 10:28:22 +01:00
|
|
|
keymgr_key_init(newkey, now);
|
2019-10-17 11:19:35 +02:00
|
|
|
} else {
|
|
|
|
newkey = candidate;
|
|
|
|
dst_key_setnum(newkey->key, DST_NUM_LIFETIME, lifetime);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Got a key. */
|
|
|
|
if (active_key == NULL) {
|
|
|
|
/*
|
|
|
|
* If there is no active key found yet for this kasp
|
|
|
|
* key configuration, immediately make this key active.
|
|
|
|
*/
|
|
|
|
dst_key_settime(newkey->key, DST_TIME_PUBLISH, now);
|
|
|
|
dst_key_settime(newkey->key, DST_TIME_ACTIVATE, now);
|
|
|
|
active = now;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* This is a successor. Mark the relationship.
|
|
|
|
*/
|
|
|
|
dst_key_setnum(newkey->key, DST_NUM_PREDECESSOR,
|
|
|
|
dst_key_id(active_key->key));
|
|
|
|
dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR,
|
|
|
|
dst_key_id(newkey->key));
|
|
|
|
(void)dst_key_gettime(active_key->key,
|
|
|
|
DST_TIME_INACTIVE, &retire);
|
|
|
|
dst_key_settime(newkey->key, DST_TIME_PUBLISH, prepub);
|
|
|
|
dst_key_settime(newkey->key, DST_TIME_ACTIVATE, retire);
|
|
|
|
active = retire;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This key wants to be present. */
|
|
|
|
dst_key_setstate(newkey->key, DST_KEY_GOAL, OMNIPRESENT);
|
|
|
|
|
|
|
|
/* Do we need to set retire time? */
|
|
|
|
(void)dst_key_getnum(newkey->key, DST_NUM_LIFETIME, &lifetime);
|
|
|
|
if (lifetime > 0) {
|
|
|
|
dst_key_settime(newkey->key, DST_TIME_INACTIVE,
|
|
|
|
(active + lifetime));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Append dnsseckey to list of new keys. */
|
|
|
|
dns_dnssec_get_hints(newkey, now);
|
|
|
|
newkey->source = dns_keysource_repository;
|
|
|
|
INSIST(!newkey->legacy);
|
|
|
|
if (candidate == NULL) {
|
|
|
|
ISC_LIST_APPEND(newkeys, newkey, link);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Logging. */
|
|
|
|
dst_key_format(newkey->key, keystr, sizeof(keystr));
|
|
|
|
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
|
|
|
|
DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO,
|
2020-02-12 13:59:18 +01:00
|
|
|
"keymgr: DNSKEY %s (%s) %s for policy %s", keystr,
|
|
|
|
keymgr_keyrole(newkey->key),
|
2019-10-17 11:19:35 +02:00
|
|
|
(candidate != NULL) ? "selected" : "created",
|
|
|
|
dns_kasp_getname(kasp));
|
|
|
|
|
|
|
|
/* Onwards to next kasp key configuration. */
|
|
|
|
candidate = NULL;
|
|
|
|
newkey = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Walked all kasp key configurations. Append new keys. */
|
|
|
|
if (!ISC_LIST_EMPTY(newkeys)) {
|
|
|
|
ISC_LIST_APPENDLIST(*keyring, newkeys, link);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read to update key states. */
|
|
|
|
keymgr_update(keyring, kasp, now, nexttime);
|
|
|
|
|
|
|
|
/* Store key states and update hints. */
|
2020-02-12 13:59:18 +01:00
|
|
|
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL;
|
2020-02-13 14:44:37 -08:00
|
|
|
dkey = ISC_LIST_NEXT(dkey, link))
|
|
|
|
{
|
2019-10-17 11:19:35 +02:00
|
|
|
dns_dnssec_get_hints(dkey, now);
|
|
|
|
RETERR(dst_key_tofile(dkey->key, options, directory));
|
|
|
|
}
|
|
|
|
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
if (dir_open) {
|
|
|
|
isc_dir_close(&dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
INSIST(newkey == NULL);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) {
|
|
|
|
ISC_LIST_UNLINK(newkeys, newkey, link);
|
|
|
|
INSIST(newkey->key != NULL);
|
|
|
|
dst_key_free(&newkey->key);
|
|
|
|
dns_dnsseckey_destroy(mctx, &newkey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (result);
|
|
|
|
}
|