new: usr: Add dnssec-policy keys configuration check to named-checkconf

A new option `-k` is added to `named-checkconf` that allows checking the `dnssec-policy` `keys` configuration against the configured key stores. If the found key files are not in sync with the given `dnssec-policy`, the check will fail.

This is useful to run before migrating to `dnssec-policy`.

Closes #5486

Merge branch '5486-named-checkconf-dnssec-policy-key-directory' into 'main'

See merge request isc-projects/bind9!10907
This commit is contained in:
Matthijs Mekking 2025-09-24 15:44:08 +00:00
commit 23a79b42ea
26 changed files with 1173 additions and 196 deletions

View file

@ -57,7 +57,7 @@ usage(void);
static void
usage(void) {
fprintf(stderr,
"usage: %s [-achijlvz] [-p [-x]] [-t directory] "
"usage: %s [-achijklvz] [-p [-x]] [-t directory] "
"[named.conf]\n",
isc_commandline_progname);
exit(EXIT_SUCCESS);
@ -593,7 +593,7 @@ main(int argc, char **argv) {
/*
* Process memory debugging argument first.
*/
#define CMDLINE_FLAGS "acdhijlm:nt:pvxz"
#define CMDLINE_FLAGS "acdhijklm:nt:pvxz"
while ((c = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
switch (c) {
case 'm':
@ -638,6 +638,10 @@ main(int argc, char **argv) {
nomerge = false;
break;
case 'k':
checkflags |= BIND_CHECK_KEYS;
break;
case 'l':
list_zones = true;
break;

View file

@ -21,7 +21,7 @@ named-checkconf - named configuration file syntax checking tool
Synopsis
~~~~~~~~
:program:`named-checkconf` [**-achjlnvz**] [**-p** [**-x** ]] [**-t** directory] {filename}
:program:`named-checkconf` [**-achjklnvz**] [**-p** [**-x** ]] [**-t** directory] {filename}
Description
~~~~~~~~~~~
@ -56,6 +56,12 @@ Options
When loading a zonefile, this option instructs :iscman:`named` to read the journal if it exists.
.. option:: -k
Check the `dnssec-policy`'s DNSSEC keys against the key files in
the `key-directory`. This is useful when checking a `named.conf`
to ensure a DNSSEC policy matches the existing keys.
.. option:: -l
This option lists all the configured zones. Each line of output contains the zone

View file

@ -601,6 +601,9 @@ kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name,
const cfg_obj_t *keystores = NULL;
dns_keystore_t *keystore = NULL;
dns_keystorelist_t kslist;
unsigned int options = (ISCCFG_KASPCONF_CHECK_ALGORITHMS |
ISCCFG_KASPCONF_CHECK_KEYLIST |
ISCCFG_KASPCONF_LOG_ERRORS);
ISC_LIST_INIT(kasplist);
ISC_LIST_INIT(kslist);
@ -635,8 +638,8 @@ kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name,
continue;
}
result = cfg_kasp_fromconfig(kconfig, NULL, true, mctx, &kslist,
&kasplist, &kasp);
result = cfg_kasp_fromconfig(kconfig, NULL, options, mctx,
&kslist, &kasplist, &kasp);
if (result != ISC_R_SUCCESS) {
fatal("failed to configure dnssec-policy '%s': %s",
cfg_obj_asstring(cfg_tuple_get(kconfig, "name")),

View file

@ -8052,6 +8052,9 @@ configure_kasplist(const cfg_obj_t *config, dns_kasplist_t *kasplist,
isc_result_t result = ISC_R_SUCCESS;
dns_kasp_t *default_kasp = NULL;
const cfg_obj_t *kasps = NULL;
unsigned int kaspopts = (ISCCFG_KASPCONF_CHECK_ALGORITHMS |
ISCCFG_KASPCONF_CHECK_KEYLIST |
ISCCFG_KASPCONF_LOG_ERRORS);
APPLY_CONFIGURATION_SUBROUTINE_LOG;
@ -8063,7 +8066,7 @@ configure_kasplist(const cfg_obj_t *config, dns_kasplist_t *kasplist,
cfg_obj_t *kconfig = cfg_listelt_value(element);
dns_kasp_t *kasp = NULL;
result = cfg_kasp_fromconfig(kconfig, default_kasp, true,
result = cfg_kasp_fromconfig(kconfig, default_kasp, kaspopts,
isc_g_mctx, keystorelist, kasplist,
&kasp);
if (result != ISC_R_SUCCESS) {
@ -8091,7 +8094,7 @@ configure_kasplist(const cfg_obj_t *config, dns_kasplist_t *kasplist,
cfg_obj_t *kconfig = cfg_listelt_value(element);
dns_kasp_t *kasp = NULL;
result = cfg_kasp_fromconfig(kconfig, default_kasp, true,
result = cfg_kasp_fromconfig(kconfig, default_kasp, kaspopts,
isc_g_mctx, keystorelist, kasplist,
&kasp);
if (result != ISC_R_SUCCESS) {

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
dnssec-policy "alternative-kz" {
keys {
ksk key-directory lifetime unlimited algorithm RSASHA256;
zsk key-directory lifetime unlimited algorithm RSASHA256;
};
};
zone "bad-algorithm.kz.example" {
type primary;
file "bad-algorithm.kz.example.db";
dnssec-policy "alternative-kz";
};

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

@ -0,0 +1,33 @@
/*
* 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.
*/
key-store "ksk" {
directory "ksk";
};
key-store "zsk" {
directory "zsk";
};
dnssec-policy "keystores-kz" {
keys {
ksk key-store "ksk" lifetime unlimited algorithm ECDSAP256SHA256;
zsk key-store "zsk" lifetime unlimited algorithm ECDSAP256SHA256;
};
};
zone "bad-keystores.kz.example" {
type primary;
file "bad-keystores.kz.example.db";
dnssec-policy "keystores-kz";
};

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
dnssec-policy "alternative-csk" {
keys {
csk key-directory lifetime unlimited algorithm RSASHA256 2048;
};
};
zone "bad-length.csk.example" {
type primary;
file "bad-length.csk.example.db";
dnssec-policy "alternative-csk";
};

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
dnssec-policy "default-kz" {
keys {
ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
zsk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
};
};
zone "missing-keyfile.kz.example" {
type primary;
file "missing-keyfile.kz.example.db";
dnssec-policy "default-kz";
};

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
dnssec-policy "default-kz" {
keys {
ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
zsk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
};
};
zone "bad-role.kz.example" {
type primary;
file "bad-role.kz.example.db";
dnssec-policy "default-kz";
};

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
dnssec-policy "default-kz" {
keys {
ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
zsk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
};
};
zone "superfluous-keyfile.kz.example" {
type primary;
file "superfluous-keyfile.kz.example.db";
dnssec-policy "default-kz";
};

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
dnssec-policy "tagrange-csk" {
keys {
csk key-directory lifetime unlimited algorithm ECDSAP256SHA256 tag-range 0 32767;
};
};
zone "bad-tagrange.csk.example" {
type primary;
file "bad-tagrange.csk.example.db";
dnssec-policy "tagrange-csk";
};

View file

@ -0,0 +1,90 @@
/*
* 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.
*/
key-store "ksk" {
directory "ksk";
};
key-store "zsk" {
directory "zsk";
};
dnssec-policy "alternative-kz" {
keys {
ksk key-directory lifetime unlimited algorithm RSASHA256 2048;
zsk key-directory lifetime unlimited algorithm RSASHA256 2048;
};
};
dnssec-policy "alternative-csk" {
keys {
csk key-directory lifetime unlimited algorithm RSASHA256 2048;
};
};
dnssec-policy "default-kz" {
keys {
ksk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
zsk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
};
};
dnssec-policy "default-csk" {
keys {
csk key-directory lifetime unlimited algorithm ECDSAP256SHA256;
};
};
dnssec-policy "keystores-kz" {
keys {
ksk key-store "ksk" lifetime unlimited algorithm ECDSAP256SHA256;
zsk key-store "zsk" lifetime unlimited algorithm ECDSAP256SHA256;
};
};
zone "default.example" {
type primary;
file "default.example.db";
dnssec-policy "default";
};
zone "alternative.kz.example" {
type primary;
file "alternative.kz.example.db";
dnssec-policy "alternative-kz";
};
zone "alternative.csk.example" {
type primary;
file "alternative.csk.example.db";
dnssec-policy "alternative-csk";
};
zone "default.kz.example" {
type primary;
file "default.kz.example.db";
dnssec-policy "default-kz";
};
zone "default.csk.example" {
type primary;
file "default.csk.example.db";
dnssec-policy "default-csk";
};
zone "keystores.kz.example" {
type primary;
file "keystores.kz.example.db";
dnssec-policy "keystores-kz";
};

View file

@ -0,0 +1,85 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../conf.sh
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
$KEYGEN -a RSASHA256 -b 2048 -fK $zone 2>keygen.out.$zone.2
zone="alternative.csk.example"
cp template.db.in "${zone}.db"
$KEYGEN -a RSASHA256 -b 2048 -fK $zone 2>keygen.out.$zone.2
zone="default.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 $zone 2>keygen.out.$zone.1
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.2
zone="default.csk.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.2
zone="keystores.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 -fK -K ksk $zone 2>keygen.out.$zone.2
$KEYGEN -a 13 -K zsk $zone 2>keygen.out.$zone.2
zone="superfluous-keyfile.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 $zone 2>keygen.out.$zone.1
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.2
$KEYGEN -a 13 $zone 2>keygen.out.$zone.3 # superfluous
zone="missing-keyfile.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 $zone 2>keygen.out.$zone.1
# no ksk
zone="bad-algorithm.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 $zone 2>keygen.out.$zone.1
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.2
zone="bad-length.csk.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 8 -b 4096 -fK $zone 2>keygen.out.$zone.2
zone="bad-tagrange.csk.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 -M 32768:65535 -fK $zone 2>keygen.out.$zone.2
zone="bad-role.kz.example"
cp template.db.in "${zone}.db"
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.1
$KEYGEN -a 13 -fK $zone 2>keygen.out.$zone.2

View file

@ -0,0 +1,27 @@
; 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.
$TTL 300
@ IN SOA mname1. . (
1 ; serial
20 ; refresh (20 seconds)
20 ; retry (20 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
NS ns3
ns3 A 10.53.0.3
a A 10.0.0.1
b A 10.0.0.2
c A 10.0.0.3

View file

@ -0,0 +1,162 @@
# 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.
import os
import pytest
import isctest
pytestmark = pytest.mark.extra_artifacts(
[
"bad-*.conf",
"K*.key",
"K*.private",
"K*.state",
"keygen.out.*",
"named.conf",
"*.db",
"ksk/",
"zsk/",
]
)
CHECKCONF = os.environ["CHECKCONF"]
def test_dnssecpolicy_keystore():
# Good configuration.
isctest.run.cmd([CHECKCONF, "-k", "named.conf"])
# Superfluous key file.
zone = "superfluous-keyfile.kz.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-superfluous-keyfile.conf"], raise_on_exception=False
)
err = out.stdout.decode("utf-8")
assert f"zone '{zone}': wrong number of key files (3, expected 2)" in err
# Missing key file.
zone = "missing-keyfile.kz.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-missing-keyfile.conf"], raise_on_exception=False
)
err = out.stdout.decode("utf-8")
assert f"zone '{zone}': wrong number of key files (1, expected 2)" in err
# Mismatch algorithm.
zone = "bad-algorithm.kz.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-algorithm.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 alternative-kz"
in err
)
assert (
f"zone '{zone}': key file '{zone}/ECDSAP256SHA256/{keys[1].tag}' does not match dnssec-policy alternative-kz"
in err
)
assert (
f"zone '{zone}': no key file found matching dnssec-policy alternative-kz key:'ksk algorithm:RSASHA256 length:2048 tag-range:0-65535'"
in err
)
assert (
f"zone '{zone}': no key file found matching dnssec-policy alternative-kz key:'zsk algorithm:RSASHA256 length:2048 tag-range:0-65535'"
in err
)
# Mismatch length
zone = "bad-length.csk.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-length.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 alternative-csk"
in err
)
assert (
f"zone '{zone}': no key file found matching dnssec-policy alternative-csk key:'csk algorithm:RSASHA256 length:2048 tag-range:0-65535'"
in err
)
# Mismatch tag range
zone = "bad-tagrange.csk.example"
out = isctest.run.cmd(
[CHECKCONF, "-k", "bad-tagrange.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}/ECDSAP256SHA256/{keys[0].tag}' does not match dnssec-policy tagrange-csk"
in err
)
assert (
f"zone '{zone}': no key file found matching dnssec-policy tagrange-csk key:'csk algorithm:ECDSAP256SHA256 length:256 tag-range:0-32767'"
in err
)
# Mismatch role
zone = "bad-role.kz.example"
out = isctest.run.cmd([CHECKCONF, "-k", "bad-role.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}': 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

@ -31,6 +31,7 @@
#include <dns/dnssec.h>
#include <dns/keystore.h>
#include <dns/name.h>
#include <dns/types.h>
/* For storing a list of digest types */
@ -137,6 +138,8 @@ struct dns_kasp {
#define DNS_KASP_KEY_ROLE_KSK 0x01
#define DNS_KASP_KEY_ROLE_ZSK 0x02
#define DNS_KASP_KEY_FORMATSIZE (DNS_NAME_FORMATSIZE + 64)
void
dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp);
/*%<
@ -764,6 +767,17 @@ dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey);
*\li False, otherwise.
*/
void
dns_kasp_key_format(dns_kasp_key_t *key, char *cp, unsigned int size);
/*%<
* Write the identifying information about the policy key (role,
* algorithm, tag range) into a string 'cp' of size 'size'.
* Requires:
*
*\li key != NULL
*\li cp != NULL
*/
bool
dns_kasp_nsec3(dns_kasp_t *kasp);
/*%<

View file

@ -37,6 +37,21 @@ dns_keymgr_settime_syncpublish(dst_key_t *key, dns_kasp_t *kasp, bool first);
*\li 'kasp' is a valid DNSSEC policy.
*/
void
dns_keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now,
bool csk);
/*
* Initialize this key's properties if not already present. 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. If 'csk' is
* set to true, the key is considered a combined signing key (CSK).
*
* Requires:
*\li 'key' is a valid DNSSEC key.
*\li 'kasp' is a valid DNSSEC policy.
*/
isc_result_t
dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
isc_mem_t *mctx, dns_dnsseckeylist_t *keyring,

View file

@ -1221,7 +1221,6 @@ dst_algorithm_totext(dst_algorithm_t alg, isc_buffer_t *target);
*\li ISC_R_NOSPACE target buffer is too small
*/
#define DST_ALGORITHM_FORMATSIZE 20
void
dst_algorithm_format(dst_algorithm_t dst_alg, char *data, unsigned int length);
/*%<

View file

@ -559,6 +559,21 @@ dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey) {
return true;
}
void
dns_kasp_key_format(dns_kasp_key_t *key, char *cp, unsigned int size) {
REQUIRE(key != NULL);
REQUIRE(cp != NULL);
char algstr[DNS_NAME_FORMATSIZE];
bool csk = dns_kasp_key_ksk(key) && dns_kasp_key_zsk(key);
const char *rolestr = (csk ? "csk"
: (dns_kasp_key_ksk(key) ? "ksk" : "zsk"));
dst_algorithm_format(key->algorithm, algstr, sizeof(algstr));
snprintf(cp, size, "%s algorithm:%s length:%u tag-range:%u-%u", rolestr,
algstr, dns_kasp_key_size(key), key->tag_min, key->tag_max);
}
uint8_t
dns_kasp_nsec3iter(dns_kasp_t *kasp) {
REQUIRE(kasp != NULL);

View file

@ -1635,16 +1635,9 @@ transition:
return result;
}
/*
* 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) {
void
dns_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;
@ -1926,7 +1919,7 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key,
dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp));
dst_key_settime(dst_key, DST_TIME_CREATED, now);
dns_dnsseckey_create(mctx, &dst_key, &new_key);
keymgr_key_init(new_key, kasp, now, csk);
dns_keymgr_key_init(new_key, kasp, now, csk);
keycreated = true;
}
dst_key_setnum(new_key->key, DST_NUM_LIFETIME, lifetime);
@ -2174,7 +2167,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass,
ISC_LIST_FOREACH(*keyring, dkey, link) {
bool found_match = false;
keymgr_key_init(dkey, kasp, now, numkeys == 1);
dns_keymgr_key_init(dkey, kasp, now, numkeys == 1);
ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
if (dns_kasp_key_match(kkey, dkey)) {
@ -2785,7 +2778,7 @@ dns_keymgr_offline(const dns_name_t *origin, dns_dnsseckeylist_t *keyring,
continue;
}
keymgr_key_init(dkey, kasp, now, false);
dns_keymgr_key_init(dkey, kasp, now, false);
/* Get current metadata */
RETERR(dst_key_getstate(dkey->key, DST_KEY_DNSKEY,

View file

@ -47,6 +47,7 @@
#include <dns/fixedname.h>
#include <dns/journal.h>
#include <dns/kasp.h>
#include <dns/keymgr.h>
#include <dns/keystore.h>
#include <dns/keyvalues.h>
#include <dns/peer.h>
@ -1397,6 +1398,11 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config,
if (obj != NULL) {
bool bad_kasp = false;
bool bad_name = false;
unsigned int kaspopts = (ISCCFG_KASPCONF_CHECK_KEYLIST |
ISCCFG_KASPCONF_LOG_ERRORS);
if (check_algorithms) {
kaspopts |= ISCCFG_KASPCONF_CHECK_ALGORITHMS;
}
if (optlevel != optlevel_config && !cfg_obj_isstring(obj)) {
bad_kasp = true;
@ -1422,8 +1428,8 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config,
}
ret = cfg_kasp_fromconfig(
kconfig, NULL, check_algorithms,
mctx, &kslist, &list, &kasp);
kconfig, NULL, kaspopts, mctx,
&kslist, &list, &kasp);
if (ret != ISC_R_SUCCESS) {
if (result == ISC_R_SUCCESS) {
result = ret;
@ -2815,29 +2821,30 @@ cleanup:
static isc_result_t
check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
dns_name_t *zname, const char *name, const char *keydir,
isc_symtab_t *keydirs, isc_mem_t *mctx) {
dns_name_t *origin, const char *zname, const char *name,
const char *keydir, isc_symtab_t *keydirs, isc_mem_t *mctx,
bool check_keys) {
const char *dir = keydir;
isc_result_t ret, result = ISC_R_SUCCESS;
bool do_cleanup = false;
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;
dns_keystorelist_t kslist;
isc_time_t timenow;
isc_stdtime_t now;
timenow = isc_time_now();
now = isc_time_seconds(&timenow);
/* If no dnssec-policy or key-store, use the dir (key-directory) */
(void)cfg_map_get(config, "dnssec-policy", &kasps);
(void)cfg_map_get(config, "key-store", &keystores);
if (kasps == NULL || keystores == NULL) {
goto check;
}
ISC_LIST_INIT(kasplist);
ISC_LIST_INIT(kslist);
do_cleanup = true;
/*
* Build the keystore list.
@ -2849,36 +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);
const cfg_obj_t *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, false, 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(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);
@ -2888,35 +2945,108 @@ check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig,
ret = keydirexist(zconfig,
keystore ? "key-store directory"
: "key-directory",
zname, dir, name, keydirs, mctx);
origin, dir, name, keydirs, mctx);
if (ret != ISC_R_SUCCESS) {
result = ret;
}
}
dns_kasp_thaw(kasp);
done = true;
if (check_keys) {
/* Find matching key files. */
dns_dnsseckeylist_t keys;
int numkaspkeys = 0;
int numkeyfiles = 0;
ISC_LIST_INIT(keys);
ret = dns_dnssec_findmatchingkeys(origin, kasp, keydir, &kslist,
now, mctx, &keys);
if (ret != ISC_R_SUCCESS) {
result = ret;
}
ISC_LIST_FOREACH(keys, dkey, link) {
numkeyfiles++;
}
ISC_LIST_FOREACH(keys, dkey, link) {
bool found_match = false;
dns_keymgr_key_init(dkey, kasp, now, numkeyfiles == 1);
ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
if (dns_kasp_key_match(kkey, dkey)) {
found_match = true;
break;
}
}
if (!found_match) {
char keystr[DST_KEY_FORMATSIZE];
dst_key_format(dkey->key, keystr,
sizeof(keystr));
cfg_obj_log(kaspobj, ISC_LOG_ERROR,
"zone '%s': key file '%s' does not "
"match dnssec-policy %s",
zname, keystr,
dns_kasp_getname(kasp));
result = ISC_R_NOTFOUND;
}
}
ISC_LIST_FOREACH(dns_kasp_keys(kasp), kkey, link) {
bool found_match = false;
numkaspkeys++;
ISC_LIST_FOREACH(keys, dkey, link) {
if (dns_kasp_key_match(kkey, dkey)) {
found_match = true;
break;
}
}
if (!found_match) {
char keystr[DNS_KASP_KEY_FORMATSIZE];
dns_kasp_key_format(kkey, keystr,
sizeof(keystr));
cfg_obj_log(
kaspobj, ISC_LOG_ERROR,
"zone '%s': no key file found matching "
"dnssec-policy %s key:'%s'",
zname, dns_kasp_getname(kasp), keystr);
result = ISC_R_NOTFOUND;
}
}
if (numkaspkeys != numkeyfiles) {
cfg_obj_log(kaspobj, ISC_LOG_ERROR,
"zone '%s': wrong number of key files (%d, "
"expected %d)",
zname, numkeyfiles, numkaspkeys);
result = ISC_R_FAILURE;
}
ISC_LIST_FOREACH(keys, key, link) {
ISC_LIST_UNLINK(keys, key, link);
dns_dnsseckey_destroy(mctx, &key);
}
}
check:
if (!done) {
ret = keydirexist(zconfig, "key-directory", zname, dir, name,
keydirs, mctx);
if (ret != ISC_R_SUCCESS) {
result = ret;
}
if (default_kasp != NULL) {
dns_kasp_detach(&default_kasp);
}
if (do_cleanup) {
if (kasp != NULL) {
dns_kasp_detach(&kasp);
}
ISC_LIST_FOREACH(kasplist, k, link) {
ISC_LIST_UNLINK(kasplist, k, link);
dns_kasp_detach(&k);
}
ISC_LIST_FOREACH(kslist, ks, link) {
ISC_LIST_UNLINK(kslist, ks, link);
dns_keystore_detach(&ks);
}
if (kasp != NULL) {
dns_kasp_detach(&kasp);
}
ISC_LIST_FOREACH(kasplist, k, link) {
ISC_LIST_UNLINK(kasplist, k, link);
dns_kasp_detach(&k);
}
ISC_LIST_FOREACH(kslist, ks, link) {
ISC_LIST_UNLINK(kslist, ks, link);
dns_keystore_detach(&ks);
}
return result;
@ -3046,6 +3176,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
bool has_dnssecpolicy = false;
bool kasp_inlinesigning = false;
bool inline_signing = false;
bool check_keys = (flags & BIND_CHECK_KEYS) != 0;
const void *clauses = NULL;
const char *option = NULL;
const char *kaspname = NULL;
@ -3821,8 +3952,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
*/
if (zname != NULL && keydirs != NULL) {
if (has_dnssecpolicy) {
tresult = check_keydir(config, zconfig, zname, kaspname,
dir, keydirs, mctx);
tresult = check_keydir(config, zconfig, zname, znamestr,
kaspname, dir, keydirs, mctx,
check_keys);
} else {
tresult = keydirexist(zconfig, "key-directory", zname,
dir, kaspname, keydirs, mctx);

View file

@ -41,6 +41,11 @@
* Check the dnssec-policy DNSSEC algorithms against those
* supported by the crypto provider.
*/
#define BIND_CHECK_KEYS 0x00000004
/*%<
* Check the dnssec-policy DNSSEC keys against the key files
* in the key stores.
*/
isc_result_t
isccfg_check_namedconf(const cfg_obj_t *config, unsigned int flags,

View file

@ -15,13 +15,17 @@
#include <isccfg/cfg.h>
#define ISCCFG_KASPCONF_CHECK_ALGORITHMS 0x01
#define ISCCFG_KASPCONF_CHECK_KEYLIST 0x02
#define ISCCFG_KASPCONF_LOG_ERRORS 0x04
/***
*** Functions
***/
isc_result_t
cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
bool check_algorithms, isc_mem_t *mctx,
unsigned int options, isc_mem_t *mctx,
dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist,
dns_kasp_t **kaspp);
/*%<
@ -34,8 +38,16 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
*
* The 'keystorelist' is where to lookup key stores if KASP keys are using them.
*
* If 'check_algorithms' is true then the dnssec-policy DNSSEC key
* algorithms are checked against those supported by the crypto provider.
* If 'options' has ISCCFG_KASPCONF_CHECK_ALGORITHMS set, then the dnssec-policy
* DNSSEC key algorithms are checked against those supported by the crypto
* provider.
*
* If 'options' has ISCCFG_KASPCONF_CHECK_KEYLIST set, then this function
* insists that the key list is not empty, unless the policy is "insecure"
* (then the key list must be empty).
*
* If 'options' has ISCCFG_KASPCONF_LOG_ERRORS set, then configuration errors
* and warnings are logged to the global logging context.
*
* Requires:
*
@ -55,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

@ -114,7 +114,7 @@ get_string(const cfg_obj_t **maps, const char *option) {
*/
static isc_result_t
cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
bool check_algorithms, bool offline_ksk,
bool check_algorithms, bool log_errors, bool offline_ksk,
dns_keystorelist_t *keystorelist,
uint32_t ksk_min_lifetime, uint32_t zsk_min_lifetime) {
isc_result_t result;
@ -151,10 +151,13 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
key->role |= DNS_KASP_KEY_ROLE_ZSK;
} else if (strcmp(rolestr, "csk") == 0) {
if (offline_ksk) {
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: csk keys are not "
"allowed when offline-ksk is enabled");
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: csk keys "
"are not "
"allowed when offline-ksk "
"is enabled");
}
result = ISC_R_FAILURE;
goto cleanup;
}
@ -172,14 +175,20 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
result = dns_keystorelist_find(keystorelist, keydir,
&key->keystore);
if (result == ISC_R_NOTFOUND) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: keystore %s does not exist",
keydir);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: keystore %s does "
"not exist",
keydir);
}
result = ISC_R_FAILURE;
goto cleanup;
} else if (result != ISC_R_SUCCESS) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: bad keystore %s", keydir);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: bad keystore %s",
keydir);
}
result = ISC_R_FAILURE;
goto cleanup;
}
@ -192,9 +201,12 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
}
if (key->lifetime > 0) {
if (key->lifetime < 30 * (24 * 3600)) {
cfg_obj_log(obj, ISC_LOG_WARNING,
"dnssec-policy: key lifetime is "
"shorter than 30 days");
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_WARNING,
"dnssec-policy: key "
"lifetime is "
"shorter than 30 days");
}
}
if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 &&
key->lifetime <= ksk_min_lifetime)
@ -207,10 +219,14 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
error = true;
}
if (error) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: key lifetime is "
"shorter than the time it takes to "
"do a rollover");
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: key "
"lifetime is "
"shorter than the time it "
"takes to "
"do a rollover");
}
result = ISC_R_FAILURE;
goto cleanup;
}
@ -222,9 +238,11 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
result = dst_algorithm_fromtext(&key->algorithm,
(isc_textregion_t *)&alg);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: bad algorithm %s",
alg.base);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: bad algorithm %s",
alg.base);
}
result = DNS_R_BADALG;
goto cleanup;
}
@ -232,10 +250,13 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
(key->algorithm == DST_ALG_RSASHA1 ||
key->algorithm == DST_ALG_NSEC3RSASHA1))
{
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: algorithm %s not supported "
"in FIPS mode",
alg.base);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: algorithm %s not "
"supported "
"in FIPS mode",
alg.base);
}
result = DNS_R_BADALG;
goto cleanup;
}
@ -243,9 +264,12 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
if (check_algorithms &&
!dst_algorithm_supported(key->algorithm))
{
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: algorithm %s not supported",
alg.base);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: algorithm %s not "
"supported",
alg.base);
}
result = DNS_R_BADALG;
goto cleanup;
}
@ -253,10 +277,13 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
switch (key->algorithm) {
case DST_ALG_RSASHA1:
case DST_ALG_NSEC3RSASHA1:
cfg_obj_log(obj, ISC_LOG_WARNING,
"dnssec-policy: DNSSEC algorithm %s is "
"deprecated",
alg.base);
if (log_errors) {
cfg_obj_log(
obj, ISC_LOG_WARNING,
"dnssec-policy: DNSSEC algorithm %s is "
"deprecated",
alg.base);
}
break;
default:
break;
@ -280,11 +307,15 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
min = DST_ALG_RSASHA512 ? 1024 : 512;
}
if (size < min || size > 4096) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: key with "
"algorithm %s has invalid "
"key length %u",
alg.base, size);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: "
"key with "
"algorithm %s has "
"invalid "
"key length %u",
alg.base, size);
}
result = ISC_R_RANGE;
goto cleanup;
}
@ -293,11 +324,15 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
case DST_ALG_ECDSA384:
case DST_ALG_ED25519:
case DST_ALG_ED448:
cfg_obj_log(obj, ISC_LOG_WARNING,
"dnssec-policy: key algorithm %s "
"has predefined length; ignoring "
"length value %u",
alg.base, size);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_WARNING,
"dnssec-policy: key "
"algorithm %s "
"has predefined length; "
"ignoring "
"length value %u",
alg.base, size);
}
default:
break;
}
@ -311,25 +346,31 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
obj = cfg_tuple_get(tagrange, "tag-min");
tag_min = cfg_obj_asuint32(obj);
if (tag_min > 0xffff) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: tag-min "
"too big");
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: tag-min "
"too big");
}
result = ISC_R_RANGE;
goto cleanup;
}
obj = cfg_tuple_get(tagrange, "tag-max");
tag_max = cfg_obj_asuint32(obj);
if (tag_max > 0xffff) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: tag-max "
"too big");
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: tag-max "
"too big");
}
result = ISC_R_RANGE;
goto cleanup;
}
if (tag_min >= tag_max) {
cfg_obj_log(
obj, ISC_LOG_ERROR,
"dnssec-policy: tag-min >= tag_max");
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: tag-min >= "
"tag_max");
}
result = ISC_R_RANGE;
goto cleanup;
}
@ -348,7 +389,8 @@ cleanup:
}
static isc_result_t
cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) {
cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
bool log_errors) {
unsigned int min_keysize = 4096;
const cfg_obj_t *obj = NULL;
uint32_t iter = DEFAULT_NSEC3PARAM_ITER;
@ -382,18 +424,22 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) {
if (badalg > 0) {
char algstr[DNS_SECALG_FORMATSIZE];
dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr));
cfg_obj_log(
obj, ISC_LOG_ERROR,
"dnssec-policy: cannot use nsec3 with algorithm '%s'",
algstr);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: cannot use nsec3 with "
"algorithm '%s'",
algstr);
}
return DNS_R_NSEC3BADALG;
}
if (iter != DEFAULT_NSEC3PARAM_ITER) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: nsec3 iterations value %u "
"not allowed, must be zero",
iter);
if (log_errors) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: nsec3 iterations value %u "
"not allowed, must be zero",
iter);
}
return DNS_R_NSEC3ITERRANGE;
}
@ -409,9 +455,12 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) {
saltlen = cfg_obj_asuint32(obj);
}
if (saltlen > 0xff) {
cfg_obj_log(obj, ISC_LOG_ERROR,
"dnssec-policy: nsec3 salt length %u too high",
saltlen);
if (log_errors) {
cfg_obj_log(
obj, ISC_LOG_ERROR,
"dnssec-policy: nsec3 salt length %u too high",
saltlen);
}
return DNS_R_NSEC3SALTRANGE;
}
@ -420,7 +469,7 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) {
}
static isc_result_t
add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) {
add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest, bool log_errors) {
isc_result_t result = ISC_R_SUCCESS;
isc_textregion_t r;
dns_dsdigest_t alg;
@ -430,21 +479,28 @@ add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) {
r.length = strlen(str);
result = dns_dsdigest_fromtext(&alg, &r);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(digest, ISC_LOG_ERROR,
"dnssec-policy: bad cds digest-type %s", str);
if (log_errors) {
cfg_obj_log(digest, ISC_LOG_ERROR,
"dnssec-policy: bad cds digest-type %s",
str);
}
result = DNS_R_BADALG;
} else if (!dst_ds_digest_supported(alg)) {
cfg_obj_log(digest, ISC_LOG_ERROR,
"dnssec-policy: unsupported cds "
"digest-type %s",
str);
if (log_errors) {
cfg_obj_log(digest, ISC_LOG_ERROR,
"dnssec-policy: unsupported cds "
"digest-type %s",
str);
}
result = DST_R_UNSUPPORTEDALG;
} else {
if (alg == DNS_DSDIGEST_SHA1) {
cfg_obj_log(
digest, ISC_LOG_WARNING,
"dnssec-policy: deprecated CDS digest-type %s",
str);
if (log_errors) {
cfg_obj_log(digest, ISC_LOG_WARNING,
"dnssec-policy: deprecated CDS "
"digest-type %s",
str);
}
}
dns_kasp_adddigest(kasp, alg);
}
@ -453,7 +509,7 @@ add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest) {
isc_result_t
cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
bool check_algorithms, isc_mem_t *mctx,
unsigned int options, isc_mem_t *mctx,
dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist,
dns_kasp_t **kaspp) {
isc_result_t result;
@ -474,6 +530,10 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
uint32_t ipub = 0, iret = 0;
uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0;
bool offline_ksk = false, manual_mode = false;
bool check_algorithms = (options & ISCCFG_KASPCONF_CHECK_ALGORITHMS) !=
0;
bool check_keylist = (options & ISCCFG_KASPCONF_CHECK_KEYLIST) != 0;
bool log_errors = (options & ISCCFG_KASPCONF_LOG_ERRORS) != 0;
REQUIRE(config != NULL);
REQUIRE(kaspp != NULL && *kaspp == NULL);
@ -487,10 +547,12 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
result = dns_kasplist_find(kasplist, kaspname, &kasp);
if (result == ISC_R_SUCCESS) {
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: duplicately named policy found '%s'",
kaspname);
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: duplicately named policy "
"found '%s'",
kaspname);
}
dns_kasp_detach(&kasp);
return ISC_R_EXISTS;
}
@ -508,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 */
@ -526,42 +586,51 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
sigvalidity = get_duration(maps, "signatures-validity-dnskey",
DNS_KASP_SIG_VALIDITY_DNSKEY);
if (sigrefresh >= (sigvalidity * 0.9)) {
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' signatures-refresh must be "
"at most 90%% of the signatures-validity-dnskey",
kaspname);
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' "
"signatures-refresh must be "
"at most 90%% of the "
"signatures-validity-dnskey",
kaspname);
}
result = ISC_R_FAILURE;
}
dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity);
if (sigjitter > sigvalidity) {
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' signatures-jitter cannot "
"be larger than signatures-validity-dnskey",
kaspname);
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' "
"signatures-jitter cannot "
"be larger than signatures-validity-dnskey",
kaspname);
}
result = ISC_R_FAILURE;
}
sigvalidity = get_duration(maps, "signatures-validity",
DNS_KASP_SIG_VALIDITY);
if (sigrefresh >= (sigvalidity * 0.9)) {
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' signatures-refresh must be "
"at most 90%% of the signatures-validity",
kaspname);
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' "
"signatures-refresh must be "
"at most 90%% of the signatures-validity",
kaspname);
}
result = ISC_R_FAILURE;
}
dns_kasp_setsigvalidity(kasp, sigvalidity);
if (sigjitter > sigvalidity) {
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' signatures-jitter cannot "
"be larger than signatures-validity",
kaspname);
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' "
"signatures-jitter cannot "
"be larger than signatures-validity",
kaspname);
}
result = ISC_R_FAILURE;
}
@ -619,7 +688,8 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
(void)confget(maps, "cds-digest-types", &cds);
if (cds != NULL) {
CFG_LIST_FOREACH(cds, element) {
result = add_digest(kasp, cfg_listelt_value(element));
result = add_digest(kasp, cfg_listelt_value(element),
log_errors);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
@ -658,14 +728,16 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
CFG_LIST_FOREACH(keys, element) {
cfg_obj_t *kobj = cfg_listelt_value(element);
result = cfg_kaspkey_fromconfig(
kobj, kasp, check_algorithms, offline_ksk,
keystorelist, ksk_min_lifetime,
kobj, kasp, check_algorithms, log_errors,
offline_ksk, keystorelist, ksk_min_lifetime,
zsk_min_lifetime);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(kobj, ISC_LOG_ERROR,
"dnssec-policy: failed to "
"configure keys (%s)",
isc_result_totext(result));
if (log_errors) {
cfg_obj_log(kobj, ISC_LOG_ERROR,
"dnssec-policy: failed to "
"configure keys (%s)",
isc_result_totext(result));
}
goto cleanup;
}
}
@ -698,19 +770,22 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
if (role[i] !=
(DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK))
{
cfg_obj_log(keys, ISC_LOG_ERROR,
"dnssec-policy: algorithm %zu "
"requires both KSK and ZSK roles",
i);
if (log_errors) {
cfg_obj_log(keys, ISC_LOG_ERROR,
"dnssec-policy: algorithm "
"%zu requires both KSK and "
"ZSK roles",
i);
}
result = ISC_R_FAILURE;
}
if (warn[i][0]) {
if (warn[i][0] && log_errors) {
cfg_obj_log(keys, ISC_LOG_WARNING,
"dnssec-policy: algorithm %zu has "
"multiple keys with ZSK role",
i);
}
if (warn[i][1]) {
if (warn[i][1] && log_errors) {
cfg_obj_log(keys, ISC_LOG_WARNING,
"dnssec-policy: algorithm %zu has "
"multiple keys with KSK role",
@ -744,20 +819,22 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
keystorelist, DNS_KEYSTORE_KEYDIRECTORY,
&new_key->keystore);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: failed to "
"find keystore (%s)",
isc_result_totext(result));
if (log_errors) {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: failed to "
"find keystore (%s)",
isc_result_totext(result));
}
goto cleanup;
}
dns_kasp_addkey(kasp, new_key);
}
}
if (strcmp(kaspname, "insecure") == 0) {
if (strcmp(kaspname, "insecure") == 0 && check_keylist) {
/* "dnssec-policy insecure": key list must be empty */
INSIST(dns_kasp_keylist_empty(kasp));
} else if (default_kasp != NULL) {
} else if (default_kasp != NULL && check_keylist) {
/* There must be keys configured. */
INSIST(!(dns_kasp_keylist_empty(kasp)));
}
@ -775,7 +852,7 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
}
} else {
dns_kasp_setnsec3(kasp, true);
result = cfg_nsec3param_fromconfig(nsec3, kasp);
result = cfg_nsec3param_fromconfig(nsec3, kasp, log_errors);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
@ -798,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,