diff --git a/CHANGES b/CHANGES index a6a44f5125..fac1565025 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,20 @@ +5365. [bug] Algorithm rollover was stuck on submitting DS + because keymgr thought it would move to an invalid + state. Fixed by when checking the current key, + check it against the desired state, not the existing + state. [GL #1626] + +5364. [bug] Algorithm rollover waited too long before introducing + zone signatures. It waited to make sure all signatures + were resigned, but when introducing a new algorithm, + all signatures are resigned immediately. Only + add the sign delay if there is a predecessor key. + [GL #1625] + +5363. [bug] When changing a dnssec-policy, existing keys with + properties that no longer match were not being retired. + [GL #1624] + 5362. [func] Limit the size of IXFR responses so that AXFR will be used instead if it would be smaller. This is controlled by the "max-ixfr-ratio" option, which diff --git a/bin/tests/system/kasp/README b/bin/tests/system/kasp/README index ceafd19772..61c0a2d095 100644 --- a/bin/tests/system/kasp/README +++ b/bin/tests/system/kasp/README @@ -11,3 +11,5 @@ ns2 is running primary service for ns3. ns3 is an authoritative server for the various test domains. ns4 and ns5 are authoritative servers for various test domains related to views. + +ns6 is an authoritative server that tests changes in dnssec-policy. diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh index 803dd703cd..cf2f2452ce 100644 --- a/bin/tests/system/kasp/clean.sh +++ b/bin/tests/system/kasp/clean.sh @@ -22,5 +22,4 @@ rm -f ns*/dsset-* ns*/*.db ns*/*.db.signed rm -f ns*/keygen.out.* ns*/settime.out.* ns*/signer.out.* rm -f ns*/managed-keys.bind rm -f ns*/*.mkeys -# NS3 specific -rm -f ns3/zones ns3/*.db.infile +rm -f ns*/zones* ns*/*.db.infile diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in index 38a656b0d3..da7c04734e 100644 --- a/bin/tests/system/kasp/ns3/named.conf.in +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -202,6 +202,30 @@ zone "zsk-retired.autosign" { dnssec-policy "autosign"; }; +/* + * Zones for testing enabling DNSSEC. + */ +zone "step1.enable-dnssec.autosign" { + type master; + file "step1.enable-dnssec.autosign.db"; + dnssec-policy "enable-dnssec"; +}; +zone "step2.enable-dnssec.autosign" { + type master; + file "step2.enable-dnssec.autosign.db"; + dnssec-policy "enable-dnssec"; +}; +zone "step3.enable-dnssec.autosign" { + type master; + file "step3.enable-dnssec.autosign.db"; + dnssec-policy "enable-dnssec"; +}; +zone "step4.enable-dnssec.autosign" { + type master; + file "step4.enable-dnssec.autosign.db"; + dnssec-policy "enable-dnssec"; +}; + /* * Zones for testing ZSK Pre-Publication steps. */ @@ -259,11 +283,6 @@ zone "step5.ksk-doubleksk.autosign" { file "step5.ksk-doubleksk.autosign.db"; dnssec-policy "ksk-doubleksk"; }; -zone "step6.ksk-doubleksk.autosign" { - type master; - file "step6.ksk-doubleksk.autosign.db"; - dnssec-policy "ksk-doubleksk"; -}; /* * Zones for testing CSK rollover steps. diff --git a/bin/tests/system/kasp/ns3/policies/autosign.conf b/bin/tests/system/kasp/ns3/policies/autosign.conf index 751783ee0e..bafbe859ef 100644 --- a/bin/tests/system/kasp/ns3/policies/autosign.conf +++ b/bin/tests/system/kasp/ns3/policies/autosign.conf @@ -23,6 +23,27 @@ dnssec-policy "autosign" { }; }; +dnssec-policy "enable-dnssec" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 300; + max-zone-ttl PT12H; + zone-propagation-delay PT5M; + retire-safety PT20M; + publish-safety PT5M; + + parent-propagation-delay 1h; + parent-registration-delay P1D; + parent-ds-ttl 2h; + + keys { + csk lifetime unlimited algorithm 13; + }; +}; + dnssec-policy "zsk-prepub" { signatures-refresh P1W; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index e1f065dce2..5be0f0b0c0 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -149,6 +149,53 @@ private_type_record $zone 13 "$ZSK" >> "$infile" $SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 $SETTIME -s -I now -g HIDDEN "$ZSK" > settime.out.$zone.3 2>&1 +# +# The zones at enable-dnssec.autosign represent the various steps of the +# initial signing of a zone. +# + +# Step 1: +# This is an unsigned zone and named should perform the initial steps of +# introducing the DNSSEC records in the right order. +setup step1.enable-dnssec.autosign +cp template.db.in $zonefile + +# Step 2: +# The DNSKEY has been published long enough to become OMNIPRESENT. +setup step2.enable-dnssec.autosign +CSK=$($KEYGEN -k enable-dnssec -l policies/autosign.conf $zone 2> keygen.out.$zone.1) +TpubN="now-900s" +$SETTIME -s -P $TpubN -A $TpubN -g $O -k $R $TpubN -r $R $TpubN -d $H $TpubN -z $R $TpubN "$CSK" > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 "$CSK" >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# The zone signatures have been published long enough to become OMNIPRESENT. +setup step3.enable-dnssec.autosign +CSK=$($KEYGEN -k enable-dnssec -l policies/autosign.conf $zone 2> keygen.out.$zone.1) +TpubN="now-44700s" +TactN="now-43800s" +$SETTIME -s -P $TpubN -A $TpubN -g $O -k $O $TactN -r $O $TactN -d $H $TpubN -z $R $TpubN "$CSK" > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 "$CSK" >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 +setup step3.enable-dnssec.autosign + +# Step 4: +# The DS has been submitted long enough ago to become OMNIPRESENT. +# Add 27 hour plus retire safety of 20 minutes (98400 seconds) to the times. +setup step4.enable-dnssec.autosign +CSK=$($KEYGEN -k enable-dnssec -l policies/autosign.conf $zone 2> keygen.out.$zone.1) +TpubN="now-143100s" +TactN="now-142200s" +TomnN="now-98400s" +$SETTIME -s -P $TpubN -A $TpubN -g $O -k $O $TactN -r $O $TactN -d $R $TomnN -z $O $TomnN "$CSK" > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 "$CSK" >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 +setup step3.enable-dnssec.autosign + # # The zones at zsk-prepub.autosign represent the various steps of a ZSK # Pre-Publication rollover. diff --git a/bin/tests/system/kasp/ns6/named.conf.in b/bin/tests/system/kasp/ns6/named.conf.in new file mode 100644 index 0000000000..5a6ca042c4 --- /dev/null +++ b/bin/tests/system/kasp/ns6/named.conf.in @@ -0,0 +1,48 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS6 + +include "policies/kasp.conf"; +include "policies/csk1.conf"; + +options { + query-source address 10.53.0.6; + notify-source 10.53.0.6; + transfer-source 10.53.0.6; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.6; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "step1.algorithm-roll.kasp" { + type master; + file "step1.algorithm-roll.kasp.db"; + dnssec-policy "rsasha1"; +}; + +zone "step1.csk-algorithm-roll.kasp" { + type master; + file "step1.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; diff --git a/bin/tests/system/kasp/ns6/named2.conf.in b/bin/tests/system/kasp/ns6/named2.conf.in new file mode 100644 index 0000000000..52660c6084 --- /dev/null +++ b/bin/tests/system/kasp/ns6/named2.conf.in @@ -0,0 +1,111 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS6 + +include "policies/kasp.conf"; +include "policies/csk2.conf"; + +options { + query-source address 10.53.0.6; + notify-source 10.53.0.6; + transfer-source 10.53.0.6; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.6; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "step1.algorithm-roll.kasp" { + type master; + file "step1.algorithm-roll.kasp.db"; + dnssec-policy "ecdsa256"; +}; + +zone "step2.algorithm-roll.kasp" { + type master; + file "step2.algorithm-roll.kasp.db"; + dnssec-policy "ecdsa256"; +}; + +zone "step3.algorithm-roll.kasp" { + type master; + file "step3.algorithm-roll.kasp.db"; + dnssec-policy "ecdsa256"; +}; + +zone "step4.algorithm-roll.kasp" { + type master; + file "step4.algorithm-roll.kasp.db"; + dnssec-policy "ecdsa256"; +}; + +zone "step5.algorithm-roll.kasp" { + type master; + file "step5.algorithm-roll.kasp.db"; + dnssec-policy "ecdsa256"; +}; + +zone "step6.algorithm-roll.kasp" { + type master; + file "step6.algorithm-roll.kasp.db"; + dnssec-policy "ecdsa256"; +}; + +/* + * Zones for testing CSK algorithm roll. + */ +zone "step1.csk-algorithm-roll.kasp" { + type master; + file "step1.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; + +zone "step2.csk-algorithm-roll.kasp" { + type master; + file "step2.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; + +zone "step3.csk-algorithm-roll.kasp" { + type master; + file "step3.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; + +zone "step4.csk-algorithm-roll.kasp" { + type master; + file "step4.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; + +zone "step5.csk-algorithm-roll.kasp" { + type master; + file "step5.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; + +zone "step6.csk-algorithm-roll.kasp" { + type master; + file "step6.csk-algorithm-roll.kasp.db"; + dnssec-policy "csk-algoroll"; +}; diff --git a/bin/tests/system/kasp/ns6/policies/csk1.conf b/bin/tests/system/kasp/ns6/policies/csk1.conf new file mode 100644 index 0000000000..8f93444807 --- /dev/null +++ b/bin/tests/system/kasp/ns6/policies/csk1.conf @@ -0,0 +1,29 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "csk-algoroll" { + signatures-refresh P5D; + signatures-validity 30d; + signatures-validity-dnskey 30d; + + keys { + csk lifetime unlimited algorithm rsasha1; + }; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 2h; + zone-propagation-delay 3600; + max-zone-ttl 6h; + parent-registration-delay 1d; + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; diff --git a/bin/tests/system/kasp/ns6/policies/csk2.conf b/bin/tests/system/kasp/ns6/policies/csk2.conf new file mode 100644 index 0000000000..f379c0574f --- /dev/null +++ b/bin/tests/system/kasp/ns6/policies/csk2.conf @@ -0,0 +1,29 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "csk-algoroll" { + signatures-refresh P5D; + signatures-validity 30d; + signatures-validity-dnskey 30d; + + keys { + csk lifetime unlimited algorithm 13; + }; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 2h; + zone-propagation-delay 3600; + max-zone-ttl 6h; + parent-registration-delay 1d; + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; diff --git a/bin/tests/system/kasp/ns6/policies/kasp.conf b/bin/tests/system/kasp/ns6/policies/kasp.conf new file mode 100644 index 0000000000..ad7028ed0c --- /dev/null +++ b/bin/tests/system/kasp/ns6/policies/kasp.conf @@ -0,0 +1,50 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "rsasha1" { + signatures-refresh P5D; + signatures-validity 30d; + signatures-validity-dnskey 30d; + + keys { + ksk lifetime unlimited algorithm rsasha1; + zsk lifetime unlimited algorithm rsasha1; + }; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 2h; + zone-propagation-delay 3600; + max-zone-ttl 6h; + parent-registration-delay 1d; + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; + +dnssec-policy "ecdsa256" { + signatures-refresh P5D; + signatures-validity 30d; + signatures-validity-dnskey 30d; + + keys { + ksk lifetime unlimited algorithm ecdsa256; + zsk lifetime unlimited algorithm ecdsa256; + }; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 2h; + zone-propagation-delay 3600; + max-zone-ttl 6h; + parent-registration-delay 1d; + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; diff --git a/bin/tests/system/kasp/ns6/setup.sh b/bin/tests/system/kasp/ns6/setup.sh new file mode 100644 index 0000000000..cae3475535 --- /dev/null +++ b/bin/tests/system/kasp/ns6/setup.sh @@ -0,0 +1,295 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns6/setup.sh" + +setup() { + zone="$1" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + infile="${zone}.db.infile" + echo "$zone" >> zones.2 +} + +private_type_record() { + _zone=$1 + _algorithm=$2 + _keyfile=$3 + + _id=$(keyfile_to_key_id "$_keyfile") + + printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id" +} + + +# Make lines shorter by storing key states in environment variables. +H="HIDDEN" +R="RUMOURED" +O="OMNIPRESENT" +U="UNRETENTIVE" + +# +# The zones at algorithm-roll.kasp represent the various steps of a ZSK/KSK +# algorithm rollover. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.algorithm-roll.kasp +KSK=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2) +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN "$KSK" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN "$ZSK" > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 5 "$KSK" >> "$infile" +private_type_record $zone 5 "$ZSK" >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# After the publication interval has passed the DNSKEY is OMNIPRESENT. +setup step2.algorithm-roll.kasp +KSK1=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK1=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2) +KSK2=$($KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK2=$($KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2) +# The time passed since the new algorithm keys have been introduced is 3 hours. +TactN="now-3h" +TpubN1="now-3h" +TactN1="now+6h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -r $O $TactN -d $O $TactN "$KSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -z $O $TactN "$ZSK1" > settime.out.$zone.2 2>&1 +$SETTIME -s -P $TpubN1 -A $TpubN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 "$KSK2" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $R $TpubN1 -z $R $TpubN1 "$ZSK2" > settime.out.$zone.2 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${KSK1}.state" +echo "Lifetime: 0" >> "${ZSK1}.state" +cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 5 "$KSK1" >> "$infile" +private_type_record $zone 5 "$ZSK1" >> "$infile" +private_type_record $zone 13 "$KSK2" >> "$infile" +private_type_record $zone 13 "$ZSK2" >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# The zone signatures are also OMNIPRESENT. +setup step3.algorithm-roll.kasp +KSK1=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK1=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2) +KSK2=$($KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK2=$($KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2) +# The time passed since the new algorithm keys have been introduced is 9 hours. +TactN="now-9h" +TpubN1="now-9h" +TactN1="now" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -r $O $TactN -d $O $TactN "$KSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -z $O $TactN "$ZSK1" > settime.out.$zone.2 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -r $O $TpubN1 -d $H $TpubN1 "$KSK2" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" > settime.out.$zone.2 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${KSK1}.state" +echo "Lifetime: 0" >> "${ZSK1}.state" +cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 5 "$KSK1" >> "$infile" +private_type_record $zone 5 "$ZSK1" >> "$infile" +private_type_record $zone 13 "$KSK2" >> "$infile" +private_type_record $zone 13 "$ZSK2" >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# The DS is swapped and can become OMNIPRESENT. +setup step4.algorithm-roll.kasp +KSK1=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK1=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2) +KSK2=$($KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK2=$($KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2) +# The time passed since the DS has been swapped is 29 hours. +TactN="now-38h" +TpubN1="now-38h" +TactN1="now-29h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -r $O $TactN -d $U $TactN1 "$KSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -z $O $TactN "$ZSK1" > settime.out.$zone.2 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -r $O $TpubN1 -d $R $TactN1 "$KSK2" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" > settime.out.$zone.2 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${KSK1}.state" +echo "Lifetime: 0" >> "${ZSK1}.state" +cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 5 "$KSK1" >> "$infile" +private_type_record $zone 5 "$ZSK1" >> "$infile" +private_type_record $zone 13 "$KSK2" >> "$infile" +private_type_record $zone 13 "$ZSK2" >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# The DNSKEY is removed long enough to be HIDDEN. +setup step5.algorithm-roll.kasp +KSK1=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK1=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2) +KSK2=$($KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK2=$($KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2) +# The time passed since the DNSKEY has been removed is 2 hours. +TactN="now-40h" +TpubN1="now-40h" +TactN1="now-31h" +TremN="now-2h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $U $TremN -r $U $TremN -d $H $TremN "$KSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $U $TremN -z $U $TremN "$ZSK1" > settime.out.$zone.2 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -r $O $TpubN1 -d $O $TremN "$KSK2" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" > settime.out.$zone.2 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${KSK1}.state" +echo "Lifetime: 0" >> "${ZSK1}.state" +cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 5 "$KSK1" >> "$infile" +private_type_record $zone 5 "$ZSK1" >> "$infile" +private_type_record $zone 13 "$KSK2" >> "$infile" +private_type_record $zone 13 "$ZSK2" >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 6: +# The RRSIGs have been removed long enough to be HIDDEN. +setup step6.algorithm-roll.kasp +KSK1=$($KEYGEN -a RSASHA1 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK1=$($KEYGEN -a RSASHA1 -L 3600 $zone 2> keygen.out.$zone.2) +KSK2=$($KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1) +ZSK2=$($KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2) +# Additional time passed: 7h. +TactN="now-47h" +TpubN1="now-47h" +TactN1="now-38h" +TremN="now-9h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $U $TremN -r $U $TremN -d $H $TremN "$KSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $U $TremN -z $U $TremN "$ZSK1" > settime.out.$zone.2 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -r $O $TpubN1 -d $O $TremN "$KSK2" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" > settime.out.$zone.2 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${KSK1}.state" +echo "Lifetime: 0" >> "${ZSK1}.state" +cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 5 "$KSK1" >> "$infile" +private_type_record $zone 5 "$ZSK1" >> "$infile" +private_type_record $zone 13 "$KSK2" >> "$infile" +private_type_record $zone 13 "$ZSK2" >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at csk-algorithm-roll.kasp represent the various steps of a CSK +# algorithm rollover. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.csk-algorithm-roll.kasp +echo "$zone" >> zones +CSK=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $zone 2> keygen.out.$zone.1) +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -z $O $TactN -d $O $TactN "$CSK" > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 5 "$CSK" >> "$infile" +$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# After the publication interval has passed the DNSKEY is OMNIPRESENT. +setup step2.csk-algorithm-roll.kasp +CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $zone 2> keygen.out.$zone.1) +CSK2=$($KEYGEN -k csk-algoroll -l policies/csk2.conf $zone 2> keygen.out.$zone.1) +# The time passed since the new algorithm keys have been introduced is 3 hours. +TactN="now-3h" +TpubN1="now-3h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -r $O $TactN -z $O $TactN -d $O $TactN "$CSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TpubN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -z $R $TpubN1 -d $H $TpubN1 "$CSK2" > settime.out.$zone.1 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${CSK1}.state" +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 5 "$CSK1" >> "$infile" +private_type_record $zone 13 "$CSK2" >> "$infile" +$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# The zone signatures are also OMNIPRESENT. +setup step3.csk-algorithm-roll.kasp +CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $zone 2> keygen.out.$zone.1) +CSK2=$($KEYGEN -k csk-algoroll -l policies/csk2.conf $zone 2> keygen.out.$zone.1) +# The time passed since the new algorithm keys have been introduced is 9 hours. +TactN="now-9h" +TpubN1="now-9h" +TactN1="now-6h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -r $O $TactN -z $O $TactN -d $O $TactN "$CSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TpubN1 -g $O -k $O $TactN1 -r $O $TactN1 -z $R $TpubN1 -d $H $TpubN1 "$CSK2" > settime.out.$zone.1 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${CSK1}.state" +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 5 "$CSK1" >> "$infile" +private_type_record $zone 13 "$CSK2" >> "$infile" +$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# The DS is swapped and can become OMNIPRESENT. +setup step4.csk-algorithm-roll.kasp +CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $zone 2> keygen.out.$zone.1) +CSK2=$($KEYGEN -k csk-algoroll -l policies/csk2.conf $zone 2> keygen.out.$zone.1) +# The time passed since the DS has been swapped is 29 hours. +TactN="now-38h" +TpubN1="now-38h" +TactN1="now-35h" +TsubN1="now-29h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -r $O $TactN -z $O $TactN -d $U $TactN1 "$CSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TpubN1 -g $O -k $O $TactN1 -r $O $TactN1 -z $O $TsubN1 -d $R $TsubN1 "$CSK2" > settime.out.$zone.1 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${CSK1}.state" +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 5 "$CSK1" >> "$infile" +private_type_record $zone 13 "$CSK2" >> "$infile" +$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# The DNSKEY is removed long enough to be HIDDEN. +setup step5.csk-algorithm-roll.kasp +CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $zone 2> keygen.out.$zone.1) +CSK2=$($KEYGEN -k csk-algoroll -l policies/csk2.conf $zone 2> keygen.out.$zone.1) +# The time passed since the DNSKEY has been removed is 2 hours. +TactN="now-40h" +TpubN1="now-40h" +TactN1="now-37h" +TsubN1="now-31h" +TremN="now-2h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $U $TremN -r $U $TremN -z $U $TremN -d $H $TremN "$CSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TpubN1 -g $O -k $O $TactN1 -r $O $TactN1 -z $O $TsubN1 -d $O $TremN "$CSK2" > settime.out.$zone.1 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${CSK1}.state" +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 5 "$CSK1" >> "$infile" +private_type_record $zone 13 "$CSK2" >> "$infile" +$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 6: +# The RRSIGs have been removed long enough to be HIDDEN. +setup step6.csk-algorithm-roll.kasp +CSK1=$($KEYGEN -k csk-algoroll -l policies/csk1.conf $zone 2> keygen.out.$zone.1) +CSK2=$($KEYGEN -k csk-algoroll -l policies/csk2.conf $zone 2> keygen.out.$zone.1) +# Additional time passed: 7h. +TactN="now-47h" +TpubN1="now-47h" +TactN1="now-44h" +TsubN1="now-38h" +TremN="now-9h" +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $U $TremN -r $U $TremN -z $U $TremN -d $H $TremN "$CSK1" > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TpubN1 -A $TpubN1 -g $O -k $O $TactN1 -r $O $TactN1 -z $O $TsubN1 -d $O $TremN "$CSK2" > settime.out.$zone.1 2>&1 +# Fake lifetime of old algorithm keys. +echo "Lifetime: 0" >> "${CSK1}.state" +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 5 "$CSK1" >> "$infile" +private_type_record $zone 13 "$CSK2" >> "$infile" +$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/kasp/ns6/template.db.in b/bin/tests/system/kasp/ns6/template.db.in new file mode 100644 index 0000000000..051a312891 --- /dev/null +++ b/bin/tests/system/kasp/ns6/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$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 + diff --git a/bin/tests/system/kasp/setup.sh b/bin/tests/system/kasp/setup.sh index 0d93046ae1..a41b0ca946 100644 --- a/bin/tests/system/kasp/setup.sh +++ b/bin/tests/system/kasp/setup.sh @@ -22,6 +22,7 @@ copy_setports ns2/named.conf.in ns2/named.conf copy_setports ns3/named.conf.in ns3/named.conf copy_setports ns4/named.conf.in ns4/named.conf copy_setports ns5/named.conf.in ns5/named.conf +copy_setports ns6/named.conf.in ns6/named.conf # Setup zones ( @@ -40,3 +41,7 @@ copy_setports ns5/named.conf.in ns5/named.conf cd ns5 $SHELL setup.sh ) +( + cd ns6 + $SHELL setup.sh +) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 47d8e05747..d5f6b1808a 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -94,6 +94,8 @@ key_clear() { } # Start clear. +# There can be at most 4 keys at the same time during a rollover: +# 2x KSK, 2x ZSK key_clear "KEY1" key_clear "KEY2" key_clear "KEY3" @@ -125,18 +127,13 @@ get_keys_which_signed() { awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' < "$_output" } -# Get the key ids from key files for zone $2 in directory $1 -# that matches algorithm $3. +# Get the key ids from key files for zone $2 in directory $1. get_keyids() { _dir=$1 _zone=$2 - _algorithm=$(printf "%03d" "$3") - _start="K${_zone}.+${_algorithm}+" - _end=".key" + _regex="K${_zone}.+*+*.key" - if [ "$_algorithm" -ne 0 ]; then - find "${_dir}" -mindepth 1 -maxdepth 1 -name "${_start}*${_end}" | sed "s,$_dir/K${_zone}.+${_algorithm}+\([0-9]\{5\}\)${_end},\1," - fi + find "${_dir}" -mindepth 1 -maxdepth 1 -name "${_regex}" | sed "s,$_dir/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2," } # By default log errors and don't quit immediately. @@ -289,6 +286,12 @@ check_key() { STATE_FILE="${BASE_FILE}.state" KEY_ID="${_key_id}" + # Check file existence. + [ -s "$KEY_FILE" ] || ret=1 + [ -s "$PRIVATE_FILE" ] || ret=1 + [ -s "$STATE_FILE" ] || ret=1 + [ "$ret" -eq 0 ] || return + test $_log -eq 1 && echo_i "check key $BASE_FILE" # Check the public key file. @@ -415,7 +418,7 @@ key_unused() { _zone=$ZONE _key_idpad=$1 _key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//') - _alg_num=$(key_get KEY1 ALG_NUM) + _alg_num=$2 _alg_numpad=$(printf "%03d" "$_alg_num") BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" @@ -426,6 +429,12 @@ key_unused() { test $_log -eq 1 && echo_i "key unused $KEY_ID?" + # Check file existence. + [ -s "$KEY_FILE" ] || ret=1 + [ -s "$PRIVATE_FILE" ] || ret=1 + [ -s "$STATE_FILE" ] || ret=1 + [ "$ret" -eq 0 ] || return + # Check timing metadata. grep "; Publish:" "$KEY_FILE" > /dev/null && log_error "unexpected publish comment in $KEY_FILE" grep "Publish:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected publish in $PRIVATE_FILE" @@ -456,6 +465,9 @@ dnssec_verify() status=$((status+ret)) } +# Default next key event threshold. May be extended by wait periods. +next_key_event_threshold=100 + ############################################################################### # Tests # ############################################################################### @@ -473,32 +485,29 @@ lines=$(wc -l < "keygen.out.$POLICY.test$n") test "$lines" -eq 4 || log_error "wrong number of keys created for policy kasp: $lines" # Temporarily don't log errors because we are searching multiple files. _log=0 -# Check one algorithm. + key_properties "KEY1" "csk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" -ids=$(get_keyids "$DIR" "$ZONE" "$(key_get KEY1 ALG_NUM)") -for id in $ids; do - check_key "KEY1" "$id" -done -test "$ret" -eq 0 || echo_i "failed" -status=$((status+ret)) -# Check the other algorithm. -key_properties "KEY1" "ksk" "31536000" "8" "RSASHA256" "2048" "no" "yes" -key_timings "KEY1" "none" "none" "none" "none" "none" -key_states "KEY1" "none" "none" "none" "none" "none" -key_properties "KEY2" "zsk" "2592000" "8" "RSASHA256" "1024" "yes" "no" +key_properties "KEY2" "ksk" "31536000" "8" "RSASHA256" "2048" "no" "yes" key_timings "KEY2" "none" "none" "none" "none" "none" key_states "KEY2" "none" "none" "none" "none" "none" -key_properties "KEY3" "zsk" "16070400" "8" "RSASHA256" "2000" "yes" "no" +key_properties "KEY3" "zsk" "2592000" "8" "RSASHA256" "1024" "yes" "no" key_timings "KEY3" "none" "none" "none" "none" "none" key_states "KEY3" "none" "none" "none" "none" "none" -ids=$(get_keyids "$DIR" "$ZONE" "$(key_get KEY1 ALG_NUM)") +key_properties "KEY4" "zsk" "16070400" "8" "RSASHA256" "2000" "yes" "no" +key_timings "KEY4" "none" "none" "none" "none" "none" +key_states "KEY4" "none" "none" "none" "none" "none" + +lines=$(get_keyids "$DIR" "$ZONE" | wc -l) +test "$lines" -eq 4 || log_error "bad number of key ids" + +ids=$(get_keyids "$DIR" "$ZONE") for id in $ids; do - # There are three key files with the same algorithm. + # There are four key files with the same algorithm. # Check them until a match is found. ret=0 && check_key "KEY1" "$id" test "$ret" -eq 0 && continue @@ -507,6 +516,9 @@ for id in $ids; do test "$ret" -eq 0 && continue ret=0 && check_key "KEY3" "$id" + test "$ret" -eq 0 && continue + + ret=0 && check_key "KEY4" "$id" # If ret is still non-zero, non of the files matched. test "$ret" -eq 0 || echo_i "failed" @@ -525,7 +537,7 @@ key_states "KEY1" "none" "none" "none" "none" "none" $KEYGEN -k "$POLICY" "$ZONE" > "keygen.out.$POLICY.test$n" 2>/dev/null || ret=1 lines=$(wc -l < "keygen.out.default.test$n") test "$lines" -eq 1 || log_error "wrong number of keys created for policy default: $lines" -ids=$(get_keyids "$DIR" "$ZONE" "$(key_get KEY1 ALG_NUM)") +ids=$(get_keyids "$DIR" "$ZONE") for id in $ids; do check_key "KEY1" "$id" done @@ -542,7 +554,7 @@ key_states "KEY1" "none" "none" "none" "none" "none" $KEYGEN -k "$POLICY" "$ZONE" > "keygen.out.$POLICY.test$n" 2>/dev/null || ret=1 lines=$(wc -l < "keygen.out.$POLICY.test$n") test "$lines" -eq 1 || log_error "wrong number of keys created for policy default: $lines" -ids=$(get_keyids "$DIR" "$ZONE" "$(key_get KEY1 ALG_NUM)") +ids=$(get_keyids "$DIR" "$ZONE") for id in $ids; do check_key "KEY1" "$id" done @@ -602,7 +614,6 @@ check_key "KEY1" "$id" test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) - # # named # @@ -624,6 +635,14 @@ do grep "NS SOA" "dig.out.ns3.test$n.$zone" > /dev/null || ret=1 grep "$zone\..*IN.*RRSIG" "dig.out.ns3.test$n.$zone" > /dev/null || ret=1 done < ns3/zones + + while read -r zone + do + dig_with_opts "$zone" @10.53.0.6 nsec > "dig.out.ns6.test$n.$zone" || ret=1 + grep "NS SOA" "dig.out.ns6.test$n.$zone" > /dev/null || ret=1 + grep "$zone\..*IN.*RRSIG" "dig.out.ns6.test$n.$zone" > /dev/null || ret=1 + done < ns6/zones + i=$((i+1)) if [ $ret = 0 ]; then break; fi echo_i "waiting ... ($i)" @@ -632,6 +651,8 @@ done test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +next_key_event_threshold=$((next_key_event_threshold+i)) + # # Zone: default.kasp. # @@ -647,7 +668,7 @@ key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" n=$((n+1)) echo_i "check key is created for zone ${ZONE} ($n)" ret=0 -ids=$(get_keyids "$DIR" "$ZONE" "$(key_get KEY1 ALG_NUM)") +ids=$(get_keyids "$DIR" "$ZONE") for id in $ids; do check_key "KEY1" "$id" done @@ -664,7 +685,7 @@ echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 dig_with_opts "$ZONE" "@${SERVER}" $qtype > "dig.out.$DIR.test$n" || log_error "dig ${ZONE} ${qtype} failed" grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || log_error "mismatch status in DNS response" -grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*${KEY1__ALG_NUM}" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${qtype} record in response" +grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${qtype} record in response" lines=$(get_keys_which_signed $qtype "dig.out.$DIR.test$n" | wc -l) test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" get_keys_which_signed $qtype "dig.out.$DIR.test$n" | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}" @@ -737,29 +758,24 @@ key_timings "KEY3" "published" "active" "retired" "none" "none" key_states "KEY1" "omnipresent" "rumoured" "none" "rumoured" "hidden" key_states "KEY2" "omnipresent" "rumoured" "rumoured" "none" "none" key_states "KEY3" "omnipresent" "rumoured" "rumoured" "none" "none" +key_clear "KEY4" # Check keys for a configured zone. This verifies: # 1. The right number of keys exist in the key pool ($1). -# 2. The right number of keys is active (always expect three keys). -# The algorithm expected is set with $2 (string) and $3 (number), and the -# expected sizes for the keys are set with $4 (ksk), $5 and $6 (zsk). -# A size set to 0 means the corresponding key (KEY1, KEY2 or KEY3) is not -# expected. +# 2. The right number of keys is active. Checks KEY1, KEY2, KEY3, and KEY4. # -# It is expected that KEY1, KEY2 and KEY3 arrays are set correctly. Found key -# identifiers are stored in the right key array. +# It is expected that KEY1, KEY2, KEY3, and KEY4 arrays are set correctly. +# Found key identifiers are stored in the right key array. check_keys() { n=$((n+1)) echo_i "check keys are created for zone ${ZONE} ($n)" ret=0 - _key_algnum=$(key_get KEY1 ALG_NUM) - n=$((n+1)) - echo_i "check number of keys with algorithm ${_key_algnum} for zone ${ZONE} in dir ${DIR} ($n)" + echo_i "check number of keys for zone ${ZONE} in dir ${DIR} ($n)" ret=0 - _numkeys=$(get_keyids "$DIR" "$ZONE" "$_key_algnum" | wc -l) + _numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l) test "$_numkeys" -eq "$NUM_KEYS" || log_error "bad number ($_numkeys) of key files for zone $ZONE (expected $NUM_KEYS)" test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) @@ -771,9 +787,10 @@ check_keys() key_set KEY1 ID "no" key_set KEY2 ID "no" key_set KEY3 ID "no" + key_set KEY4 ID "no" # Check key files. - _ids=$(get_keyids "$DIR" "$ZONE" "$_key_algnum") + _ids=$(get_keyids "$DIR" "$ZONE") for _id in $_ids; do # There are three key files with the same algorithm. # Check them until a match is found. @@ -794,9 +811,14 @@ check_keys() check_key "KEY3" "$_id" test "$ret" -eq 0 && key_set KEY3 ID "$KEY_ID" && continue fi + if [ "no" = "$(key_get KEY4 ID)" ] && [ "$(key_get KEY4 EXPECT)" = "yes" ]; then + ret=0 + check_key "KEY4" "$_id" + test "$ret" -eq 0 && key_set KEY4 ID "$KEY_ID" && continue + fi - # This may be an unused key. - ret=0 && key_unused "$_id" + # This may be an unused key. Assume algorithm of KEY1. + ret=0 && key_unused "$_id" "$(key_get KEY1 ALG_NUM)" test "$ret" -eq 0 && continue # If ret is still non-zero, non of the files matched. @@ -817,6 +839,9 @@ check_keys() if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then test "no" = "$(key_get KEY3 ID)" && log_error "No KEY3 found for zone ${ZONE}" fi + if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then + test "no" = "$(key_get KEY4 ID)" && log_error "No KEY4 found for zone ${ZONE}" + fi test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) } @@ -851,6 +876,12 @@ check_signatures() { elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY3 ID)" fi + + if [ "$(key_get KEY4 "$_expect_type")" = "yes" ] && [ "$(key_get KEY4 "$_role")" = "yes" ]; then + get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null || log_error "${_qtype} RRset not signed with key $(key_get KEY4 ID)" + elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then + get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY4 ID)" + fi } response_has_cds_for_key() ( @@ -858,7 +889,7 @@ response_has_cds_for_key() ( -v ttl="${DNSKEY_TTL}" \ -v qtype="CDS" \ -v keyid="$(key_get "${1}" ID)" \ - -v keyalg="${_key_algnum}" \ + -v keyalg="$(key_get "${1}" ALG_NUM)" \ -v hashalg="2" \ 'BEGIN { ret=1; } $1 == zone && $2 == ttl && $4 == qtype && $5 == keyid && $6 == keyalg && $7 == hashalg { ret=0; exit; } @@ -871,7 +902,7 @@ response_has_cdnskey_for_key() ( -v ttl="${DNSKEY_TTL}" \ -v qtype="CDNSKEY" \ -v flags="257" \ - -v keyalg="${_key_algnum}" \ + -v keyalg="$(key_get "${1}" ALG_NUM)" \ 'BEGIN { ret=1; } $1 == zone && $2 == ttl && $4 == qtype && $5 == flags && $7 == keyalg { ret=0; exit; } END { exit ret; }' \ @@ -881,8 +912,6 @@ response_has_cdnskey_for_key() ( # Test CDS and CDNSKEY publication. check_cds() { - _key_algnum="$(key_get KEY1 ALG_NUM)" - n=$((n+1)) echo_i "check CDS and CDNSKEY rrset are signed correctly for zone ${ZONE} ($n)" ret=0 @@ -932,6 +961,19 @@ check_cds() { # so let's skip this check for now. fi + if [ "$(key_get KEY4 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DS)" = "omnipresent" ]; then + response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" || log_error "missing CDS record in response for key $(key_get KEY4 ID)" + check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK" + response_has_cdnskey_for_key KEY4 "dig.out.$DIR.test$n.cdnskey" || log_error "missing CDNSKEY record in response for key $(key_get KEY4 ID)" + check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK" + elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then + response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" && log_error "unexpected CDS record in response for key $(key_get KEY4 ID)" + # KEY4 should not have an associated CDNSKEY, but there may be + # one for another key. Since the CDNSKEY has no field for key + # id, it is hard to check what key the CDNSKEY may belong to + # so let's skip this check for now. + fi + test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) } @@ -939,38 +981,45 @@ check_cds() { # Test the apex of a configured zone. This checks that the SOA and DNSKEY # RRsets are signed correctly and with the appropriate keys. check_apex() { - # Test DNSKEY query. _qtype="DNSKEY" - _key_algnum="$(key_get KEY1 ALG_NUM)" n=$((n+1)) echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 dig_with_opts "$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || log_error "dig ${ZONE} ${_qtype} failed" grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || log_error "mismatch status in DNS response" + if [ "$(key_get KEY1 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DNSKEY)" = "omnipresent" ]; then - grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY1 ID)" + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY1 ID)" check_signatures $_qtype "dig.out.$DIR.test$n" "KSK" numkeys=$((numkeys+1)) elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY1 ID)" + grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY1 ID)" fi if [ "$(key_get KEY2 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DNSKEY)" = "omnipresent" ]; then - grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY2 ID)" + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY2 ID)" check_signatures $_qtype "dig.out.$DIR.test$n" "KSK" numkeys=$((numkeys+1)) elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY2 ID)" + grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY2 ID)" fi if [ "$(key_get KEY3 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DNSKEY)" = "omnipresent" ]; then - grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY3 ID)" + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY3 ID)" check_signatures $_qtype "dig.out.$DIR.test$n" "KSK" numkeys=$((numkeys+1)) elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY3 ID)" + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY3 ID)" + fi + + if [ "$(key_get KEY4 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DNSKEY)" = "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY4 ID)" + check_signatures $_qtype "dig.out.$DIR.test$n" "KSK" + numkeys=$((numkeys+1)) + elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY4 ID)" fi lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l) @@ -1022,6 +1071,7 @@ zone_properties "ns3" "unsigned.kasp" "none" "0" "0" "10.53.0.3" key_clear "KEY1" key_clear "KEY2" key_clear "KEY3" +key_clear "KEY4" check_keys check_apex check_subdomain @@ -1033,6 +1083,7 @@ zone_properties "ns3" "unlimited.kasp" "unlimited" "1234" "1" "10.53.0.3" key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_clear "KEY2" key_clear "KEY3" +key_clear "KEY4" # The first key is immediately published and activated. key_timings "KEY1" "published" "active" "none" "none" "none" # DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. @@ -1059,6 +1110,7 @@ key_timings "KEY3" "published" "active" "retired" "none" "none" key_states "KEY1" "omnipresent" "rumoured" "none" "rumoured" "hidden" key_states "KEY2" "omnipresent" "rumoured" "rumoured" "none" "none" key_states "KEY3" "omnipresent" "rumoured" "rumoured" "none" "none" +key_clear "KEY4" check_keys check_apex check_subdomain @@ -1243,6 +1295,7 @@ key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" key_timings "KEY2" "published" "active" "retired" "none" "none" # Expect only two keys. key_clear "KEY3" +key_clear "KEY4" check_keys check_apex @@ -1553,6 +1606,99 @@ dnssec_verify # Clear TSIG. TSIG="" +# +# Testing DNSSEC introduction. +# + +# +# Zone: step1.enable-dnssec.autosign. +# +zone_properties "ns3" "step1.enable-dnssec.autosign" "enable-dnssec" "300" "1" "10.53.0.3" +# The DNSKEY and signatures are introduced first, the DS remains hidden. +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "published" "active" "none" "none" "none" +key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +check_next_key_event() { + _expect=$1 + + n=$((n+1)) + echo_i "check next key event for zone ${ZONE} ($n)" + ret=0 + grep "zone ${ZONE}.*: next key event in .* seconds" "${DIR}/named.run" > "keyevent.out.$ZONE.test$n" || log_error "no next key event for zone ${ZONE}" + + # Get the latest next key event. + _time=$(awk '{print $10}' < "keyevent.out.$ZONE.test$n" | tail -1) + + # The next key event time must within threshold of the + # expected time. + _expectmin=$((_expect-next_key_event_threshold)) + _expectmax=$((_expect+next_key_event_threshold)) + + test $_expectmin -le "$_time" || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" + test $_expectmax -ge "$_time" || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" + + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Next key event is when the DNSKEY RRset becomes OMNIPRESENT: DNSKEY TTL plus +# publish safety plus the zone propagation delay: 900 seconds. +check_next_key_event 900 + +# +# Zone: step2.enable-dnssec.autosign. +# +zone_properties "ns3" "step2.enable-dnssec.autosign" "enable-dnssec" "300" "1" "10.53.0.3" +# The DNSKEY and signatures are introduced first, the DS remains hidden. +key_states "KEY1" "omnipresent" "omnipresent" "rumoured" "omnipresent" "hidden" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the zone signatures become OMNIPRESENT: max-zone-ttl +# plus zone propagation delay plus retire safety minus the already elapsed +# 900 seconds: 12h + 300s + 20m - 900 = 44700 - 900 = 43800 seconds +check_next_key_event 43800 + +# +# Zone: step3.enable-dnssec.autosign. +# +zone_properties "ns3" "step3.enable-dnssec.autosign" "enable-dnssec" "300" "1" "10.53.0.3" +# The DS can be introduced. +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "rumoured" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DS can move to the OMNIPRESENT state. This occurs +# when the parent registration and propagation delay have passed, plus the +# DS TTL and retire safety delay: 1d + 1h + 2h + 20m = 27h20m = 98400 seconds +check_next_key_event 98400 + +# +# Zone: step4.enable-dnssec.autosign. +# +zone_properties "ns3" "step4.enable-dnssec.autosign" "enable-dnssec" "300" "1" "10.53.0.3" +# The DS is omnipresent. +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is never, the zone dnssec-policy has been established. So we +# fall back to the default loadkeys interval. +check_next_key_event 3600 + # # Testing ZSK Pre-Publication rollover. # @@ -1575,33 +1721,12 @@ check_apex check_subdomain dnssec_verify -check_next_key_event() { - _expect=$1 - - n=$((n+1)) - echo_i "check next key event for zone ${ZONE} ($n)" - ret=0 - grep "zone ${ZONE}.*: next key event in .* seconds" "${DIR}/named.run" > "keyevent.out.$ZONE.test$n" || log_error "no next key event for zone ${ZONE}" - - _time=$(awk '{print $10}' < "keyevent.out.$ZONE.test$n") - - # The next key event time must within 60 seconds of the - # expected time. - _expectmin=$((_expect-60)) - _expectmax=$((_expect+60)) - - test $_expectmin -le "$_time" || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" - test $_expectmax -ge "$_time" || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" - - test "$ret" -eq 0 || echo_i "failed" - status=$((status+ret)) -} - # Next key event is when the successor ZSK needs to be published. That is # the ZSK lifetime - prepublication time. The prepublication time is DNSKEY # TTL plus publish safety plus the zone propagation delay. For the # zsk-prepub policy that means: 30d - 3600s + 1d + 1h = 2498400 seconds. check_next_key_event 2498400 + # # Zone: step2.zsk-prepub.autosign. # @@ -1961,7 +2086,7 @@ dnssec_verify check_next_key_event 13708800 # -# Testing CSK key rollover (1). +# Testing CSK key rollover (2). # # @@ -2098,5 +2223,347 @@ dnssec_verify # Next key event is when the new successor needs to be published. check_next_key_event 14684400 +# +# Testing algorithm rollover. +# + +# +# Zone: step1.algorithm-roll.kasp +# +zone_properties "ns6" "step1.algorithm-roll.kasp" "rsasha1" "3600" "2" "10.53.0.6" +# The KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. +key_properties "KEY1" "ksk" "0" "5" "RSASHA1" "2048" "no" "yes" +key_timings "KEY1" "published" "active" "none" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "0" "5" "RSASHA1" "2048" "yes" "no" +key_timings "KEY2" "published" "active" "none" "none" "none" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +key_clear "KEY3" +key_clear "KEY4" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor keys need to be published. +# Since the lifetime of the keys are unlimited, so default to loadkeys +# interval. +check_next_key_event 3600 + +# +# Zone: step1.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step1.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "1" "10.53.0.6" +# The CSK (KEY1) starta in OMNIPRESENT. +key_properties "KEY1" "csk" "0" "5" "RSASHA1" "2048" "yes" "yes" +key_timings "KEY1" "published" "active" "none" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +key_clear "KEY2" +key_clear "KEY3" +key_clear "KEY4" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor keys need to be published. +# Since the lifetime of the keys are unlimited, so default to loadkeys +# interval. +check_next_key_event 3600 + +# Reconfig dnssec-policy (triggering algorithm roll). +echo_i "reconfig dnssec-policy to trigger algorithm rollover" +copy_setports ns6/named2.conf.in ns6/named.conf +rndc_reconfig ns6 10.53.0.6 + +# The NSEC record at the apex of the zone and its RRSIG records are +# added as part of the last step in signing a zone. We wait for the +# NSEC records to appear before proceeding with a counter to prevent +# infinite loops if there is a error. +# +n=$((n+1)) +echo_i "waiting for reconfig signing changes to take effect ($n)" +i=0 +while [ $i -lt 30 ] +do + ret=0 + while read -r zone + do + dig_with_opts "$zone" @10.53.0.6 nsec > "dig.out.ns6.test$n.$zone" || ret=1 + grep "NS SOA" "dig.out.ns6.test$n.$zone" > /dev/null || ret=1 + grep "$zone\..*IN.*RRSIG" "dig.out.ns6.test$n.$zone" > /dev/null || ret=1 + done < ns6/zones.2 + + i=$((i+1)) + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +next_key_event_threshold=$((next_key_event_threshold+i)) + +# +# Testing KSK/ZSK algorithm rollover. +# + +# +# Zone: step1.algorithm-roll.kasp +# +zone_properties "ns6" "step1.algorithm-roll.kasp" "ecdsa256" "3600" "4" "10.53.0.6" +# The RSAHSHA1 keys are outroducing. +key_properties "KEY1" "ksk" "0" "5" "RSASHA1" "2048" "no" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "hidden" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "0" "5" "RSASHA1" "2048" "yes" "no" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_states "KEY2" "hidden" "omnipresent" "omnipresent" "none" "none" +# The ECDSAP256SHA256 keys are introducing. +key_properties "KEY3" "ksk" "0" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_timings "KEY3" "published" "active" "none" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_properties "KEY4" "zsk" "0" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_timings "KEY4" "published" "active" "none" "none" "none" +key_states "KEY4" "omnipresent" "rumoured" "rumoured" "none" "none" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the ecdsa256 keys have been propagated. +# This is the DNSKEY TTL plus publish safety plus zone propagation delay: +# 3 times an hour: 10800 seconds. +check_next_key_event 10800 + +# +# Zone: step2.algorithm-roll.kasp +# +zone_properties "ns6" "step2.algorithm-roll.kasp" "ecdsa256" "3600" "4" "10.53.0.6" +# The RSAHSHA1 keys are outroducing, but need to stay present until the new +# algorithm chain of trust has been established. Thus the properties, timings +# and states of the KEY1 and KEY2 are the same as above. +# +# The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset is omnipresent, +# but the zone signatures are not. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "hidden" +key_states "KEY4" "omnipresent" "omnipresent" "rumoured" "none" "none" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when all zone signatures are signed with the new +# algorithm. This is the max-zone-ttl plus zone propagation delay +# plus retire safety: 6h + 1h + 2h. But three hours have already passed +# (the time it took to make the DNSKEY omnipresent), so the next event +# should be scheduled in 6 hour: 21600 seconds. +check_next_key_event 21600 + +# +# Zone: step3.algorithm-roll.kasp +# +zone_properties "ns6" "step3.algorithm-roll.kasp" "ecdsa256" "3600" "4" "10.53.0.6" +# The RSAHSHA1 keys are outroducing, and it is time to swap the DS. +key_states "KEY1" "hidden" "omnipresent" "none" "omnipresent" "unretentive" +# The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset and all signatures +# are now omnipresent, so the DS can be introduced. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "rumoured" +key_states "KEY4" "omnipresent" "omnipresent" "omnipresent" "none" "none" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DS becomes OMNIPRESENT. This happens after the +# parent registration delay, parent propagation delay, retire safety delay, +# and DS TTL: 24h + 1h + 2h + 2h = 29h = 104400 seconds. +check_next_key_event 104400 + +# +# Zone: step4.algorithm-roll.kasp +# +zone_properties "ns6" "step4.algorithm-roll.kasp" "ecdsa256" "3600" "4" "10.53.0.6" +# The old DS is HIDDEN, we can remove the old algorithm DNSKEY/RRSIG records. +key_properties "KEY1" "ksk" "0" "5" "RSASHA1" "2048" "no" "no" +key_states "KEY1" "hidden" "unretentive" "none" "unretentive" "hidden" +key_properties "KEY2" "zsk" "0" "5" "RSASHA1" "2048" "no" "no" +key_states "KEY2" "hidden" "unretentive" "unretentive" "none" "none" +# The ECDSAP256SHA256 DS is now OMNIPRESENT. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the old DNSKEY becomes HIDDEN. This happens after the +# DNSKEY TTL plus zone propagation delay (2h). +check_next_key_event 7200 + +# +# Zone: step5.algorithm-roll.kasp +# +zone_properties "ns6" "step5.algorithm-roll.kasp" "ecdsa256" "3600" "4" "10.53.0.6" +# The DNSKEY becomes HIDDEN. +key_states "KEY1" "hidden" "hidden" "none" "hidden" "hidden" +key_states "KEY2" "hidden" "hidden" "unretentive" "none" "none" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the RSASHA1 signatures become HIDDEN. This happens +# after the max-zone-ttl plus zone propagation delay plus retire safety +# (6h + 1h + 2h) minus the time already passed since the UNRETENTIVE state has +# been reached (2h): 9h - 2h = 7h = 25200 +check_next_key_event 25200 + +# +# Zone: step6.algorithm-roll.kasp +# +zone_properties "ns6" "step6.algorithm-roll.kasp" "ecdsa256" "3600" "4" "10.53.0.6" +# The zone signatures should now also be HIDDEN. +key_states "KEY2" "hidden" "hidden" "hidden" "none" "none" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is never since we established the policy and the keys have +# an unlimited lifetime. Fallback to the default loadkeys interval. +check_next_key_event 3600 + +# +# Testing CSK algorithm rollover. +# + +# +# Zone: step1.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step1.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "2" "10.53.0.6" +# The RSAHSHA1 key is outroducing. +key_properties "KEY1" "csk" "0" "5" "RSASHA1" "2048" "yes" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +# The ECDSAP256SHA256 key is introducing. +key_properties "KEY2" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY2" "published" "active" "none" "none" "none" +key_states "KEY2" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" +key_clear "KEY3" +key_clear "KEY4" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new key has been propagated. +# This is the DNSKEY TTL plus publish safety plus zone propagation delay: +# 3 times an hour: 10800 seconds. +check_next_key_event 10800 + +# +# Zone: step2.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step2.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "2" "10.53.0.6" +# The RSAHSHA1 key is outroducing, but need to stay present until the new +# algorithm chain of trust has been established. Thus the properties, timings +# and states of KEY1 is the same as above. +# +# The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset is omnipresent, +# but the zone signatures are not. +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "hidden" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when all zone signatures are signed with the new +# algorithm. This is the max-zone-ttl plus zone propagation delay +# plus retire safety: 6h + 1h + 2h. But three hours have already passed +# (the time it took to make the DNSKEY omnipresent), so the next event +# should be scheduled in 6 hour: 21600 seconds. +check_next_key_event 21600 + +# +# Zone: step3.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step3.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "2" "10.53.0.6" +# The RSAHSHA1 key is outroducing, and it is time to swap the DS. +key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "unretentive" +# The ECDSAP256SHA256 key is introducing. The DNSKEY RRset and all signatures +# are now omnipresent, so the DS can be introduced. +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "rumoured" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DS becomes OMNIPRESENT. This happens after the +# parent registration delay, parent propagation delay, retire safety delay, +# and DS TTL: 24h + 1h + 2h + 2h = 29h = 104400 seconds. +check_next_key_event 104400 + +# +# Zone: step4.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step4.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "2" "10.53.0.6" +# The old DS is HIDDEN, we can remove the old algorithm DNSKEY/RRSIG records. +key_properties "KEY1" "csk" "0" "5" "RSASHA1" "2048" "no" "no" +key_states "KEY1" "hidden" "unretentive" "unretentive" "unretentive" "hidden" +# The ECDSAP256SHA256 DS is now OMNIPRESENT. +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the old DNSKEY becomes HIDDEN. This happens after the +# DNSKEY TTL plus zone propagation delay (2h). +check_next_key_event 7200 + +# +# Zone: step5.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step5.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "2" "10.53.0.6" +# The DNSKEY becomes HIDDEN. +key_states "KEY1" "hidden" "hidden" "unretentive" "hidden" "hidden" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the RSASHA1 signatures become HIDDEN. This happens +# after the max-zone-ttl plus zone propagation delay plus retire safety +# (6h + 1h + 2h) minus the time already passed since the UNRETENTIVE state has +# been reached (2h): 9h - 2h = 7h = 25200 +check_next_key_event 25200 + +# +# Zone: step6.csk-algorithm-roll.kasp +# +zone_properties "ns6" "step6.csk-algorithm-roll.kasp" "csk-algoroll" "3600" "2" "10.53.0.6" +# The zone signatures should now also be HIDDEN. +key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is never since we established the policy and the keys have +# an unlimited lifetime. Fallback to the default loadkeys interval. +check_next_key_event 3600 + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c index 40d38ff835..8a54ea9771 100644 --- a/lib/dns/keymgr.c +++ b/lib/dns/keymgr.c @@ -546,8 +546,14 @@ keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, * chain of trust (can be this key). */ dnskey_omnipresent[DST_KEY_DS] = NA; - (void)dst_key_getstate(dkey->key, DST_KEY_DS, - &dnskey_omnipresent[DST_KEY_DS]); + if (next_state != NA && + dst_key_id(dkey->key) == dst_key_id(key->key)) { + /* Check next state rather than current state. */ + dnskey_omnipresent[DST_KEY_DS] = next_state; + } else { + (void)dst_key_getstate(dkey->key, DST_KEY_DS, + &dnskey_omnipresent[DST_KEY_DS]); + } if (!keymgr_key_exists_with_state( keyring, key, type, next_state, dnskey_omnipresent, na, false, match_algorithms)) @@ -993,14 +999,22 @@ keymgr_transition_time(dns_dnsseckey_t *key, int type, * TTLsig is the maximum TTL of all zone RRSIG * records. This translates to: * - * Dsgn + zone-propragation-delay + max-zone-ttl. + * Dsgn + zone-propagation-delay + max-zone-ttl. * * We will also add the retire-safety interval. */ - nexttime = lastchange + dns_kasp_signdelay(kasp) + - dns_kasp_zonemaxttl(kasp) + + nexttime = lastchange + dns_kasp_zonemaxttl(kasp) + dns_kasp_zonepropagationdelay(kasp) + dns_kasp_retiresafety(kasp); + /* + * Only add the sign delay Dsgn if there is an actual + * predecessor key. + */ + uint32_t pre; + if (dst_key_getnum(key->key, DST_NUM_PREDECESSOR, + &pre) == ISC_R_SUCCESS) { + nexttime += dns_kasp_signdelay(kasp); + } break; default: nexttime = now; @@ -1282,6 +1296,39 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, } } + /* Do we need to remove keys? */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + bool found_match = false; + + /* Make sure this key knows about roles. */ + keymgr_key_init_role(dkey); + + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { + found_match = true; + dst_key_format(dkey->key, keystr, + sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: DNSKEY %s (%s) matches " + "policy %s", + keystr, keymgr_keyrole(dkey->key), + dns_kasp_getname(kasp)); + break; + } + } + + /* No match, so retire unwanted retire key. */ + if (!found_match) { + keymgr_key_retire(dkey, now); + } + } + /* Create keys according to the policy, if come in short. */ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) @@ -1294,9 +1341,6 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) { - /* Make sure this key knows about roles. */ - keymgr_key_init_role(dkey); - if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { /* Found a match. */ diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 17cbdeff24..ff02549c78 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -19657,17 +19657,28 @@ zone_rekey(dns_zone_t *zone) { /* * If keymgr provided a next time, use the calculated next rekey time. */ - if (kasp != NULL && nexttime > 0) { + if (kasp != NULL) { isc_time_t timenext; + uint32_t nexttime_seconds; - DNS_ZONE_TIME_ADD(&timenow, nexttime - now, &timenext); + /* + * Set the key refresh timer to the next scheduled key event + * or to 'dnssec-loadkeys-interval' seconds in the future + * if no next key event is scheduled (nexttime == 0). + */ + if (nexttime > 0) { + nexttime_seconds = nexttime - now; + } else { + nexttime_seconds = zone->refreshkeyinterval; + } + + DNS_ZONE_TIME_ADD(&timenow, nexttime_seconds, &timenext); zone->refreshkeytime = timenext; zone_settimer(zone, &timenow); isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); dnssec_log(zone, ISC_LOG_DEBUG(3), - "next key event in %u seconds: %s", (nexttime - now), - timebuf); + "next key event in %u seconds", nexttime_seconds); dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); } /* diff --git a/util/copyrights b/util/copyrights index f5f9b4c02c..65f2e15d5a 100644 --- a/util/copyrights +++ b/util/copyrights @@ -697,6 +697,7 @@ ./bin/tests/system/kasp/ns3/setup.sh SH 2019,2020 ./bin/tests/system/kasp/ns4/setup.sh SH 2019,2020 ./bin/tests/system/kasp/ns5/setup.sh SH 2019,2020 +./bin/tests/system/kasp/ns6/setup.sh SH 2020 ./bin/tests/system/kasp/setup.sh SH 2019,2020 ./bin/tests/system/kasp/tests.sh SH 2019,2020 ./bin/tests/system/keepalive/clean.sh SH 2017,2018,2019,2020