new: usr: Support restricted key tag range when generating new keys

It is useful when multiple signers are being used
to sign a zone to able to specify a restricted
range of range of key tags that will be used by an
operator to sign the zone.  This adds controls to
named (dnssec-policy), dnssec-signzone, dnssec-keyfromlabel and
dnssec-ksr (dnssec-policy) to specify such ranges.

Closes #4830

Merge branch '4830-support-restricted-key-tag-range-when-generating-new-keys' into 'main'

Closes #4830

See merge request isc-projects/bind9!9258
This commit is contained in:
Mark Andrews 2024-08-22 12:55:46 +00:00
commit d40b722d46
26 changed files with 483 additions and 21 deletions

View file

@ -43,6 +43,8 @@
const char *program = "dnssec-keyfromlabel";
static uint16_t tag_min = 0, tag_max = 0xffff;
ISC_NORETURN static void
usage(void);
@ -68,6 +70,7 @@ usage(void) {
"key files\n");
fprintf(stderr, " -k: generate a TYPE=KEY key\n");
fprintf(stderr, " -L ttl: default key TTL\n");
fprintf(stderr, " -M <min>:<max>: allowed Key ID range\n");
fprintf(stderr, " -n nametype: ZONE | HOST | ENTITY | USER | "
"OTHER\n");
fprintf(stderr, " (DNSKEY generation defaults to ZONE\n");
@ -156,7 +159,7 @@ main(int argc, char **argv) {
isc_commandline_errprint = false;
#define CMDLINE_FLAGS "3A:a:Cc:D:E:Ff:GhI:i:kK:L:l:n:P:p:R:S:t:v:Vy"
#define CMDLINE_FLAGS "3A:a:Cc:D:E:Ff:GhI:i:kK:L:l:M:n:P:p:R:S:t:v:Vy"
while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
switch (ch) {
case '3':
@ -203,6 +206,20 @@ main(int argc, char **argv) {
case 'l':
label = isc_mem_strdup(mctx, isc_commandline_argument);
break;
case 'M': {
unsigned long ul;
tag_min = ul = strtoul(isc_commandline_argument, &endp,
10);
if (*endp != ':' || ul > 0xffff) {
fatal("-M range invalid");
}
tag_max = ul = strtoul(endp + 1, &endp, 10);
if (*endp != '\0' || ul > 0xffff || tag_max <= tag_min)
{
fatal("-M range invalid");
}
break;
}
case 'n':
nametype = isc_commandline_argument;
break;
@ -677,7 +694,8 @@ main(int argc, char **argv) {
* is a risk of ID collision due to this key or another key
* being revoked.
*/
if (key_collision(key, name, directory, mctx, &exact)) {
if (key_collision(key, name, directory, mctx, tag_min, tag_max, &exact))
{
isc_buffer_clear(&buf);
ret = dst_key_buildfilename(key, 0, directory, &buf);
if (ret != ISC_R_SUCCESS) {

View file

@ -21,7 +21,7 @@ dnssec-keyfromlabel - DNSSEC key generation tool
Synopsis
~~~~~~~~
:program:`dnssec-keyfromlabel` {**-l** label} [**-3**] [**-a** algorithm] [**-A** date/offset] [**-c** class] [**-D** date/offset] [**-D** sync date/offset] [**-f** flag] [**-G**] [**-I** date/offset] [**-i** interval] [**-k**] [**-K** directory] [**-L** ttl] [**-n** nametype] [**-P** date/offset] [**-P** sync date/offset] [**-p** protocol] [**-R** date/offset] [**-S** key] [**-t** type] [**-v** level] [**-V**] [**-y**] {name}
:program:`dnssec-keyfromlabel` {**-l** label} [**-3**] [**-a** algorithm] [**-A** date/offset] [**-c** class] [**-D** date/offset] [**-D** sync date/offset] [**-f** flag] [**-G**] [**-I** date/offset] [**-i** interval] [**-k**] [**-K** directory] [**-L** ttl] [**-M** tag_min:tag_max] [**-n** nametype] [**-P** date/offset] [**-P** sync date/offset] [**-p** protocol] [**-R** date/offset] [**-S** key] [**-t** type] [**-v** level] [**-V**] [**-y**] {name}
Description
~~~~~~~~~~~
@ -123,6 +123,18 @@ Options
place, in which case the existing TTL would take precedence. Setting
the default TTL to ``0`` or ``none`` removes it.
.. option:: -M tag_min:tag_max
This option sets the range of key tag values
that ``dnssec-keyfromlabel`` will accept. If the key tag of the new
key or the key tag of the revoked version of the new key is
outside this range, the new key will be rejected. This is
designed to be used when generating keys in a multi-signer
scenario, where each operator is given a range of key tags to
prevent collisions among different operators. The valid
values for ``tag_min`` and ``tag_max`` are [0..65535]. The
default allows all key tag values to be accepted.
.. option:: -p protocol
This option sets the protocol value for the key. The protocol is a number between

View file

@ -89,6 +89,8 @@ struct keygen_ctx {
char *type;
int protocol;
int size;
uint16_t tag_min;
uint16_t tag_max;
int signatory;
dns_rdataclass_t rdclass;
int options;
@ -177,6 +179,7 @@ usage(void) {
fprintf(stderr, " -f <keyflag>: ZSK | KSK | REVOKE\n");
fprintf(stderr, " -F: FIPS mode\n");
fprintf(stderr, " -L <ttl>: default key TTL\n");
fprintf(stderr, " -M <min>:<max>: allowed Key ID range\n");
fprintf(stderr, " -p <protocol>: (default: 3 [dnssec])\n");
fprintf(stderr, " -s <strength>: strength value this key signs DNS "
"records with (default: 0)\n");
@ -753,7 +756,9 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) {
* if there is a risk of ID collision due to this key
* or another key being revoked.
*/
if (key_collision(key, name, ctx->directory, mctx, NULL)) {
if (key_collision(key, name, ctx->directory, mctx, ctx->tag_min,
ctx->tag_max, NULL))
{
conflict = true;
if (null_key) {
dst_key_free(&key);
@ -862,8 +867,8 @@ main(int argc, char **argv) {
/*
* Process memory debugging argument first.
*/
#define CMDLINE_FLAGS \
"3A:a:b:Cc:D:d:E:Ff:GhI:i:K:k:L:l:m:n:P:p:qR:r:S:s:" \
#define CMDLINE_FLAGS \
"3A:a:b:Cc:D:d:E:Ff:GhI:i:K:k:L:l:M:m:n:P:p:qR:r:S:s:" \
"T:t:v:V"
while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
switch (ch) {
@ -952,6 +957,21 @@ main(int argc, char **argv) {
case 'n':
ctx.nametype = isc_commandline_argument;
break;
case 'M': {
unsigned long ul;
ctx.tag_min = ul = strtoul(isc_commandline_argument,
&endp, 10);
if (*endp != ':' || ul > 0xffff) {
fatal("-M range invalid");
}
ctx.tag_max = ul = strtoul(endp + 1, &endp, 10);
if (*endp != '\0' || ul > 0xffff ||
ctx.tag_max <= ctx.tag_min)
{
fatal("-M range invalid");
}
break;
}
case 'm':
break;
case 'p':
@ -1215,6 +1235,8 @@ main(int argc, char **argv) {
ctx.ksk = true;
ctx.zsk = true;
ctx.lifetime = 0;
ctx.tag_min = 0;
ctx.tag_max = 0xffff;
keygen(&ctx, mctx, argc, argv);
} else {
@ -1263,6 +1285,8 @@ main(int argc, char **argv) {
if (ctx.keystore != NULL) {
check_keystore_options(&ctx);
}
ctx.tag_min = dns_kasp_key_tagmin(kaspkey);
ctx.tag_max = dns_kasp_key_tagmax(kaspkey);
if ((ctx.ksk && !ctx.wantksk && ctx.wantzsk) ||
(ctx.zsk && !ctx.wantzsk && ctx.wantksk))
{

View file

@ -21,7 +21,7 @@ dnssec-keygen: DNSSEC key generation tool
Synopsis
~~~~~~~~
:program:`dnssec-keygen` [**-3**] [**-A** date/offset] [**-a** algorithm] [**-b** keysize] [**-C**] [**-c** class] [**-D** date/offset] [**-d** bits] [**-D** sync date/offset] [**-f** flag] [**-F**] [**-G**] [**-h**] [**-I** date/offset] [**-i** interval] [**-K** directory] [**-k** policy] [**-L** ttl] [**-l** file] [**-n** nametype] [**-P** date/offset] [**-P** sync date/offset] [**-p** protocol] [**-q**] [**-R** date/offset] [**-S** key] [**-s** strength] [**-T** rrtype] [**-t** type] [**-V**] [**-v** level] {name}
:program:`dnssec-keygen` [**-3**] [**-A** date/offset] [**-a** algorithm] [**-b** keysize] [**-C**] [**-c** class] [**-D** date/offset] [**-d** bits] [**-D** sync date/offset] [**-f** flag] [**-F**] [**-G**] [**-h**] [**-I** date/offset] [**-i** interval] [**-K** directory] [**-k** policy] [**-L** ttl] [**-l** file] [**-M** tag_min:tag_max] [**-n** nametype] [**-P** date/offset] [**-P** sync date/offset] [**-p** protocol] [**-q**] [**-R** date/offset] [**-S** key] [**-s** strength] [**-T** rrtype] [**-t** type] [**-V**] [**-v** level] {name}
Description
~~~~~~~~~~~
@ -150,6 +150,19 @@ Options
This option provides a configuration file that contains a ``dnssec-policy`` statement
(matching the policy set with :option:`-k`).
.. option:: -M tag_min:tag_max
This option sets the range of acceptable key tag values that ``dnssec-keygen``
will produce. If the key tag of the new key or the key tag of
the revoked version of the new key is outside this range,
the new key will be rejected and another new key will be generated.
This is designed to be used when generating keys in a multi-signer
scenario, where each operator is given a range of key tags to
prevent collisions among different operators. The valid values
for ``tag_min`` and ``tag_max`` are [0..65535]. The default allows all
key tag values to be produced. This option is ignored when ``-k policy``
is specified.
.. option:: -n nametype
This option specifies the owner type of the key. The value of ``nametype`` must

View file

@ -436,7 +436,10 @@ create_zsk(ksr_ctx_t *ksr, dns_kasp_key_t *kaspkey, dns_dnsseckeylist_t *keys,
}
/* Do not overwrite an existing key. */
if (key_collision(key, name, ksr->keydir, mctx, NULL)) {
if (key_collision(key, name, ksr->keydir, mctx,
dns_kasp_key_tagmin(kaspkey),
dns_kasp_key_tagmax(kaspkey), NULL))
{
conflict = true;
if (verbose > 0) {
isc_buffer_clear(&buf);

View file

@ -450,7 +450,7 @@ set_keyversion(dst_key_t *key) {
bool
key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir,
isc_mem_t *mctx, bool *exact) {
isc_mem_t *mctx, uint16_t min, uint16_t max, bool *exact) {
isc_result_t result;
bool conflict = false;
dns_dnsseckeylist_t matchkeys;
@ -468,6 +468,21 @@ key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir,
rid = dst_key_rid(dstkey);
alg = dst_key_alg(dstkey);
if (min != max) {
if (id < min || id > max) {
fprintf(stderr, "Key ID %d outside of [%u..%u]\n", id,
min, max);
return (true);
}
if (rid < min || rid > max) {
fprintf(stderr,
"Revoked Key ID %d (for tag %d) outside of "
"[%u..%u]\n",
rid, id, min, max);
return (true);
}
}
ISC_LIST_INIT(matchkeys);
result = dns_dnssec_findmatchingkeys(name, NULL, dir, NULL, now, mctx,
&matchkeys);

View file

@ -106,7 +106,7 @@ set_keyversion(dst_key_t *key);
bool
key_collision(dst_key_t *key, dns_name_t *name, const char *dir,
isc_mem_t *mctx, bool *exact);
isc_mem_t *mctx, uint16_t min, uint16_t max, bool *exact);
bool
isoptarg(const char *arg, char **argv, void (*usage)(void));

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.
*/
dnssec-policy reverse-order {
keys {
csk lifetime unlimited algorithm rsasha256 tag-range 32767 0 2048;
};
};

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.
*/
dnssec-policy too-big-start {
keys {
csk lifetime unlimited algorithm rsasha256 tag-range 65536 0 2048;
};
};

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.
*/
dnssec-policy too-big-end {
keys {
csk lifetime unlimited algorithm rsasha256 tag-range 0 65536 2048;
};
};

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.
*/
dnssec-policy start-equals-end {
keys {
csk lifetime unlimited algorithm rsasha256 tag-range 0 0 2048;
};
};

View file

@ -0,0 +1,26 @@
/*
* 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 restricted-range {
keys {
ksk lifetime unlimited algorithm rsasha256 tag-range 0 32767 2048;
zsk lifetime unlimited algorithm rsasha256 tag-range 0 32767;
};
};
dnssec-policy unrestricted-range {
keys {
ksk lifetime unlimited algorithm rsasha256 2048;
zsk lifetime unlimited algorithm rsasha256;
};
};

View file

@ -23,7 +23,7 @@ dnssec-policy "test" {
};
dnskey-ttl 3600;
keys {
ksk key-directory lifetime P1Y algorithm 13;
ksk key-directory lifetime P1Y algorithm 13 tag-range 0 32767;
zsk lifetime P30D algorithm 13;
csk key-store "hsm" lifetime P30D algorithm 8 2048;
};

View file

@ -4468,5 +4468,24 @@ n=$((n + 1))
if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "check that dnssec-keygen honours key tag ranges ($n)"
ret=0
zone=settagrange
ksk=$("$KEYGEN" -f KSK -q -a $DEFAULT_ALGORITHM -n zone -M 0:32767 "$zone")
zsk=$("$KEYGEN" -q -a $DEFAULT_ALGORITHM -n zone -M 32768:65535 "$zone")
kid=$(keyfile_to_key_id "$ksk")
zid=$(keyfile_to_key_id "$zsk")
[ $kid -ge 0 -a $kid -le 32767 ] || ret=1
[ $zid -ge 32768 -a $zid -le 65535 ] || ret=1
rksk=$($REVOKE -R $ksk)
rzsk=$($REVOKE -R $zsk)
krid=$(keyfile_to_key_id "$rksk")
zrid=$(keyfile_to_key_id "$rzsk")
[ $krid -ge 0 -a $krid -le 32767 ] || ret=1
[ $zrid -ge 32768 -a $zrid -le 65535 ] || ret=1
n=$((n + 1))
if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -92,6 +92,7 @@ key_stat() {
key_save() {
# Save key id.
key_set "$1" ID "$KEY_ID"
key_set "$1" RID "$KEY_RID"
# Save base filename.
key_set "$1" BASEFILE "$BASE_FILE"
# Save creation date.
@ -107,6 +108,7 @@ key_save() {
# This will update either the KEY1, KEY2, or KEY3 array.
key_clear() {
key_set "$1" "ID" 'no'
key_set "$1" "RID" 'no'
key_set "$1" "IDPAD" 'no'
key_set "$1" "EXPECT" 'no'
key_set "$1" "ROLE" 'none'
@ -407,6 +409,9 @@ check_key() {
[ "$ret" -eq 0 ] || _log_error "${BASE_FILE} files missing"
[ "$ret" -eq 0 ] || return 0
# Retrieve revoked key id
KEY_RID=$($REVOKE -R ${BASE_FILE})
# Retrieve creation date.
grep "; Created:" "$KEY_FILE" >"${ZONE}.${KEY_ID}.${_alg_num}.created" || _log_error "mismatch created comment in $KEY_FILE"
KEY_CREATED=$(awk '{print $3}' <"${ZONE}.${KEY_ID}.${_alg_num}.created")

View file

@ -219,6 +219,17 @@ zone "multisigner-model2.kasp" {
allow-update { any; };
};
/*
* A zone that starts with keys that have tags that are
* outside of the desired multi-signer key tag range.
*/
zone "single-to-multisigner.kasp" {
type primary;
file "single-to-multisigner.kasp.db";
dnssec-policy "multisigner-model2";
allow-update { any; };
};
/*
* Different algorithms.
*/

View file

@ -37,8 +37,8 @@ dnssec-policy "multisigner-model2" {
inline-signing no;
keys {
ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@ tag-range 32768 65535;
zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@ tag-range 32768 65535;
};
};

View file

@ -130,8 +130,8 @@ $KEYGEN -G -k rsasha256 -l policies/kasp.conf $zone >keygen.out.$zone.2 2>&1
zone="multisigner-model2.kasp"
echo_i "setting up zone: $zone"
# Import the ZSK sets of the other providers into their DNSKEY RRset.
ZSK1=$($KEYGEN -K ../ -a $DEFAULT_ALGORITHM -L 3600 $zone 2>keygen.out.$zone.1)
ZSK2=$($KEYGEN -K ../ -a $DEFAULT_ALGORITHM -L 3600 $zone 2>keygen.out.$zone.2)
ZSK1=$($KEYGEN -K ../ -a $DEFAULT_ALGORITHM -L 3600 -M 0:32767 $zone 2>keygen.out.$zone.1)
ZSK2=$($KEYGEN -K ../ -a $DEFAULT_ALGORITHM -L 3600 -M 0:32767 $zone 2>keygen.out.$zone.2)
# ZSK1 will be added to the unsigned zonefile.
cat "../${ZSK1}.key" | grep -v ";.*" >>"${zone}.db"
cat "../${ZSK1}.key" | grep -v ";.*" >"${zone}.zsk1"
@ -184,6 +184,17 @@ cat template.db.in "${CSK}.key" >"$infile"
cp $infile $zonefile
$SIGNER -PS -z -x -s now-2w -e now-1mi -o $zone -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# We are changing an existing single-signed zone to multi-signed
# zone where the key tags do not match the dnssec-policy key tag range
setup single-to-multisigner.kasp
T="now-1d"
KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -M 0:32767 -L 3600 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -M 0:32767 -L 3600 $zsktimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1
cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
$SIGNER -PS -z -x -s now-2w -e now-1mi -o $zone -f "${zonefile}" $infile >signer.out.$zone.1 2>&1
# These signatures are set to expire long in the past, update immediately.
setup expired-sigs.autosign
T="now-6mo"

View file

@ -2154,6 +2154,86 @@ retry_quiet 10 zsks_are_published || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# A zone transitioning from single-signed to multi-signed.
# We should have the old omnipresent keys outside of the
# desired key range and the new keys in the desired key range
# KEY1 and KEY2 are the new keys. KEY3 and KEY4 are the old keys.
#
set_zone "single-to-multisigner.kasp"
set_policy "multisigner-model2" "4" "3600"
set_server "ns3" "10.53.0.3"
key_clear "KEY1"
key_clear "KEY2"
key_clear "KEY3"
key_clear "KEY4"
# Key properties.
set_keyrole "KEY1" "ksk"
set_keylifetime "KEY1" "0"
set_keyalgorithm "KEY1" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS"
set_keysigning "KEY1" "yes"
set_zonesigning "KEY1" "no"
set_keyrole "KEY2" "zsk"
set_keylifetime "KEY2" "0"
set_keyalgorithm "KEY2" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS"
set_keysigning "KEY2" "no"
set_zonesigning "KEY2" "no" # waiting for DNSKEY to be omnipresent
set_keyrole "KEY3" "ksk"
set_keyalgorithm "KEY3" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS"
set_keysigning "KEY3" "yes"
set_zonesigning "KEY3" "no"
set_keyrole "KEY4" "zsk"
set_keyalgorithm "KEY4" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS"
set_keysigning "KEY4" "no"
set_zonesigning "KEY4" "yes"
set_keystate "KEY1" "GOAL" "omnipresent"
set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
set_keystate "KEY1" "STATE_DS" "hidden"
set_keystate "KEY2" "GOAL" "omnipresent"
set_keystate "KEY2" "STATE_DNSKEY" "rumoured"
set_keystate "KEY2" "STATE_ZRRSIG" "hidden" # waiting for DNSKEY to be omnipresent
set_keystate "KEY3" "GOAL" "hidden"
set_keystate "KEY3" "STATE_DNSKEY" "omnipresent"
set_keystate "KEY3" "STATE_KRRSIG" "omnipresent"
set_keystate "KEY3" "STATE_DS" "omnipresent"
set_keystate "KEY4" "GOAL" "hidden"
set_keystate "KEY4" "STATE_DNSKEY" "omnipresent"
set_keystate "KEY4" "STATE_ZRRSIG" "omnipresent"
check_keys
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
check_apex
check_subdomain
dnssec_verify
# KEY1 tag range 32768 65535
# KEY2 tag range 32768 65535
# KEY3 tag range 0 32767
# KEY4 tag range 0 32767
n=$((n + 1))
echo_i "check that the key IDs are in the expected ranges ($n)"
ret=0
test $(key_get KEY1 ID) -ge 32768 -a $(key_get KEY1 ID) -le 65535 || ret=1
test $(key_get KEY2 ID) -ge 32768 -a $(key_get KEY2 ID) -le 65535 || ret=1
test $(key_get KEY3 ID) -ge 0 -a $(key_get KEY3 ID) -le 32767 || ret=1
test $(key_get KEY4 ID) -ge 0 -a $(key_get KEY4 ID) -le 32767 || ret=1
test $(key_get KEY1 RID) -ge 32768 -a $(key_get KEY1 RID) -le 65535 || ret=1
test $(key_get KEY2 RID) -ge 32768 -a $(key_get KEY2 RID) -le 65535 || ret=1
test $(key_get KEY3 RID) -ge 0 -a $(key_get KEY3 RID) -le 32767 || ret=1
test $(key_get KEY4 RID) -ge 0 -a $(key_get KEY4 RID) -le 32767 || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status + ret))
#
# Testing manual rollover.
#

View file

@ -6474,7 +6474,7 @@ The following options can be specified in a :any:`dnssec-policy` statement:
keys {
ksk key-directory lifetime unlimited algorithm rsasha256 2048;
zsk lifetime 30d algorithm 8;
zsk lifetime 30d algorithm 8 tag-range 0 32767;
csk key-store "hsm" lifetime P6MT12H3M15S algorithm ecdsa256;
};
@ -6498,6 +6498,11 @@ The following options can be specified in a :any:`dnssec-policy` statement:
When using ``key-directory``, the key is stored in the zone's
configured :any:`key-directory`. This is also the default.
When using ``tag-range``, valid key tags for managed keys are
restricted to this range [``tag-min`` ``tag-max``]. The optional
``tag-range`` is intended to be used in multi-signer scenarios.
The default is unlimited ([0..65535]).
The ``lifetime`` parameter specifies how long a key may be used
before rolling over. For convenience, TTL-style time-unit suffixes
can be used to specify the key lifetime. It also accepts ISO 8601

View file

@ -15,7 +15,7 @@ dnssec-policy <string> {
cds-digest-types { <string>; ... };
dnskey-ttl <duration>;
inline-signing <boolean>;
keys { ( csk | ksk | zsk ) [ key-directory | key-store <string> ] lifetime <duration_or_unlimited> algorithm <string> [ <integer> ]; ... };
keys { ( csk | ksk | zsk ) [ key-directory | key-store <string> ] lifetime <duration_or_unlimited> algorithm <string> [ tag-range <integer> <integer> ] [ <integer> ]; ... };
max-zone-ttl <duration>;
nsec3param [ iterations <integer> ] [ optout <boolean> ] [ salt-length <integer> ];
offline-ksk <boolean>;

View file

@ -58,6 +58,8 @@ struct dns_kasp_key {
uint8_t algorithm;
int length;
uint8_t role;
uint16_t tag_min;
uint16_t tag_max;
};
struct dns_kasp_nsec3param {
@ -721,6 +723,26 @@ dns_kasp_key_zsk(dns_kasp_key_t *key);
*
*/
uint16_t
dns_kasp_key_tagmin(dns_kasp_key_t *key);
/*%<
* Returns the minimum permitted key tag value.
*
* Requires:
*
*\li key != NULL
*/
uint16_t
dns_kasp_key_tagmax(dns_kasp_key_t *key);
/*%<
* Returns the maximum permitted key tag value.
*
* Requires:
*
*\li key != NULL
*/
bool
dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey);
/*%<

View file

@ -401,7 +401,7 @@ dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) {
isc_result_t
dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) {
dns_kasp_key_t *key = NULL;
dns_kasp_key_t k = { .length = -1 };
dns_kasp_key_t k = { .tag_max = 0xffff, .length = -1 };
REQUIRE(DNS_KASP_VALID(kasp));
REQUIRE(keyp != NULL && *keyp == NULL);
@ -507,6 +507,18 @@ dns_kasp_key_zsk(dns_kasp_key_t *key) {
return (key->role & DNS_KASP_KEY_ROLE_ZSK);
}
uint16_t
dns_kasp_key_tagmin(dns_kasp_key_t *key) {
REQUIRE(key != NULL);
return (key->tag_min);
}
uint16_t
dns_kasp_key_tagmax(dns_kasp_key_t *key) {
REQUIRE(key != NULL);
return (key->tag_min);
}
bool
dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey) {
isc_result_t ret;
@ -532,6 +544,16 @@ dns_kasp_key_match(dns_kasp_key_t *key, dns_dnsseckey_t *dkey) {
if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(key)) {
return (false);
}
/* Valid key tag range? */
uint16_t id = dst_key_id(dkey->key);
uint16_t rid = dst_key_rid(dkey->key);
if (id < key->tag_min || id > key->tag_max) {
return (false);
}
if (rid < key->tag_min || rid > key->tag_max) {
return (false);
}
/* Found a match. */
return (true);
}

View file

@ -426,11 +426,19 @@ keymgr_key_update_lifetime(dns_dnsseckey_t *key, dns_kasp_t *kasp,
}
static bool
keymgr_keyid_conflict(dst_key_t *newkey, dns_dnsseckeylist_t *keys) {
keymgr_keyid_conflict(dst_key_t *newkey, uint16_t min, uint16_t max,
dns_dnsseckeylist_t *keys) {
uint16_t id = dst_key_id(newkey);
uint32_t rid = dst_key_rid(newkey);
uint32_t alg = dst_key_alg(newkey);
if (id < min || id > max) {
return (true);
}
if (rid < min || rid > max) {
return (true);
}
for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keys); dkey != NULL;
dkey = ISC_LIST_NEXT(dkey, link))
{
@ -484,9 +492,11 @@ keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin,
}
/* Key collision? */
conflict = keymgr_keyid_conflict(newkey, keylist);
conflict = keymgr_keyid_conflict(newkey, kkey->tag_min,
kkey->tag_max, keylist);
if (!conflict) {
conflict = keymgr_keyid_conflict(newkey, newkeys);
conflict = keymgr_keyid_conflict(
newkey, kkey->tag_min, kkey->tag_max, newkeys);
}
if (conflict) {
/* Try again. */

View file

@ -117,6 +117,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
uint32_t ksk_min_lifetime, uint32_t zsk_min_lifetime) {
isc_result_t result;
dns_kasp_key_t *key = NULL;
const cfg_obj_t *tagrange = NULL;
/* Create a new key reference. */
result = dns_kasp_key_create(kasp, &key);
@ -291,6 +292,38 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
key->length = size;
}
tagrange = cfg_tuple_get(config, "tag-range");
if (cfg_obj_istuple(tagrange)) {
uint32_t tag_min = 0, tag_max = 0xffff;
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");
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");
result = ISC_R_RANGE;
goto cleanup;
}
if (tag_min >= tag_max) {
cfg_obj_log(
obj, ISC_LOG_ERROR,
"dnssec-policy: tag-min >= tag_max");
result = ISC_R_RANGE;
goto cleanup;
}
key->tag_min = tag_min;
key->tag_max = tag_max;
}
}
dns_kasp_addkey(kasp, key);

View file

@ -649,12 +649,73 @@ static keyword_type_t lifetime_kw = { "lifetime",
static cfg_type_t cfg_type_lifetime = { "lifetime", parse_keyvalue,
print_keyvalue, doc_keyvalue,
&cfg_rep_duration, &lifetime_kw };
/*
*
*/
static void
print_tagrange(cfg_printer_t *pctx, const cfg_obj_t *obj) {
REQUIRE(pctx != NULL);
REQUIRE(obj != NULL);
REQUIRE(obj->type->rep == &cfg_rep_tuple);
if (cfg_obj_istuple(obj)) {
cfg_print_cstr(pctx, "tag-range ");
cfg_print_tuple(pctx, obj);
}
}
static cfg_tuplefielddef_t tagrange_fields[] = {
{ "tag-min", &cfg_type_uint32, 0 },
{ "tag-max", &cfg_type_uint32, 0 },
{ NULL, NULL, 0 }
};
static cfg_type_t cfg_type_tagrange = { "tagrange", cfg_parse_tuple,
print_tagrange, cfg_doc_tuple,
&cfg_rep_tuple, tagrange_fields };
static keyword_type_t tagrange_kw = { "tag-range", &cfg_type_tagrange };
static void
doc_optionaltagrange(cfg_printer_t *pctx, const cfg_type_t *type) {
UNUSED(type);
cfg_print_cstr(pctx, "[ tag-range <integer> <integer> ]");
}
static isc_result_t
parse_optionaltagrange(cfg_parser_t *pctx, const cfg_type_t *type,
cfg_obj_t **ret) {
isc_result_t result;
cfg_obj_t *obj = NULL;
UNUSED(type);
CHECK(cfg_peektoken(pctx, 0));
if (pctx->token.type == isc_tokentype_string &&
strcasecmp(TOKEN_STRING(pctx), "tag-range") == 0)
{
CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
CHECK(cfg_parse_obj(pctx, &cfg_type_tagrange, &obj));
} else {
CHECK(cfg_parse_void(pctx, NULL, &obj));
}
*ret = obj;
cleanup:
return (result);
}
static cfg_type_t cfg_type_optional_tagrange = {
"optionaltagrange", parse_optionaltagrange, NULL,
doc_optionaltagrange, &cfg_rep_tuple, &tagrange_kw
};
static cfg_tuplefielddef_t kaspkey_fields[] = {
{ "role", &cfg_type_dnsseckeyrole, 0 },
{ "keystorage", &cfg_type_optional_keystore, 0 },
{ "lifetime", &cfg_type_lifetime, 0 },
{ "algorithm", &cfg_type_algorithm, 0 },
{ "tag-range", &cfg_type_optional_tagrange, 0 },
{ "length", &cfg_type_optional_uint32, 0 },
{ NULL, NULL, 0 }
};