Change checkconf to include built-in dnssec-policy

The configuration should also take into account the built-in
DNSSEC policies when verifying the keys in the key-directory match the
given policy. Update the code accordingly and add some good and
failure test cases.
This commit is contained in:
Matthijs Mekking 2025-08-28 14:48:07 +02:00
parent 3918a8ca4c
commit dcd49f2ead
8 changed files with 282 additions and 30 deletions

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
zone "bad-default-algorithm.example" {
type primary;
file "bad-default-algorithm.example.db";
dnssec-policy "default";
};

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
zone "bad-default-kz.example" {
type primary;
file "bad-default-kz.example.db";
dnssec-policy "default";
};

View file

@ -53,6 +53,12 @@ dnssec-policy "keystores-kz" {
};
};
zone "default.example" {
type primary;
file "default.example.db";
dnssec-policy "default";
};
zone "alternative.kz.example" {
type primary;
file "alternative.kz.example.db";

View file

@ -19,6 +19,19 @@ set -e
mkdir ksk
mkdir zsk
zone="default.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.1
zone="bad-default-kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.1
$KEYGEN -a 13 $zone 2>keygen.out.$zone.2
zone="bad-default-algorithm.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 8 -fK $zone 2>keygen.out.$zone.1
zone="alternative.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a RSASHA256 -b 2048 $zone 2>keygen.out.$zone.1

View file

@ -121,3 +121,42 @@ def test_dnssecpolicy_keystore():
f"zone '{zone}': no key file found matching dnssec-policy default-kz key:'zsk algorithm:ECDSAP256SHA256 length:256 tag-range:0-65535'"
in err
)
# Mismatch algorithm (default policy)
zone = "bad-default-algorithm.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-default-algorithm.conf"], raise_on_exception=False
)
err = out.stdout.decode("utf-8")
keys = isctest.kasp.keydir_to_keylist(zone)
assert len(keys) == 1
assert (
f"zone '{zone}': key file '{zone}/RSASHA256/{keys[0].tag}' does not match dnssec-policy default"
in err
)
assert (
f"zone '{zone}': no key file found matching dnssec-policy default key:'csk algorithm:ECDSAP256SHA256 length:256 tag-range:0-65535'"
in err
)
# Mismatch role (default policy)
zone = "bad-default-kz.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-default-kz.conf"], raise_on_exception=False
)
err = out.stdout.decode("utf-8")
keys = isctest.kasp.keydir_to_keylist(zone)
assert len(keys) == 2
assert (
f"zone '{zone}': key file '{zone}/ECDSAP256SHA256/{keys[0].tag}' does not match dnssec-policy default"
in err
)
assert (
f"zone '{zone}': key file '{zone}/ECDSAP256SHA256/{keys[1].tag}' does not match dnssec-policy default"
in err
)
assert (
f"zone '{zone}': no key file found matching dnssec-policy default key:'csk algorithm:ECDSAP256SHA256 length:256 tag-range:0-65535'"
in err
)
assert f"zone '{zone}': wrong number of key files (2, expected 1)" in err

View file

@ -2826,10 +2826,11 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
bool check_keys) {
const char *dir = keydir;
isc_result_t ret, result = ISC_R_SUCCESS;
bool done = false;
bool keystore = false;
const cfg_obj_t *kasps = NULL;
const cfg_obj_t *kaspobj = NULL;
const cfg_obj_t *obj = NULL;
dns_kasp_t *default_kasp = NULL;
dns_kasp_t *kasp = NULL;
dns_kasplist_t kasplist;
const cfg_obj_t *keystores = NULL;
@ -2855,37 +2856,86 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
(void)cfg_keystore_fromconfig(NULL, mctx, &kslist, NULL);
/*
* Look for the dnssec-policy by name, which is the dnssec-policy
* for the zone in question.
* dnssec-policy "default".
*/
ret = cfg_kasp_builtinconfig(mctx, "default", &kslist, &kasplist,
&default_kasp);
if (ret != ISC_R_SUCCESS) {
cfg_obj_log(config, ISC_LOG_ERROR,
"failed to load the 'default' dnssec-policy: %s",
isc_result_totext(ret));
result = ret;
goto check;
}
dns_kasp_freeze(default_kasp);
/*
* dnssec-policy "insecure".
*/
ret = cfg_kasp_builtinconfig(mctx, "insecure", &kslist, &kasplist,
&kasp);
if (ret != ISC_R_SUCCESS) {
cfg_obj_log(config, ISC_LOG_ERROR,
"failed to load the 'insecure' dnssec-policy: %s",
isc_result_totext(ret));
result = ret;
goto check;
}
dns_kasp_freeze(kasp);
dns_kasp_detach(&kasp);
/*
* Configured dnssec-policy clauses.
*/
CFG_LIST_FOREACH(kasps, element) {
cfg_obj_t *kconfig = cfg_listelt_value(element);
kaspobj = NULL;
obj = NULL;
if (!cfg_obj_istuple(kconfig)) {
continue;
}
kaspobj = cfg_tuple_get(kconfig, "name");
if (strcmp(name, cfg_obj_asstring(kaspobj)) != 0) {
continue;
}
ret = cfg_kasp_fromconfig(kconfig, NULL, 0, mctx, &kslist,
&kasplist, &kasp);
obj = cfg_tuple_get(kconfig, "name");
ret = cfg_kasp_fromconfig(kconfig, default_kasp, 0, mctx,
&kslist, &kasplist, &kasp);
if (ret != ISC_R_SUCCESS) {
kasp = NULL;
result = ret;
goto check;
}
break;
if (strcmp(name, cfg_obj_asstring(obj)) == 0) {
kaspobj = obj;
dns_kasp_freeze(kasp);
}
dns_kasp_detach(&kasp);
}
if (kasp == NULL) {
/*
* Look for the dnssec-policy by name, which is the dnssec-policy
* for the zone in question.
*/
ret = dns_kasplist_find(&kasplist, name, &kasp);
if (ret != ISC_R_SUCCESS) {
cfg_obj_log(config, ISC_LOG_ERROR,
"no dnssec-policy found for zone '%s'", zname);
result = ISC_R_NOTFOUND;
goto check;
}
INSIST(kaspobj != NULL);
INSIST(kasp != NULL);
if (kaspobj == NULL) {
kaspobj = kasps == NULL ? config : kasps;
}
if (strcmp(name, "insecure") == 0 || strcmp(name, "default") == 0) {
ret = keydirexist(zconfig, "key-directory", origin, dir, name,
keydirs, mctx);
if (ret != ISC_R_SUCCESS) {
result = ret;
}
}
/* Check key-stores of keys */
dns_kasp_freeze(kasp);
ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
dns_keystore_t *kks = dns_kasp_key_keystore(kkey);
dir = dns_keystore_directory(kks, keydir);
@ -2983,18 +3033,10 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
}
}
dns_kasp_thaw(kasp);
done = true;
check:
if (!done) {
ret = keydirexist(zconfig, "key-directory", origin, dir, name,
keydirs, mctx);
if (ret != ISC_R_SUCCESS) {
result = ret;
}
if (default_kasp != NULL) {
dns_kasp_detach(&default_kasp);
}
if (kasp != NULL) {
dns_kasp_detach(&kasp);
}

View file

@ -67,6 +67,30 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
*\li Other errors are possible.
*/
isc_result_t
cfg_kasp_builtinconfig(isc_mem_t *mctx, const char *name,
dns_keystorelist_t *keystorelist,
dns_kasplist_t *kasplist, dns_kasp_t **kaspp);
/*%<
* Create built-in KASP.
*
* If a 'kasplist' is provided, a lookup happens and if a KASP already exists
* with the same name, no new KASP is created, and no attach to 'kaspp' happens.
*
* Requires:
*
*\li 'mctx' is a valid memory context.
*
*\li kaspp != NULL && *kaspp == NULL
*
* Returns:
*
*\li #ISC_R_SUCCESS If creating and configuring the KASP succeeds.
*\li #ISC_R_EXISTS If 'kasplist' already has the default policy.
*
*\li Other errors are possible.
*/
isc_result_t
cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx,
dns_keystorelist_t *keystorelist,

View file

@ -570,10 +570,8 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
/* Now configure. */
INSIST(DNS_KASP_VALID(kasp));
if (config != NULL) {
koptions = cfg_tuple_get(config, "options");
maps[i++] = koptions;
}
koptions = cfg_tuple_get(config, "options");
maps[i++] = koptions;
maps[i] = NULL;
/* Configuration: Signatures */
@ -877,6 +875,100 @@ cleanup:
return result;
}
isc_result_t
cfg_kasp_builtinconfig(isc_mem_t *mctx, const char *name,
dns_keystorelist_t *keystorelist,
dns_kasplist_t *kasplist, dns_kasp_t **kaspp) {
isc_result_t result;
dns_kasp_t *kasp = NULL;
REQUIRE(kaspp != NULL && *kaspp == NULL);
REQUIRE(strcmp(name, "default") == 0 || strcmp(name, "insecure") == 0);
result = dns_kasplist_find(kasplist, name, &kasp);
if (result == ISC_R_SUCCESS) {
dns_kasp_detach(&kasp);
return ISC_R_EXISTS;
}
if (result != ISC_R_NOTFOUND) {
return result;
}
result = ISC_R_SUCCESS;
/* No kasp with configured name was found in list, create new one. */
INSIST(kasp == NULL);
dns_kasp_create(mctx, name, &kasp);
INSIST(kasp != NULL);
/* Now configure. */
INSIST(DNS_KASP_VALID(kasp));
/* Configuration: Signatures */
dns_kasp_setsigjitter(kasp, parse_duration(DNS_KASP_SIG_JITTER));
dns_kasp_setsigrefresh(kasp, parse_duration(DNS_KASP_SIG_REFRESH));
dns_kasp_setsigvalidity_dnskey(
kasp, parse_duration(DNS_KASP_SIG_VALIDITY_DNSKEY));
dns_kasp_setsigvalidity(kasp, parse_duration(DNS_KASP_SIG_VALIDITY));
/* Configuration: Zone settings */
dns_kasp_setinlinesigning(kasp, true);
dns_kasp_setmanualmode(kasp, false);
dns_kasp_setzonemaxttl(kasp, parse_duration(DNS_KASP_ZONE_MAXTTL));
dns_kasp_setzonepropagationdelay(
kasp, parse_duration(DNS_KASP_ZONE_PROPDELAY));
/* Configuration: Parent settings */
dns_kasp_setdsttl(kasp, parse_duration(DNS_KASP_DS_TTL));
dns_kasp_setparentpropagationdelay(
kasp, parse_duration(DNS_KASP_PARENT_PROPDELAY));
/* Configuration: Keys */
dns_kasp_setofflineksk(kasp, false);
dns_kasp_setcdnskey(kasp, true);
dns_kasp_adddigest(kasp, DNS_DSDIGEST_SHA256);
dns_kasp_setdnskeyttl(kasp, parse_duration(DNS_KASP_KEY_TTL));
dns_kasp_setpublishsafety(kasp,
parse_duration(DNS_KASP_PUBLISH_SAFETY));
dns_kasp_setretiresafety(kasp, parse_duration(DNS_KASP_RETIRE_SAFETY));
dns_kasp_setpurgekeys(kasp, parse_duration(DNS_KASP_PURGE_KEYS));
if (strcmp(name, "default") == 0) {
dns_kasp_key_t *new_key = NULL;
dns_kasp_key_create(kasp, &new_key);
new_key->role |= DNS_KASP_KEY_ROLE_KSK;
new_key->role |= DNS_KASP_KEY_ROLE_ZSK;
new_key->lifetime = 0;
new_key->algorithm = DST_ALG_ECDSA256;
new_key->length = 256;
result = dns_keystorelist_find(keystorelist,
DNS_KEYSTORE_KEYDIRECTORY,
&new_key->keystore);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
dns_kasp_addkey(kasp, new_key);
}
/* Configuration: Denial of existence */
dns_kasp_setnsec3(kasp, false);
/* Append it to the list for future lookups. */
ISC_LIST_APPEND(*kasplist, kasp, link);
INSIST(!(ISC_LIST_EMPTY(*kasplist)));
/* Success: Attach the kasp to the pointer and return. */
dns_kasp_attach(kasp, kaspp);
/* Don't detach as kasp is on '*kasplist' */
return ISC_R_SUCCESS;
cleanup:
/* Something bad happened, detach (destroys kasp) and return error. */
dns_kasp_detach(&kasp);
return result;
}
isc_result_t
cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx,
dns_keystorelist_t *keystorelist,