Merge branch '3486-checkconf-dnssec-policy-nsec3-incompatible-algorithm' into 'main'

Graceful dnssec-policy transition from NSEC only to NSEC3

Closes #3486

See merge request isc-projects/bind9!6647
This commit is contained in:
Matthijs Mekking 2022-08-22 14:37:07 +00:00
commit ae14334083
14 changed files with 586 additions and 174 deletions

View file

@ -1,3 +1,6 @@
5947. [func] Change dnssec-policy to allow graceful transition from
an NSEC only zone to NSEC3. [GL #3486]
5946. [bug] Fix statistics channel's handling of multiple HTTP
requests in a single connection which have non-empty
request bodies. [GL #3463]

View file

@ -3901,7 +3901,7 @@ main(int argc, char *argv[]) {
hashlist_init(&hashlist,
dns_db_nodecount(gdb, dns_dbtree_main) * 2,
hash_length);
result = dns_nsec_nseconly(gdb, gversion, &answer);
result = dns_nsec_nseconly(gdb, gversion, NULL, &answer);
if (result == ISC_R_NOTFOUND) {
fprintf(stderr,
"%s: warning: NSEC3 generation "

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 "badnsec3alg" {
keys {
csk lifetime unlimited algorithm rsasha1;
};
nsec3param iterations 0 optout 0 salt-length 0;
};
zone "example.net" {
type primary;
file "example.db";
dnssec-policy "badnsec3alg";
};

View file

@ -13,9 +13,10 @@
set -e
rm -f dig.out.* rndc.signing.*
rm -f dig.out.* rndc.signing.* verify.out.*
rm -f ns*/named.conf ns*/named.memstats ns*/named.run*
rm -f ns*/*.jnl ns*/*.jbk ns*/managed-keys.bind
rm -f ns*/K*.private ns*/K*.key ns*/K*.state
rm -f ns*/dsset-* ns*/*.db ns*/*.db.signed
rm -f ns*/keygen.out.* ns*/settime.out.*
rm -f created.key-* *.created unused.key-*

View file

@ -18,6 +18,12 @@ dnssec-policy "nsec" {
// NSEC will be used;
};
dnssec-policy "rsasha1" {
keys {
csk lifetime unlimited algorithm rsasha1;
};
};
dnssec-policy "nsec3" {
nsec3param;
};
@ -59,6 +65,56 @@ zone "nsec-to-nsec3.kasp" {
dnssec-policy "nsec";
};
/*
* This zone starts with NSEC, but will be reconfigured to use NSEC3.
* This should work despite the incompatible RSAHSHA1 algorithm,
* because the DS is still in hidden state.
*/
zone "rsasha1-to-nsec3.kasp" {
type primary;
file "rsasha1-to-nsec3.kasp.db";
inline-signing yes;
dnssec-policy "rsasha1";
};
/*
* This zone starts with NSEC, but will be reconfigured to use NSEC3.
* This should block because RSASHA1 is not compatible with NSEC3,
* and the DS is published.
*/
zone "rsasha1-to-nsec3-wait.kasp" {
type primary;
file "rsasha1-to-nsec3-wait.kasp.db";
inline-signing yes;
dnssec-policy "rsasha1";
};
/*
* This zone starts with NSEC3, but will be reconfigured to use NSEC with an
* NSEC only algorithm. This should work despite the incompatible RSAHSHA1
* algorithm, because the DS is still in hidden state.
*/
zone "nsec3-to-rsasha1.kasp" {
type primary;
file "nsec3-to-rsasha1.kasp.db";
inline-signing yes;
dnssec-policy "nsec3";
};
/*
* This zone starts with NSEC3, but will be reconfigured to use NSEC with an
* NSEC only algorithm. This should also be fine because we are allowed
* to change to NSEC with any algorithm, then we can also publish the new
* DNSKEY and signatures of the RSASHA1 algorithm.
*/
zone "nsec3-to-rsasha1-ds.kasp" {
type primary;
file "nsec3-to-rsasha1-ds.kasp.db";
inline-signing yes;
dnssec-policy "nsec3";
};
/* These zones use the default NSEC3 settings. */
zone "nsec3.kasp" {
type primary;

View file

@ -18,6 +18,12 @@ dnssec-policy "nsec" {
// NSEC will be used;
};
dnssec-policy "rsasha1" {
keys {
csk lifetime unlimited algorithm rsasha1;
};
};
dnssec-policy "nsec3" {
nsec3param;
};
@ -60,6 +66,59 @@ zone "nsec-to-nsec3.kasp" {
dnssec-policy "nsec3";
};
/*
* This zone starts with NSEC, but will be reconfigured to use NSEC3.
* This should work despite the incompatible RSAHSHA1 algorithm,
* because the DS is still in hidden state.
*/
zone "rsasha1-to-nsec3.kasp" {
type primary;
file "rsasha1-to-nsec3.kasp.db";
inline-signing yes;
//dnssec-policy "rsasha1";
dnssec-policy "nsec3";
};
/*
* This zone starts with NSEC, but will be reconfigured to use NSEC3.
* This should block because RSASHA1 is not compatible with NSEC3,
* and the DS is published.
*/
zone "rsasha1-to-nsec3-wait.kasp" {
type primary;
file "rsasha1-to-nsec3-wait.kasp.db";
inline-signing yes;
//dnssec-policy "rsasha1";
dnssec-policy "nsec3";
};
/*
* This zone starts with NSEC3, but will be reconfigured to use NSEC with an
* NSEC only algorithm. This should work despite the incompatible RSAHSHA1
* algorithm, because the DS is still in hidden state.
*/
zone "nsec3-to-rsasha1.kasp" {
type primary;
file "nsec3-to-rsasha1.kasp.db";
inline-signing yes;
//dnssec-policy "nsec3";
dnssec-policy "rsasha1";
};
/*
* This zone starts with NSEC3, but will be reconfigured to use NSEC with an
* NSEC only algorithm. This should also be fine because we are allowed
* to change to NSEC with any algorithm, then we can also publish the new
* DNSKEY and signatures of the RSASHA1 algorithm.
*/
zone "nsec3-to-rsasha1-ds.kasp" {
type primary;
file "nsec3-to-rsasha1-ds.kasp.db";
inline-signing yes;
//dnssec-policy "nsec3";
dnssec-policy "rsasha1";
};
/* These zones use the default NSEC3 settings. */
zone "nsec3.kasp" {
type primary;

View file

@ -30,4 +30,29 @@ do
setup "${zn}.kasp"
done
if (cd ..; $SHELL ../testcrypto.sh -q RSASHA1)
then
for zn in rsasha1-to-nsec3 rsasha1-to-nsec3-wait nsec3-to-rsasha1 \
nsec3-to-rsasha1-ds
do
setup "${zn}.kasp"
done
longago="now-1y"
keytimes="-P ${longago} -A ${longago}"
O="omnipresent"
zone="rsasha1-to-nsec3-wait.kasp"
CSK=$($KEYGEN -k "rsasha1" -l named.conf $keytimes $zone 2> keygen.out.$zone)
echo_i "Created key file $CSK"
$SETTIME -s -g $O -k $O $longago -r $O $longago -z $O $longago -d $O $longago "$CSK" > settime.out.$zone 2>&1
zone="nsec3-to-rsasha1-ds.kasp"
CSK=$($KEYGEN -k "default" -l named.conf $keytimes $zone 2> keygen.out.$zone)
echo_i "Created key file $CSK"
$SETTIME -s -g $O -k $O $longago -r $O $longago -z $O $longago -d $O $longago "$CSK" > settime.out.$zone 2>&1
else
echo_i "skip: skip rsasha1 zones - signing with RSASHA1 not supported"
fi
cp nsec3-fails-to-load.kasp.db.in nsec3-fails-to-load.kasp.db

View file

@ -36,6 +36,8 @@ rndccmd() {
set_zone_policy() {
ZONE=$1
POLICY=$2
NUM_KEYS=$3
DNSKEY_TTL=$4
}
# Set expected NSEC3 parameters: flags ($1), iterations ($2), and
# salt length ($3).
@ -47,6 +49,49 @@ set_nsec3param() {
SALT=""
}
# Set expected default dnssec-policy keys values.
set_key_default_values() {
key_clear $1
set_keyrole $1 "csk"
set_keylifetime $1 "0"
set_keyalgorithm $1 "13" "ECDSAP256SHA256" "256"
set_keysigning $1 "yes"
set_zonesigning $1 "yes"
set_keystate $1 "GOAL" "omnipresent"
set_keystate $1 "STATE_DNSKEY" "rumoured"
set_keystate $1 "STATE_KRRSIG" "rumoured"
set_keystate $1 "STATE_ZRRSIG" "rumoured"
set_keystate $1 "STATE_DS" "hidden"
}
# Set expected rsasha1 dnssec-policy keys values.
set_key_rsasha1_values() {
key_clear $1
set_keyrole $1 "csk"
set_keylifetime $1 "0"
set_keyalgorithm $1 "5" "RSASHA1" "2048"
set_keysigning $1 "yes"
set_zonesigning $1 "yes"
set_keystate $1 "GOAL" "omnipresent"
set_keystate $1 "STATE_DNSKEY" "rumoured"
set_keystate $1 "STATE_KRRSIG" "rumoured"
set_keystate $1 "STATE_ZRRSIG" "rumoured"
set_keystate $1 "STATE_DS" "hidden"
}
# Update the key states.
set_key_states() {
set_keystate $1 "GOAL" "$2"
set_keystate $1 "STATE_DNSKEY" "$3"
set_keystate $1 "STATE_KRRSIG" "$4"
set_keystate $1 "STATE_ZRRSIG" "$5"
set_keystate $1 "STATE_DS" "$6"
}
# The apex NSEC3PARAM record indicates that it is signed.
_wait_for_nsec3param() {
dig_with_opts +noquestion "@${SERVER}" "$ZONE" NSEC3PARAM > "dig.out.test$n.wait" || return 1
@ -67,7 +112,7 @@ _wait_for_nsec() {
wait_for_zone_is_signed() {
n=$((n+1))
ret=0
echo_i "wait for ${ZONE} to be signed ($n)"
echo_i "wait for ${ZONE} to be signed with $1 ($n)"
if [ "$1" = "nsec3" ]; then
retry_quiet 10 _wait_for_nsec3param || log_error "wait for ${ZONE} to be signed failed"
@ -79,24 +124,45 @@ wait_for_zone_is_signed() {
status=$((status+ret))
}
# Test: check DNSSEC verify
_check_dnssec_verify() {
dig_with_opts @$SERVER "${ZONE}" AXFR > "dig.out.test$n.axfr.$ZONE" || return 1
$VERIFY -z -o "$ZONE" "dig.out.test$n.axfr.$ZONE" > "verify.out.test$n.$ZONE" 2>&1 || return 1
return 0
}
# Test: check NSEC in answers
_check_nsec_nsec3param()
{
_check_nsec_nsec3param() {
dig_with_opts +noquestion @$SERVER "${ZONE}" NSEC3PARAM > "dig.out.test$n.nsec3param.$ZONE" || return 1
grep "NSEC3PARAM" "dig.out.test$n.nsec3param.$ZONE" > /dev/null && return 1
return 0
}
_check_nsec_nxdomain()
{
_check_nsec_nxdomain() {
dig_with_opts @$SERVER "nosuchname.${ZONE}" > "dig.out.test$n.nxdomain.$ZONE" || return 1
grep "${ZONE}.*IN.*NSEC.*NS.*SOA.*RRSIG.*NSEC.*DNSKEY" "dig.out.test$n.nxdomain.$ZONE" > /dev/null || return 1
grep "NSEC3" "dig.out.test$n.nxdomain.$ZONE" > /dev/null && return 1
return 0
}
check_nsec()
{
check_nsec() {
wait_for_zone_is_signed "nsec"
n=$((n+1))
echo_i "check DNSKEY rrset is signed correctly for zone ${ZONE} ($n)"
ret=0
check_keys
retry_quiet 10 _check_apex_dnskey || log_error "bad DNSKEY RRset for zone ${ZONE}"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "verify DNSSEC for zone ${ZONE} ($n)"
ret=0
retry_quiet 10 _check_dnssec_verify || log_error "DNSSEC verify failed for zone ${ZONE}"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "check NSEC3PARAM response for zone ${ZONE} ($n)"
ret=0
@ -113,8 +179,7 @@ check_nsec()
}
# Test: check NSEC3 parameters in answers
_check_nsec3_nsec3param()
{
_check_nsec3_nsec3param() {
dig_with_opts +noquestion @$SERVER "${ZONE}" NSEC3PARAM > "dig.out.test$n.nsec3param.$ZONE" || return 1
grep "${ZONE}.*0.*IN.*NSEC3PARAM.*1.*0.*${ITERATIONS}.*${SALT}" "dig.out.test$n.nsec3param.$ZONE" > /dev/null || return 1
@ -124,15 +189,15 @@ _check_nsec3_nsec3param()
return 0
}
_check_nsec3_nxdomain()
{
_check_nsec3_nxdomain() {
dig_with_opts @$SERVER "nosuchname.${ZONE}" > "dig.out.test$n.nxdomain.$ZONE" || return 1
grep ".*\.${ZONE}.*IN.*NSEC3.*1.${FLAGS}.*${ITERATIONS}.*${SALT}" "dig.out.test$n.nxdomain.$ZONE" > /dev/null || return 1
return 0
}
check_nsec3()
{
check_nsec3() {
wait_for_zone_is_signed "nsec3"
n=$((n+1))
echo_i "check that NSEC3PARAM 1 0 ${ITERATIONS} is published zone ${ZONE} ($n)"
ret=0
@ -146,74 +211,119 @@ check_nsec3()
retry_quiet 10 _check_nsec3_nxdomain || log_error "bad NXDOMAIN response for zone ${ZONE}"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
n=$((n+1))
echo_i "verify DNSSEC for zone ${ZONE} ($n)"
ret=0
retry_quiet 10 _check_dnssec_verify || log_error "DNSSEC verify failed for zone ${ZONE}"
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
}
start_time="$(TZ=UTC date +%s)"
status=0
n=0
key_clear "KEY1"
key_clear "KEY2"
key_clear "KEY3"
key_clear "KEY4"
# Zone: nsec-to-nsec3.kasp.
set_zone_policy "nsec-to-nsec3.kasp" "nsec"
set_zone_policy "nsec-to-nsec3.kasp" "nsec" 1 3600
set_server "ns3" "10.53.0.3"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec
dnssec_verify
if ($SHELL ../testcrypto.sh -q RSASHA1)
then
# Zone: rsasha1-to-nsec3.kasp.
set_zone_policy "rsasha1-to-nsec3.kasp" "rsasha1" 1 3600
set_server "ns3" "10.53.0.3"
set_key_rsasha1_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec
# Zone: rsasha1-to-nsec3-wait.kasp.
set_zone_policy "rsasha1-to-nsec3-wait.kasp" "rsasha1" 1 3600
set_server "ns3" "10.53.0.3"
set_key_rsasha1_values "KEY1"
set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent"
echo_i "initial check zone ${ZONE}"
check_nsec
# Zone: nsec3-to-rsasha1.kasp.
set_zone_policy "nsec3-to-rsasha1.kasp" "nsec3" 1 3600
set_server "ns3" "10.53.0.3"
set_key_rsasha1_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
# Zone: nsec3-to-rsasha1-ds.kasp.
set_zone_policy "nsec3-to-rsasha1-ds.kasp" "nsec3" 1 3600
set_server "ns3" "10.53.0.3"
set_key_rsasha1_values "KEY1"
set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent"
echo_i "initial check zone ${ZONE}"
check_nsec3
fi
# Zone: nsec3.kasp.
set_zone_policy "nsec3.kasp" "nsec3"
set_zone_policy "nsec3.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-dynamic.kasp.
set_zone_policy "nsec3-dynamic.kasp" "nsec3"
set_zone_policy "nsec3-dynamic.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-change.kasp.
set_zone_policy "nsec3-change.kasp" "nsec3"
set_zone_policy "nsec3-change.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-dynamic-change.kasp.
set_zone_policy "nsec3-dynamic-change.kasp" "nsec3"
set_zone_policy "nsec3-dynamic-change.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-to-nsec.kasp.
set_zone_policy "nsec3-to-nsec.kasp" "nsec3"
set_zone_policy "nsec3-to-nsec.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-to-optout.kasp.
set_zone_policy "nsec3-to-optout.kasp" "nsec3"
set_zone_policy "nsec3-to-optout.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-from-optout.kasp.
set_zone_policy "nsec3-from-optout.kasp" "optout"
set_zone_policy "nsec3-from-optout.kasp" "optout" 1 3600
set_nsec3param "1" "0" "0"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Zone: nsec3-other.kasp.
set_zone_policy "nsec3-other.kasp" "nsec3-other"
set_zone_policy "nsec3-other.kasp" "nsec3-other" 1 3600
set_nsec3param "1" "11" "8"
set_key_default_values "KEY1"
echo_i "initial check zone ${ZONE}"
check_nsec3
dnssec_verify
# Reconfig named.
echo_i "reconfig dnssec-policy to trigger nsec3 rollovers"
@ -221,88 +331,141 @@ copy_setports ns3/named2.conf.in ns3/named.conf
rndc_reconfig ns3 10.53.0.3
# Zone: nsec-to-nsec3.kasp. (reconfigured)
set_zone_policy "nsec-to-nsec3.kasp" "nsec3"
set_zone_policy "nsec-to-nsec3.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
dnssec_verify
if ($SHELL ../testcrypto.sh -q RSASHA1)
then
# Zone: rsasha1-to-nsec3.kasp.
set_zone_policy "rsasha1-to-nsec3.kasp" "nsec3" 2 3600
set_server "ns3" "10.53.0.3"
set_key_rsasha1_values "KEY1"
set_key_states "KEY1" "hidden" "unretentive" "unretentive" "unretentive" "hidden"
set_keysigning "KEY1" "no"
set_zonesigning "KEY1" "no"
set_key_default_values "KEY2"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
# Zone: rsasha1-to-nsec3-wait.kasp.
set_zone_policy "rsasha1-to-nsec3-wait.kasp" "nsec3" 2 3600
set_server "ns3" "10.53.0.3"
set_key_rsasha1_values "KEY1"
set_key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "omnipresent"
set_key_default_values "KEY2"
echo_i "check zone ${ZONE} after reconfig"
ret=0
wait_for_log 10 "zone $ZONE/IN (signed): wait building NSEC3 chain until NSEC only DNSKEYs are removed" ns3/named.run || ret=1
test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
check_nsec
# Zone: nsec3-to-rsasha1.kasp.
set_zone_policy "nsec3-to-rsasha1.kasp" "rsasha1" 2 3600
set_nsec3param "1" "0" "0"
set_server "ns3" "10.53.0.3"
set_key_default_values "KEY1"
set_key_states "KEY1" "hidden" "unretentive" "unretentive" "unretentive" "hidden"
set_keysigning "KEY1" "no"
set_zonesigning "KEY1" "no"
set_key_rsasha1_values "KEY2"
echo_i "check zone ${ZONE} after reconfig"
check_nsec
# Zone: nsec3-to-rsasha1-ds.kasp.
set_zone_policy "nsec3-to-rsasha1-ds.kasp" "rsasha1" 2 3600
set_nsec3param "1" "0" "0"
set_server "ns3" "10.53.0.3"
set_key_default_values "KEY1"
set_key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "omnipresent"
set_key_rsasha1_values "KEY2"
echo_i "check zone ${ZONE} after reconfig"
check_nsec
key_clear "KEY1"
key_clear "KEY2"
fi
# Zone: nsec3.kasp. (same)
set_zone_policy "nsec3.kasp" "nsec3"
set_zone_policy "nsec3.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
dnssec_verify
# Zone: nsec3-dyamic.kasp. (same)
set_zone_policy "nsec3-dynamic.kasp" "nsec3"
set_zone_policy "nsec3-dynamic.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
dnssec_verify
# Zone: nsec3-change.kasp. (reconfigured)
set_zone_policy "nsec3-change.kasp" "nsec3-other"
set_zone_policy "nsec3-change.kasp" "nsec3-other" 1 3600
set_nsec3param "1" "11" "8"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
dnssec_verify
# Zone: nsec3-dynamic-change.kasp. (reconfigured)
set_zone_policy "nsec3-dynamic-change.kasp" "nsec3-other"
set_zone_policy "nsec3-dynamic-change.kasp" "nsec3-other" 1 3600
set_nsec3param "1" "11" "8"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
dnssec_verify
# Zone: nsec3-to-nsec.kasp. (reconfigured)
set_zone_policy "nsec3-to-nsec.kasp" "nsec"
set_zone_policy "nsec3-to-nsec.kasp" "nsec" 1 3600
set_nsec3param "1" "11" "8"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec
dnssec_verify
# Zone: nsec3-to-optout.kasp. (reconfigured)
# DISABLED:
# There is a bug in the nsec3param building code that thinks when the
# optout bit is changed, the chain already exists. [GL #2216]
#set_zone_policy "nsec3-to-optout.kasp" "optout"
#set_zone_policy "nsec3-to-optout.kasp" "optout" 1 3600
#set_nsec3param "1" "0" "0"
#set_key_default_values "KEY1"
#echo_i "check zone ${ZONE} after reconfig"
#check_nsec3
#dnssec_verify
# Zone: nsec3-from-optout.kasp. (reconfigured)
# DISABLED:
# There is a bug in the nsec3param building code that thinks when the
# optout bit is changed, the chain already exists. [GL #2216]
#set_zone_policy "nsec3-from-optout.kasp" "nsec3"
#set_zone_policy "nsec3-from-optout.kasp" "nsec3" 1 3600
#set_nsec3param "0" "0" "0"
#set_key_default_values "KEY1"
#echo_i "check zone ${ZONE} after reconfig"
#check_nsec3
#dnssec_verify
# Zone: nsec3-other.kasp. (same)
set_zone_policy "nsec3-other.kasp" "nsec3-other"
set_zone_policy "nsec3-other.kasp" "nsec3-other" 1 3600
set_nsec3param "1" "11" "8"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reconfig"
check_nsec3
dnssec_verify
# Using rndc signing -nsec3param (should fail)
set_zone_policy "nsec3-change.kasp" "nsec3-other"
set_zone_policy "nsec3-change.kasp" "nsec3-other" 1 3600
echo_i "use rndc signing -nsec3param ${ZONE} to change NSEC3 settings"
rndccmd $SERVER signing -nsec3param 1 1 12 ffff $ZONE > rndc.signing.test$n.$ZONE || log_error "failed to call rndc signing -nsec3param $ZONE"
grep "zone uses dnssec-policy, use rndc dnssec command instead" rndc.signing.test$n.$ZONE > /dev/null || log_error "rndc signing -nsec3param should fail"
check_nsec3
dnssec_verify
# Test NSEC3 and NSEC3PARAM is the same after restart
set_zone_policy "nsec3.kasp" "nsec3"
set_zone_policy "nsec3.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} before restart"
check_nsec3
dnssec_verify
# Restart named, NSEC3 should stay the same.
ret=0
@ -318,22 +481,22 @@ test "$ret" -eq 0 || echo_i "failed"
status=$((status+ret))
prevsalt="${SALT}"
set_zone_policy "nsec3.kasp" "nsec3"
set_zone_policy "nsec3.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
SALT="${prevsalt}"
echo_i "check zone ${ZONE} after restart has salt ${SALT}"
check_nsec3
dnssec_verify
# Zone: nsec3-fails-to-load.kasp. (should be fixed after reload)
cp ns3/template.db.in ns3/nsec3-fails-to-load.kasp.db
rndc_reload ns3 10.53.0.3
set_zone_policy "nsec3-fails-to-load.kasp" "nsec3"
set_zone_policy "nsec3-fails-to-load.kasp" "nsec3" 1 3600
set_nsec3param "0" "0" "0"
set_key_default_values "KEY1"
echo_i "check zone ${ZONE} after reload"
check_nsec3
dnssec_verify
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -38,6 +38,12 @@ Feature Changes
- Zones using ``dnssec-policy`` now require dynamic DNS or
``inline-signing`` to be configured explicitly :gl:`#3381`.
- When reconfiguring ``dnssec-policy`` from using NSEC with an NSEC-only DNSKEY
algorithm (e.g. RSASHA1) to a policy that uses NSEC3, BIND will no longer fail
to sign the zone, but keep using NSEC for a little longer until the offending
DNSKEY records have been removed from the zone, then switch to using NSEC3.
:gl:`#3486`
Bug Fixes
~~~~~~~~~

View file

@ -19,6 +19,7 @@
#include <isc/lang.h>
#include <dns/diff.h>
#include <dns/name.h>
#include <dns/types.h>
@ -60,11 +61,15 @@ dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type);
*/
isc_result_t
dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, bool *answer);
dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff,
bool *answer);
/*
* Report whether the DNSKEY RRset has a NSEC only algorithm. Unknown
* algorithms are assumed to support NSEC3. If DNSKEY is not found,
* *answer is set to false, and ISC_R_NOTFOUND is returned.
* If 'diff' is provided, check if the NSEC only DNSKEY will be deleted.
* If so, and there are no other NSEC only DNSKEYs that will stay in 'db',
* consider the DNSKEY RRset to have no NSEC only DNSKEYs.
*
* Requires:
* 'answer' to be non NULL.

View file

@ -29,6 +29,7 @@
#include <isc/tls.h>
#include <dns/catz.h>
#include <dns/diff.h>
#include <dns/master.h>
#include <dns/masterdump.h>
#include <dns/rdatastruct.h>
@ -2761,3 +2762,20 @@ dns_zone_gettid(dns_zone_t *zone);
*
* \return thread id associated with the zone
*/
bool
dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db,
dns_dbversion_t *ver, dns_diff_t *diff,
dst_key_t **keys, unsigned int numkeys);
/**<
* Return whether the zone would enter an inconsistent state where NSEC only
* DNSKEYs are present along NSEC3 chains.
*
* Requires:
* \li 'zone' to be a valid zone.
* \li 'db'is not NULL.
*
* Returns:
* \li 'true' if the check passes, that is the zone remains consistent,
* 'false' if the zone would have NSEC only DNSKEYs and an NSEC3 chain.
*/

View file

@ -247,7 +247,8 @@ dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type) {
}
isc_result_t
dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, bool *answer) {
dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff,
bool *answer) {
dns_dbnode_t *node = NULL;
dns_rdataset_t rdataset;
dns_rdata_dnskey_t dnskey;
@ -282,8 +283,35 @@ dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, bool *answer) {
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (dnskey.algorithm == DST_ALG_RSAMD5 ||
dnskey.algorithm == DST_ALG_RSASHA1) {
break;
dnskey.algorithm == DST_ALG_DH ||
dnskey.algorithm == DST_ALG_DSA ||
dnskey.algorithm == DST_ALG_RSASHA1)
{
bool deleted = false;
if (diff != NULL) {
for (dns_difftuple_t *tuple =
ISC_LIST_HEAD(diff->tuples);
tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link))
{
if (tuple->rdata.type !=
dns_rdatatype_dnskey ||
tuple->op != DNS_DIFFOP_DEL) {
continue;
}
if (dns_rdata_compare(
&rdata, &tuple->rdata) == 0)
{
deleted = true;
break;
}
}
}
if (!deleted) {
break;
}
}
}
dns_rdataset_disassociate(&rdataset);

View file

@ -3726,7 +3726,7 @@ zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) {
* latter to exist in the first place.
*/
dns_db_currentversion(db, &version);
result = dns_nsec_nseconly(db, version, &nseconly);
result = dns_nsec_nseconly(db, version, NULL, &nseconly);
nsec3ok = (result == ISC_R_SUCCESS && !nseconly);
dns_db_closeversion(db, &version, false);
if (!nsec3ok && (nsec3param->flags & DNS_NSEC3FLAG_REMOVE) == 0) {
@ -3918,7 +3918,7 @@ resume_addnsec3chain(dns_zone_t *zone) {
* In order to create NSEC3 chains we need the DNSKEY RRset at zone
* apex to exist and contain no keys using NSEC-only algorithms.
*/
result = dns_nsec_nseconly(db, version, &nseconly);
result = dns_nsec_nseconly(db, version, NULL, &nseconly);
nsec3ok = (result == ISC_R_SUCCESS && !nseconly);
/*
@ -8186,7 +8186,7 @@ try_private:
goto add;
}
result = dns_nsec_nseconly(db, ver, &nseconly);
result = dns_nsec_nseconly(db, ver, diff, &nseconly);
nsec3ok = (result == ISC_R_SUCCESS && !nseconly);
/*
@ -9508,6 +9508,105 @@ failure:
return (result);
}
/*
* Prevent the zone entering a inconsistent state where
* NSEC only DNSKEYs are present with NSEC3 chains.
*/
bool
dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db,
dns_dbversion_t *ver, dns_diff_t *diff,
dst_key_t **keys, unsigned int numkeys) {
uint8_t alg;
dns_rdatatype_t privatetype;
;
bool nseconly = false, nsec3 = false;
isc_result_t result;
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(db != NULL);
privatetype = dns_zone_getprivatetype(zone);
/* Scan the tuples for an NSEC-only DNSKEY */
if (diff != NULL) {
for (dns_difftuple_t *tuple = ISC_LIST_HEAD(diff->tuples);
tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link))
{
if (nseconly && nsec3) {
break;
}
if (tuple->op != DNS_DIFFOP_ADD) {
continue;
}
if (tuple->rdata.type == dns_rdatatype_nsec3param) {
nsec3 = true;
}
if (tuple->rdata.type != dns_rdatatype_dnskey) {
continue;
}
alg = tuple->rdata.data[3];
if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH ||
alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1)
{
nseconly = true;
}
}
}
/* Scan the zone keys for an NSEC-only DNSKEY */
if (keys != NULL && !nseconly) {
for (unsigned int i = 0; i < numkeys; i++) {
alg = dst_key_alg(keys[i]);
if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH ||
alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1)
{
nseconly = true;
break;
}
}
}
/* Check DB for NSEC-only DNSKEY */
if (!nseconly) {
result = dns_nsec_nseconly(db, ver, diff, &nseconly);
/*
* Adding an NSEC3PARAM record can proceed without a
* DNSKEY (it will trigger a delayed change), so we can
* ignore ISC_R_NOTFOUND here.
*/
if (result == ISC_R_NOTFOUND) {
result = ISC_R_SUCCESS;
}
CHECK(result);
}
/* Check existing DB for NSEC3 */
if (!nsec3) {
CHECK(dns_nsec3_activex(db, ver, false, privatetype, &nsec3));
}
/* Check kasp for NSEC3PARAM settings */
if (!nsec3) {
dns_kasp_t *kasp = dns_zone_getkasp(zone);
if (kasp != NULL) {
nsec3 = dns_kasp_nsec3(kasp);
}
}
/* Refuse to allow NSEC3 with NSEC-only keys */
if (nseconly && nsec3) {
goto failure;
}
return (true);
failure:
return (false);
}
/*
* Incrementally sign the zone using the keys requested.
* Builds the NSEC chain if required.
@ -9643,6 +9742,15 @@ zone_sign(dns_zone_t *zone) {
/* Determine which type of chain to build */
if (use_kasp) {
build_nsec3 = dns_kasp_nsec3(kasp);
if (!dns_zone_check_dnskey_nsec3(zone, db, version, NULL,
(dst_key_t **)&zone_keys,
nkeys))
{
dnssec_log(zone, ISC_LOG_INFO,
"wait building NSEC3 chain until NSEC only "
"DNSKEYs are removed");
build_nsec3 = false;
}
build_nsec = !build_nsec3;
} else {
CHECK(dns_private_chains(db, version, zone->privatetype,
@ -20506,63 +20614,6 @@ failure:
return (result);
}
/*
* Prevent the zone entering a inconsistent state where
* NSEC only DNSKEYs are present with NSEC3 chains.
* See update.c:check_dnssec()
*/
static bool
dnskey_sane(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
dns_diff_t *diff) {
isc_result_t result;
dns_difftuple_t *tuple;
bool nseconly = false, nsec3 = false;
dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
/* Scan the tuples for an NSEC-only DNSKEY */
for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link))
{
uint8_t alg;
if (tuple->rdata.type != dns_rdatatype_dnskey ||
tuple->op != DNS_DIFFOP_ADD) {
continue;
}
alg = tuple->rdata.data[3];
if (alg == DST_ALG_RSASHA1) {
nseconly = true;
break;
}
}
/* Check existing DB for NSEC-only DNSKEY */
if (!nseconly) {
result = dns_nsec_nseconly(db, ver, &nseconly);
if (result == ISC_R_NOTFOUND) {
result = ISC_R_SUCCESS;
}
CHECK(result);
}
/* Check existing DB for NSEC3 */
if (!nsec3) {
CHECK(dns_nsec3_activex(db, ver, false, privatetype, &nsec3));
}
/* Refuse to allow NSEC3 with NSEC-only keys */
if (nseconly && nsec3) {
dnssec_log(zone, ISC_LOG_ERROR,
"NSEC only DNSKEYs and NSEC3 chains not allowed");
goto failure;
}
return (true);
failure:
return (false);
}
static isc_result_t
clean_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
dns_diff_t *diff) {
@ -21558,6 +21609,7 @@ zone_rekey(dns_zone_t *zone) {
if (result == ISC_R_SUCCESS) {
bool cdsdel = false;
bool cdnskeydel = false;
bool sane_diff, sane_dnskey;
isc_stdtime_t when;
/*
@ -21726,9 +21778,21 @@ zone_rekey(dns_zone_t *zone) {
}
}
if ((newactive || fullsign || !ISC_LIST_EMPTY(diff.tuples)) &&
dnskey_sane(zone, db, ver, &diff))
{
/*
* A sane diff is one that is not empty, and that does not
* introduce a zone with NSEC only DNSKEYs along with NSEC3
* chains.
*/
sane_dnskey = dns_zone_check_dnskey_nsec3(zone, db, ver, &diff,
NULL, 0);
sane_diff = !ISC_LIST_EMPTY(diff.tuples) && sane_dnskey;
if (!sane_dnskey) {
dnssec_log(zone, ISC_LOG_ERROR,
"NSEC only DNSKEYs and NSEC3 chains not "
"allowed");
}
if (newactive || fullsign || sane_diff) {
CHECK(dns_diff_apply(&diff, db, ver));
CHECK(clean_nsec3param(zone, db, ver, &diff));
CHECK(add_signing_records(db, zone->privatetype, ver,
@ -22913,7 +22977,7 @@ rss_post(dns_zone_t *zone, isc_event_t *event) {
dns_rdata_init(&rdata);
np->data[2] |= DNS_NSEC3FLAG_CREATE;
result = dns_nsec_nseconly(db, newver, &nseconly);
result = dns_nsec_nseconly(db, newver, NULL, &nseconly);
if (result == ISC_R_NOTFOUND || nseconly) {
np->data[2] |= DNS_NSEC3FLAG_INITIAL;
}

View file

@ -2096,56 +2096,12 @@ failure:
static isc_result_t
check_dnssec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
dns_dbversion_t *ver, dns_diff_t *diff) {
dns_difftuple_t *tuple;
bool nseconly = false, nsec3 = false;
isc_result_t result;
unsigned int iterations = 0;
dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
/* Scan the tuples for an NSEC-only DNSKEY or an NSEC3PARAM */
for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link))
{
if (tuple->op != DNS_DIFFOP_ADD) {
continue;
}
if (tuple->rdata.type == dns_rdatatype_dnskey) {
uint8_t alg;
alg = tuple->rdata.data[3];
if (alg == DST_ALG_RSASHA1) {
nseconly = true;
break;
}
} else if (tuple->rdata.type == dns_rdatatype_nsec3param) {
nsec3 = true;
break;
}
}
/* Check existing DB for NSEC-only DNSKEY */
if (!nseconly) {
result = dns_nsec_nseconly(db, ver, &nseconly);
/*
* An NSEC3PARAM update can proceed without a DNSKEY (it
* will trigger a delayed change), so we can ignore
* ISC_R_NOTFOUND here.
*/
if (result == ISC_R_NOTFOUND) {
result = ISC_R_SUCCESS;
}
CHECK(result);
}
/* Check existing DB for NSEC3 */
if (!nsec3) {
CHECK(dns_nsec3_activex(db, ver, false, privatetype, &nsec3));
}
/* Refuse to allow NSEC3 with NSEC-only keys */
if (nseconly && nsec3) {
if (!dns_zone_check_dnskey_nsec3(zone, db, ver, diff, NULL, 0)) {
update_log(client, zone, ISC_LOG_ERROR,
"NSEC only DNSKEYs and NSEC3 chains not allowed");
result = DNS_R_REFUSED;
@ -2346,8 +2302,11 @@ add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
* supporting an NSEC3 chain, then we set the
* INITIAL flag to indicate that these parameters
* are to be used later.
*
* Don't provide a 'diff' here because we want to
* know the capability of the current database.
*/
result = dns_nsec_nseconly(db, ver, &nseconly);
result = dns_nsec_nseconly(db, ver, NULL, &nseconly);
if (result == ISC_R_NOTFOUND || nseconly) {
buf[2] |= DNS_NSEC3FLAG_INITIAL;
}