From 53f02790b155b7637edce9006283fd89ea5240be Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Fri, 14 Mar 2025 17:11:14 +0100 Subject: [PATCH 1/6] Convert many kasp test cases to pytst Write python-based tests for the many test cases from the kasp system test. These test cases all follow the same pattern: - Wait until the zone is signed. - Check the keys from the key-directory against expected properties. - Set the expected key timings derived from when the key was created. - Check the key timing metadata against expected timings. - Check the 'rndc dnssec -status' output. - Check the apex is signed correctly. - Check a subdomain is signed correctly. - Verify that the zone is DNSSEC correct. Remove the counterparts for the newly added test from the kasp shell tests script. (cherry picked from commit 41481af1446ad3b0e319214b3ceee8805fd67e72) --- bin/tests/system/kasp/ns3/setup.sh | 8 +- bin/tests/system/kasp/tests.sh | 307 +--------------------------- bin/tests/system/kasp/tests_kasp.py | 194 +++++++++++++++++- 3 files changed, 200 insertions(+), 309 deletions(-) diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index 6985b6f7e2..76fe7b4a81 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -217,8 +217,12 @@ $SIGNER -PS -x -s now-2mo -e now-1mo -o $zone -O raw -f "${zonefile}.signed" $in # The DNSKEY's TTLs do not match the policy. setup dnskey-ttl-mismatch.autosign -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 $zsktimes $zone 2>keygen.out.$zone.2) +T="now-6mo" +keytimes="-P $T -A $T" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 $keytimes $zone 2>keygen.out.$zone.2) +$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK " >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" cp $infile $zonefile $SIGNER -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index ca294bab01..e80ea5aa86 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -400,92 +400,6 @@ set_keytimes_algorithm_policy() { set_addkeytime "KEY3" "REMOVED" "${retired}" 867900 } -# -# Zone: rsasha1.kasp. -# -if [ $RSASHA1_SUPPORTED = 1 ]; then - set_zone "rsasha1.kasp" - set_policy "rsasha1" "3" "1234" - set_server "ns3" "10.53.0.3" - # Key properties. - key_clear "KEY1" - set_keyrole "KEY1" "ksk" - set_keylifetime "KEY1" "315360000" - set_keyalgorithm "KEY1" "5" "RSASHA1" "2048" - set_keysigning "KEY1" "yes" - set_zonesigning "KEY1" "no" - - key_clear "KEY2" - set_keyrole "KEY2" "zsk" - set_keylifetime "KEY2" "157680000" - set_keyalgorithm "KEY2" "5" "RSASHA1" "2048" - set_keysigning "KEY2" "no" - set_zonesigning "KEY2" "yes" - - key_clear "KEY3" - set_keyrole "KEY3" "zsk" - set_keylifetime "KEY3" "31536000" - set_keyalgorithm "KEY3" "5" "RSASHA1" "2000" - set_keysigning "KEY3" "no" - set_zonesigning "KEY3" "yes" - - # KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. - # ZSK: DNSKEY, RRSIG (zsk) published. - set_keystate "KEY1" "GOAL" "omnipresent" - set_keystate "KEY1" "STATE_DNSKEY" "rumoured" - set_keystate "KEY1" "STATE_KRRSIG" "rumoured" - set_keystate "KEY1" "STATE_DS" "hidden" - - set_keystate "KEY2" "GOAL" "omnipresent" - set_keystate "KEY2" "STATE_DNSKEY" "rumoured" - set_keystate "KEY2" "STATE_ZRRSIG" "rumoured" - - set_keystate "KEY3" "GOAL" "omnipresent" - set_keystate "KEY3" "STATE_DNSKEY" "rumoured" - set_keystate "KEY3" "STATE_ZRRSIG" "rumoured" - # Three keys only. - key_clear "KEY4" - - check_keys - check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - set_keytimes_algorithm_policy - check_keytimes - check_apex - check_subdomain - dnssec_verify -fi - -# -# Zone: unlimited.kasp. -# -set_zone "unlimited.kasp" -set_policy "unlimited" "1" "1234" -set_server "ns3" "10.53.0.3" -key_clear "KEY1" -key_clear "KEY2" -key_clear "KEY3" -key_clear "KEY4" -# Key properties. -set_keyrole "KEY1" "csk" -set_keylifetime "KEY1" "0" -set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "yes" -# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "rumoured" -set_keystate "KEY1" "STATE_KRRSIG" "rumoured" -set_keystate "KEY1" "STATE_ZRRSIG" "rumoured" -set_keystate "KEY1" "STATE_DS" "hidden" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_csk_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - # # Zone: keystore.kasp. # @@ -535,14 +449,7 @@ check_apex check_subdomain dnssec_verify -# -# Zone: inherit.kasp. -# -set_zone "inherit.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" - -# Key properties. +# Key properties for tests below. key_clear "KEY1" set_keyrole "KEY1" "ksk" set_keylifetime "KEY1" "315360000" @@ -580,30 +487,6 @@ set_keystate "KEY3" "STATE_ZRRSIG" "rumoured" # Three keys only. key_clear "KEY4" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# -# Zone: dnssec-keygen.kasp. -# -set_zone "dnssec-keygen.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - # # Zone: some-keys.kasp. # @@ -710,152 +593,6 @@ status=$((status + ret)) # - configuring a zone with too many active keys (should trigger retire). # - configuring a zone with keys not matching the policy. -# -# Zone: rsasha1-nsec3.kasp. -# -if [ $RSASHA1_SUPPORTED = 1 ]; then - set_zone "rsasha1-nsec3.kasp" - set_policy "rsasha1-nsec3" "3" "1234" - set_server "ns3" "10.53.0.3" - # Key properties. - set_keyalgorithm "KEY1" "7" "NSEC3RSASHA1" "2048" - set_keyalgorithm "KEY2" "7" "NSEC3RSASHA1" "2048" - set_keyalgorithm "KEY3" "7" "NSEC3RSASHA1" "2000" - # Key timings and states same as above. - - check_keys - check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - set_keytimes_algorithm_policy - check_keytimes - check_apex - check_subdomain - dnssec_verify -fi - -# -# Zone: rsasha256.kasp. -# -set_zone "rsasha256.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties. -set_keyalgorithm "KEY1" "8" "RSASHA256" "2048" -set_keyalgorithm "KEY2" "8" "RSASHA256" "2048" -set_keyalgorithm "KEY3" "8" "RSASHA256" "3072" -# Key timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# -# Zone: rsasha512.kasp. -# -set_zone "rsasha512.kasp" -set_policy "rsasha512" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties. -set_keyalgorithm "KEY1" "10" "RSASHA512" "2048" -set_keyalgorithm "KEY2" "10" "RSASHA512" "2048" -set_keyalgorithm "KEY3" "10" "RSASHA512" "3072" -# Key timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# -# Zone: ecdsa256.kasp. -# -set_zone "ecdsa256.kasp" -set_policy "ecdsa256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties. -set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256" -set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256" -set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256" -# Key timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# -# Zone: ecdsa512.kasp. -# -set_zone "ecdsa384.kasp" -set_policy "ecdsa384" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties. -set_keyalgorithm "KEY1" "14" "ECDSAP384SHA384" "384" -set_keyalgorithm "KEY2" "14" "ECDSAP384SHA384" "384" -set_keyalgorithm "KEY3" "14" "ECDSAP384SHA384" "384" -# Key timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# -# Zone: ed25519.kasp. -# -if [ $ED25519_SUPPORTED = 1 ]; then - set_zone "ed25519.kasp" - set_policy "ed25519" "3" "1234" - set_server "ns3" "10.53.0.3" - # Key properties. - set_keyalgorithm "KEY1" "15" "ED25519" "256" - set_keyalgorithm "KEY2" "15" "ED25519" "256" - set_keyalgorithm "KEY3" "15" "ED25519" "256" - # Key timings and states same as above. - - check_keys - check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - set_keytimes_algorithm_policy - check_keytimes - check_apex - check_subdomain - dnssec_verify -fi - -# -# Zone: ed448.kasp. -# -if [ $ED448_SUPPORTED = 1 ]; then - set_zone "ed448.kasp" - set_policy "ed448" "3" "1234" - set_server "ns3" "10.53.0.3" - # Key properties. - set_keyalgorithm "KEY1" "16" "ED448" "456" - set_keyalgorithm "KEY2" "16" "ED448" "456" - set_keyalgorithm "KEY3" "16" "ED448" "456" - # Key timings and states same as above. - - check_keys - check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - set_keytimes_algorithm_policy - check_keytimes - check_apex - check_subdomain - dnssec_verify -fi - # Set key times for 'autosign' policy. set_keytimes_autosign_policy() { # The KSK was published six months ago (with settime). @@ -970,48 +707,6 @@ check_rrsig_refresh() { check_rrsig_refresh -# -# Zone: dnskey-ttl-mismatch.autosign -# -set_zone "dnskey-ttl-mismatch.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "63072000" -set_keyalgorithm "KEY1" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "31536000" -set_keyalgorithm "KEY2" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" - -# Both KSK and ZSK stay OMNIPRESENT. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY1" "STATE_KRRSIG" "omnipresent" -set_keystate "KEY1" "STATE_DS" "omnipresent" - -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent" -# Expect only two keys. -key_clear "KEY3" -key_clear "KEY4" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - # # Zone: fresh-sigs.autosign. # diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 7ffa22e740..33bfaba388 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -84,7 +84,7 @@ def check_all(server, zone, policy, ksks, zsks, tsig=None): isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy) isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig) isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig) - isctest.kasp.check_dnssec_verify(server, zone) + isctest.kasp.check_dnssec_verify(server, zone, tsig=tsig) def set_keytimes_default_policy(kp): @@ -103,6 +103,198 @@ def set_keytimes_default_policy(kp): kp.timing["ZRRSIGChange"] = kp.timing["Active"] +def test_kasp_cases(servers): + # Test many different configurations and expected keys and states after + # initial startup. + server = servers["ns3"] + keydir = server.identifier + alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] + size = os.environ["DEFAULT_BITS"] + + kasp_config = { + "dnskey-ttl": timedelta(seconds=1234), + "ds-ttl": timedelta(days=1), + "key-directory": keydir, + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), + } + + autosign_config = { + "dnskey-ttl": timedelta(seconds=300), + "ds-ttl": timedelta(days=1), + "key-directory": keydir, + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=7), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), + } + + lifetime = { + "P10Y": int(timedelta(days=10 * 365).total_seconds()), + "P5Y": int(timedelta(days=5 * 365).total_seconds()), + "P2Y": int(timedelta(days=2 * 365).total_seconds()), + "P1Y": int(timedelta(days=365).total_seconds()), + "P30D": int(timedelta(days=30).total_seconds()), + "P6M": int(timedelta(days=31 * 6).total_seconds()), + } + + autosign_properties = [ + f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P1Y']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + + def rsa1_properties(alg): + return [ + f"ksk {lifetime['P10Y']} {alg} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk {lifetime['P5Y']} {alg} 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"zsk {lifetime['P1Y']} {alg} 2000 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ] + + def fips_properties(alg, bits=None): + sizes = [2048, 2048, 3072] + if bits is not None: + sizes = [bits, bits, bits] + + return [ + f"ksk {lifetime['P10Y']} {alg} {sizes[0]} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk {lifetime['P5Y']} {alg} {sizes[1]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"zsk {lifetime['P1Y']} {alg} {sizes[2]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ] + + # Test case function. + def test_case(): + zone = test["zone"] + policy = test["policy"] + ttl = int(test["config"]["dnskey-ttl"].total_seconds()) + + isctest.log.info(f"check test case zone {zone} policy {policy}") + + # Key properties. + expected = isctest.kasp.policy_to_properties( + ttl=ttl, keys=test["key-properties"] + ) + # Key files. + keys = isctest.kasp.keydir_to_keylist(zone, test["config"]["key-directory"]) + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] + + isctest.kasp.check_zone_is_signed(server, zone) + isctest.kasp.check_keys(zone, keys, expected) + + offset = test["offset"] if "offset" in test else None + + for kp in expected: + kp.set_expected_keytimes(test["config"], offset=offset) + + isctest.kasp.check_keytimes(keys, expected) + + check_all(server, zone, policy, ksks, zsks) + + # Test cases. + rsa_cases = [] + if os.environ["RSASHA1_SUPPORTED"] == 1: + rsa_cases = [ + { + "zone": "rsasha1.kasp", + "policy": "rsasha1", + "config": kasp_config, + "key-properties": rsa1_properties(5), + }, + { + "zone": "rsasha1-nsec3.kasp", + "policy": "rsasha1", + "config": kasp_config, + "key-properties": rsa1_properties(7), + }, + ] + + fips_cases = [ + { + "zone": "dnskey-ttl-mismatch.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + }, + { + "zone": "dnssec-keygen.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + }, + { + "zone": "ecdsa256.kasp", + "policy": "ecdsa256", + "config": kasp_config, + "key-properties": fips_properties(13, bits=256), + }, + { + "zone": "ecdsa384.kasp", + "policy": "ecdsa384", + "config": kasp_config, + "key-properties": fips_properties(14, bits=384), + }, + { + "zone": "inherit.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + }, + { + "zone": "rsasha256.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + }, + { + "zone": "rsasha512.kasp", + "policy": "rsasha512", + "config": kasp_config, + "key-properties": fips_properties(10), + }, + { + "zone": "unlimited.kasp", + "policy": "unlimited", + "config": kasp_config, + "key-properties": [ + f"csk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + ] + + if os.environ["ED25519_SUPPORTED"] == 1: + fips_cases.append( + { + "zone": "ed25519.kasp", + "policy": "ed25519", + "config": kasp_config, + "key-properties": fips_properties(15, bits=256), + } + ) + + if os.environ["ED448_SUPPORTED"] == 1: + fips_cases.append( + { + "zone": "ed448.kasp", + "policy": "ed448", + "config": kasp_config, + "key-properties": fips_properties(16, bits=456), + } + ) + + test_cases = rsa_cases + fips_cases + for test in test_cases: + test_case() + + def test_kasp_default(servers): server = servers["ns3"] From 6f98c8e10b39cb80b0ecbfbfea7fd09c0825a3ee Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Fri, 14 Mar 2025 17:28:28 +0100 Subject: [PATCH 2/6] Two more kasp test cases converted to pytest The zone 'pregenerated.kasp' is a case where there already exist more keys than required. For this we set the 'pregenerated' setting. This will change the 'keydir_to_keylist' function behavior: Only keys in use are considered. A key is in use if all of the states are either undefined, or set to 'hidden'. The 'some-keys.kasp' zone is similar to 'pregenerated.kasp', except only some keys have been pregenerated. (cherry picked from commit 43ded45ae9af1b5ad93a68444ac289574ae703a2) --- bin/tests/system/kasp/tests.sh | 34 ----------------------------- bin/tests/system/kasp/tests_kasp.py | 25 +++++++++++++++++++-- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index e80ea5aa86..8c6768b428 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -487,40 +487,6 @@ set_keystate "KEY3" "STATE_ZRRSIG" "rumoured" # Three keys only. key_clear "KEY4" -# -# Zone: some-keys.kasp. -# -set_zone "some-keys.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy "pregenerated" -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# -# Zone: pregenerated.kasp. -# -# There are more pregenerated keys than needed, hence the number of keys is -# six, not three. -set_zone "pregenerated.kasp" -set_policy "rsasha256" "6" "1234" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy "pregenerated" -check_keytimes -check_apex -check_subdomain -dnssec_verify - # # Zone: rumoured.kasp. # diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 33bfaba388..0f93a690ee 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -174,6 +174,9 @@ def test_kasp_cases(servers): zone = test["zone"] policy = test["policy"] ttl = int(test["config"]["dnskey-ttl"].total_seconds()) + pregenerated = False + if test.get("pregenerated"): + pregenerated = test["pregenerated"] isctest.log.info(f"check test case zone {zone} policy {policy}") @@ -182,7 +185,9 @@ def test_kasp_cases(servers): ttl=ttl, keys=test["key-properties"] ) # Key files. - keys = isctest.kasp.keydir_to_keylist(zone, test["config"]["key-directory"]) + keys = isctest.kasp.keydir_to_keylist( + zone, test["config"]["key-directory"], in_use=pregenerated + ) ksks = [k for k in keys if k.is_ksk()] zsks = [k for k in keys if not k.is_ksk()] @@ -192,7 +197,9 @@ def test_kasp_cases(servers): offset = test["offset"] if "offset" in test else None for kp in expected: - kp.set_expected_keytimes(test["config"], offset=offset) + kp.set_expected_keytimes( + test["config"], offset=offset, pregenerated=pregenerated + ) isctest.kasp.check_keytimes(keys, expected) @@ -248,6 +255,13 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(8), }, + { + "zone": "pregenerated.kasp", + "policy": "rsasha256", + "config": kasp_config, + "pregenerated": True, + "key-properties": fips_properties(8), + }, { "zone": "rsasha256.kasp", "policy": "rsasha256", @@ -260,6 +274,13 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(10), }, + { + "zone": "some-keys.kasp", + "policy": "rsasha256", + "config": kasp_config, + "pregenerated": True, + "key-properties": fips_properties(8), + }, { "zone": "unlimited.kasp", "policy": "unlimited", From 76edf2deb6c6704024587d352e57ffbc6c627d58 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 17 Mar 2025 11:52:18 +0100 Subject: [PATCH 3/6] Update kasp check_signatures for dnssec-policy The check_signatures code was initially created to be suitable for the ksr system test, to test the Offline KSK feature. For that, a key is expected to be signing if the current time is between the timing metadata Active and Retired. With dnssec-policy, the key timing metadata is indicative, the key states determine the actual signing behavior. Update the check_signatures function so that by default the signing is derived from the key states (ksigning and zsigning). Add an argument 'offline_ksk', if set the make sure that the zsigning is set if the current time is between the Active and Retired timing metadata, and for ksigning we just use the timing metadata (as the key is offline, we cannot check the key states). Another (upcoming) test case is where key files are missing. When the ZSK private key file is missing, the KSK takes over. Add an argument 'zsk_missing', when set to True the expected zone signing (zsigning) is reversed. (cherry picked from commit fddf9f778b49f454e68cbefea8c897ac3bd0ea44) --- bin/tests/system/isctest/kasp.py | 114 +++++++++++++++++++++++------- bin/tests/system/ksr/tests_ksr.py | 24 +++---- 2 files changed, 100 insertions(+), 38 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 0ea1c774ea..5b39b9fffd 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -332,6 +332,52 @@ class Key: ) return value + def get_signing_state(self, offline_ksk=False, zsk_missing=False) -> (bool, bool): + """ + This returns the signing state derived from the key states, KRRSIGState + and ZRRSIGState. + + If 'offline_ksk' is set to True, we determine the signing state from + the timing metadata. If 'zsigning' is True, ensure the current time is + between the Active and Retired timing metadata. + + If 'zsk_missing' is set to True, it means the ZSK private key file is + missing, and the KSK should take over signing the RRset, and the + expected zone signing state (zsigning) is reversed. + """ + # Fetch key timing metadata. + now = KeyTimingMetadata.now() + activate = self.get_timing("Activate") + inactive = self.get_timing("Inactive", must_exist=False) + + active = now >= activate + retired = inactive is not None and inactive <= now + signing = active and not retired + + # Fetch key state metadata. + krrsigstate = self.get_metadata("KRRSIGState", must_exist=False) + ksigning = krrsigstate in ["rumoured", "omnipresent"] + zrrsigstate = self.get_metadata("ZRRSIGState", must_exist=False) + zsigning = zrrsigstate in ["rumoured", "omnipresent"] + + if ksigning: + assert self.is_ksk() + if zsigning: + assert self.is_zsk() + + # If the ZSK private key file is missing, revers the zone signing state. + if zsk_missing: + zsigning = not zsigning + + # If testing offline KSK, retrieve the signing state from the key timing + # metadata. + if offline_ksk and signing and self.is_zsk(): + assert zsigning + if offline_ksk and signing and self.is_ksk(): + ksigning = signing + + return ksigning, zsigning + def ttl(self) -> int: with open(self.keyfile, "r", encoding="utf-8") as file: for line in file: @@ -772,8 +818,9 @@ def check_dnssecstatus(server, zone, keys, policy=None, view=None): assert f"key: {key.tag}" in response -def _check_signatures(signatures, covers, fqdn, keys): - now = KeyTimingMetadata.now() +def _check_signatures( + signatures, covers, fqdn, keys, offline_ksk=False, zsk_missing=False +): numsigs = 0 zrrsig = True if covers in [dns.rdatatype.DNSKEY, dns.rdatatype.CDNSKEY, dns.rdatatype.CDS]: @@ -781,23 +828,16 @@ def _check_signatures(signatures, covers, fqdn, keys): krrsig = not zrrsig for key in keys: - activate = key.get_timing("Activate") - inactive = key.get_timing("Inactive", must_exist=False) + ksigning, zsigning = key.get_signing_state( + offline_ksk=offline_ksk, zsk_missing=zsk_missing + ) - active = now >= activate - retired = inactive is not None and inactive <= now - signing = active and not retired alg = key.get_metadata("Algorithm") rtype = dns.rdatatype.to_text(covers) expect = rf"IN RRSIG {rtype} {alg} (\d) (\d+) (\d+) (\d+) {key.tag} {fqdn}" - if not signing: - for rrsig in signatures: - assert re.search(expect, rrsig) is None - continue - - if zrrsig and key.is_zsk(): + if zrrsig and zsigning: has_rrsig = False for rrsig in signatures: if re.search(expect, rrsig) is not None: @@ -806,11 +846,11 @@ def _check_signatures(signatures, covers, fqdn, keys): assert has_rrsig, f"Expected signature but not found: {expect}" numsigs += 1 - if zrrsig and not key.is_zsk(): + if zrrsig and not zsigning: for rrsig in signatures: assert re.search(expect, rrsig) is None - if krrsig and key.is_ksk(): + if krrsig and ksigning: has_rrsig = False for rrsig in signatures: if re.search(expect, rrsig) is not None: @@ -819,14 +859,16 @@ def _check_signatures(signatures, covers, fqdn, keys): assert has_rrsig, f"Expected signature but not found: {expect}" numsigs += 1 - if krrsig and not key.is_ksk(): + if krrsig and not ksigning: for rrsig in signatures: assert re.search(expect, rrsig) is None return numsigs -def check_signatures(rrset, covers, fqdn, ksks, zsks): +def check_signatures( + rrset, covers, fqdn, ksks, zsks, offline_ksk=False, zsk_missing=False +): # Check if signatures with covering type are signed with the right keys. # The right keys are the ones that expect a signature and have the # correct role. @@ -840,8 +882,12 @@ def check_signatures(rrset, covers, fqdn, ksks, zsks): rrsig = f"{rr.name} {rr.ttl} {rdclass} {rdtype} {rdata}" signatures.append(rrsig) - numsigs += _check_signatures(signatures, covers, fqdn, ksks) - numsigs += _check_signatures(signatures, covers, fqdn, zsks) + numsigs += _check_signatures( + signatures, covers, fqdn, ksks, offline_ksk=offline_ksk, zsk_missing=zsk_missing + ) + numsigs += _check_signatures( + signatures, covers, fqdn, zsks, offline_ksk=offline_ksk, zsk_missing=zsk_missing + ) assert numsigs == len(signatures) @@ -965,7 +1011,9 @@ def _query_rrset(server, fqdn, qtype, tsig=None): return rrs, rrsigs -def check_apex(server, zone, ksks, zsks, tsig=None): +def check_apex( + server, zone, ksks, zsks, offline_ksk=False, zsk_missing=False, tsig=None +): # Test the apex of a zone. This checks that the SOA and DNSKEY RRsets # are signed correctly and with the appropriate keys. fqdn = f"{zone}." @@ -973,30 +1021,44 @@ def check_apex(server, zone, ksks, zsks, tsig=None): # test dnskey query dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY, tsig=tsig) check_dnskeys(dnskeys, ksks, zsks) - check_signatures(rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks) + check_signatures( + rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks, offline_ksk=offline_ksk + ) # test soa query soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA, tsig=tsig) assert len(soa) == 1 assert f"{zone}. {DEFAULT_TTL} IN SOA" in soa[0].to_text() - check_signatures(rrsigs, dns.rdatatype.SOA, fqdn, ksks, zsks) + check_signatures( + rrsigs, + dns.rdatatype.SOA, + fqdn, + ksks, + zsks, + offline_ksk=offline_ksk, + zsk_missing=zsk_missing, + ) # test cdnskey query cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY, tsig=tsig) check_dnskeys(cdnskeys, ksks, zsks, cdnskey=True) if len(cdnskeys) > 0: assert len(rrsigs) > 0 - check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks) + check_signatures( + rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks, offline_ksk=offline_ksk + ) # test cds query cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS, tsig=tsig) check_cds(cds, ksks) if len(cds) > 0: assert len(rrsigs) > 0 - check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks) + check_signatures( + rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks, offline_ksk=offline_ksk + ) -def check_subdomain(server, zone, ksks, zsks, tsig=None): +def check_subdomain(server, zone, ksks, zsks, offline_ksk=False, tsig=None): # Test an RRset below the apex and verify it is signed correctly. fqdn = f"{zone}." qname = f"a.{zone}." @@ -1014,7 +1076,7 @@ def check_subdomain(server, zone, ksks, zsks, tsig=None): else: assert match in rrset.to_text() - check_signatures(rrsigs, qtype, fqdn, ksks, zsks) + check_signatures(rrsigs, qtype, fqdn, ksks, zsks, offline_ksk=offline_ksk) def verify_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None): diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py index 5512f34fa2..ae020086f9 100644 --- a/bin/tests/system/ksr/tests_ksr.py +++ b/bin/tests/system/ksr/tests_ksr.py @@ -673,9 +673,9 @@ def test_ksr_common(servers): # - check keys check_keys(overlapping_zsks, lifetime, with_state=True) # - check apex - isctest.kasp.check_apex(ns1, zone, ksks, overlapping_zsks) + isctest.kasp.check_apex(ns1, zone, ksks, overlapping_zsks, offline_ksk=True) # - check subdomain - isctest.kasp.check_subdomain(ns1, zone, ksks, overlapping_zsks) + isctest.kasp.check_subdomain(ns1, zone, ksks, overlapping_zsks, offline_ksk=True) def test_ksr_lastbundle(servers): @@ -748,9 +748,9 @@ def test_ksr_lastbundle(servers): # - check keys check_keys(zsks, lifetime, offset=offset, with_state=True) # - check apex - isctest.kasp.check_apex(ns1, zone, ksks, zsks) + isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain - isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) # check that last bundle warning is logged warning = "last bundle in skr, please import new skr file" @@ -828,9 +828,9 @@ def test_ksr_inthemiddle(servers): # - check keys check_keys(zsks, lifetime, offset=offset, with_state=True) # - check apex - isctest.kasp.check_apex(ns1, zone, ksks, zsks) + isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain - isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) # check that no last bundle warning is logged warning = "last bundle in skr, please import new skr file" @@ -1023,9 +1023,9 @@ def test_ksr_unlimited(servers): # - check keys check_keys(zsks, lifetime, with_state=True) # - check apex - isctest.kasp.check_apex(ns1, zone, ksks, zsks) + isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain - isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) def test_ksr_twotone(servers): @@ -1141,9 +1141,9 @@ def test_ksr_twotone(servers): lifetime = timedelta(days=31 * 5) check_keys(zsks_altalg, lifetime, alg, size, with_state=True) # - check apex - isctest.kasp.check_apex(ns1, zone, ksks, zsks) + isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain - isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) def test_ksr_kskroll(servers): @@ -1215,6 +1215,6 @@ def test_ksr_kskroll(servers): # - check keys check_keys(zsks, None, with_state=True) # - check apex - isctest.kasp.check_apex(ns1, zone, ksks, zsks) + isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True) # - check subdomain - isctest.kasp.check_subdomain(ns1, zone, ksks, zsks) + isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True) From a996e18417f1b51f25f728888534aa5c46f18be8 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 17 Mar 2025 14:53:55 +0100 Subject: [PATCH 4/6] Convert more kasp test cases to pytest These test cases follow the same pattern as many other, but all require some additional checks. These are set in "additional-tests". The "zsk-missing.autosign" zone is special handled, as it expects the KSK to sign the SOA RRset (because the ZSK is unavailable). The kasp/ns3/setup.sh script is updated so the SyncPublish is not set (named will initialize it correctly). For the test zones that have missing private key files we do need to set the expected key timing metadata. Remove the counterparts for the newly added test from the kasp shell tests script. (cherry picked from commit 5f23f750c24ea734e52798276bbeb270cec2aed2) --- bin/tests/system/isctest/kasp.py | 95 +++++++ bin/tests/system/kasp/ns3/setup.sh | 54 ++-- bin/tests/system/kasp/tests.sh | 382 +--------------------------- bin/tests/system/kasp/tests_kasp.py | 242 +++++++++++++++++- 4 files changed, 361 insertions(+), 412 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 5b39b9fffd..fc00a4df0c 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -1108,6 +1108,98 @@ def verify_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig= return True +def verify_rrsig_is_refreshed( + server, fqdn, zonefile, qname, qtype, ksks, zsks, tsig=None +): + """ + Verify signature for RRset has been refreshed. + """ + response = _query(server, qname, qtype, tsig=tsig) + + if response.rcode() != dns.rcode.NOERROR: + return False + + rrtype = dns.rdatatype.to_text(qtype) + match = f"{qname}. {DEFAULT_TTL} IN {rrtype}" + rrsigs = [] + for rrset in response.answer: + if rrset.match( + dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype + ): + rrsigs.append(rrset) + elif not match in rrset.to_text(): + return False + + if len(rrsigs) == 0: + return False + + tmp_zonefile = f"{zonefile}.tmp" + isctest.run.cmd( + [ + os.environ["CHECKZONE"], + "-D", + "-q", + "-o", + tmp_zonefile, + "-f", + "raw", + fqdn, + zonefile, + ], + ) + + zone = dns.zone.from_file(tmp_zonefile, fqdn) + for rrsig in rrsigs: + if isctest.util.zone_contains(zone, rrsig): + return False + + # Zone is updated, ready to verify the signatures. + check_signatures(rrsigs, qtype, fqdn, ksks, zsks) + + return True + + +def verify_rrsig_is_reused(server, fqdn, zonefile, qname, qtype, ksks, zsks, tsig=None): + """ + Verify signature for RRset has been reused. + """ + response = _query(server, qname, qtype, tsig=tsig) + + assert response.rcode() == dns.rcode.NOERROR + + rrtype = dns.rdatatype.to_text(qtype) + match = f"{qname}. {DEFAULT_TTL} IN {rrtype}" + rrsigs = [] + for rrset in response.answer: + if rrset.match( + dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype + ): + rrsigs.append(rrset) + else: + assert match in rrset.to_text() + + tmp_zonefile = f"{zonefile}.tmp" + isctest.run.cmd( + [ + os.environ["CHECKZONE"], + "-D", + "-q", + "-o", + tmp_zonefile, + "-f", + "raw", + fqdn, + zonefile, + ], + ) + + zone = dns.zone.from_file(tmp_zonefile, dns.name.from_text(fqdn), relativize=False) + for rrsig in rrsigs: + assert isctest.util.zone_contains(zone, rrsig) + + check_signatures(rrsigs, qtype, fqdn, ksks, zsks) + + def next_key_event_equals(server, zone, next_event): if next_event is None: # No next key event check. @@ -1202,6 +1294,7 @@ def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]: Then, optional data for specific tests may follow: - "goal", "dnskey", "krrsig", "zrrsig", "ds", followed by a value, sets the given state to the specific value + - "missing", set if the private key file for this key is not available. - "offset", an offset for testing key rollover timings """ proplist = [] @@ -1252,6 +1345,8 @@ def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]: elif line[i].startswith("offset:"): keyval = line[i].split(":") keyprop.properties["offset"] = timedelta(seconds=int(keyval[1])) + elif line[i] == "missing": + keyprop.properties["private"] = False else: assert False, f"undefined optional data {line[i]}" diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index 76fe7b4a81..b6062dad22 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -200,13 +200,14 @@ $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" $SIGNER -PS -z -x -s now-2w -e now-1mi -o $zone -f "${zonefile}" $infile >signer.out.$zone.1 2>&1 +# Treat the next zones as if they were signed six months ago. +T="now-6mo" +keytimes="-P $T -A $T" + # These signatures are set to expire long in the past, update immediately. setup expired-sigs.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -217,8 +218,6 @@ $SIGNER -PS -x -s now-2mo -e now-1mo -o $zone -O raw -f "${zonefile}.signed" $in # The DNSKEY's TTLs do not match the policy. setup dnskey-ttl-mismatch.autosign -T="now-6mo" -keytimes="-P $T -A $T" KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 -f KSK $keytimes $zone 2>keygen.out.$zone.1) ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 @@ -229,11 +228,8 @@ $SIGNER -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone # These signatures are still good, and can be reused. setup fresh-sigs.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -244,11 +240,8 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil # These signatures are still good, but not fresh enough, update immediately. setup unfresh-sigs.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -259,11 +252,13 @@ $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infil # These signatures are still good, but the private KSK is missing. setup ksk-missing.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" +# KSK file will be gone missing, so we set expected times during setup. +TI="now+550d" # Lifetime of 2 years minus 6 months equals 550 days +TD="now+13226h" # 550 days plus retire time of 1 day 2 hours equals 13226 hours +TS="now-257755mi" # 6 months minus 1 day, 5 minutes equals 257695 minutes +ksktimes="$keytimes -P sync $TS -I $TI -D $TD" KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -278,10 +273,11 @@ rm -f "${KSK}".private # These signatures are still good, but the private ZSK is missing. setup zsk-missing.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +# ZSK file will be gone missing, so we set expected times during setup. +TI="now+185d" # Lifetime of 1 year minus 6 months equals 185 days +TD="now+277985mi" # 185 days plus retire time (sign delay, retire safety, propagation, zone TTL) +zsktimes="$keytimes -I $TI -D $TD" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 @@ -298,11 +294,8 @@ rm -f "${ZSK}".private # These signatures are still good, but the key files will be removed # before a second run of reconfiguring keys. setup keyfiles-missing.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -313,7 +306,6 @@ $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infil # These signatures are already expired, and the private ZSK is retired. setup zsk-retired.autosign -T="now-6mo" ksktimes="-P $T -A $T -P sync $T" zsktimes="-P $T -A $T -I now" KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 8c6768b428..ad2b49fb0d 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -514,47 +514,6 @@ check_apex check_subdomain dnssec_verify -# -# Zone: secondary.kasp. -# -set_zone "secondary.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Update zone. -n=$((n + 1)) -echo_i "check that we correctly sign the zone after IXFR for zone ${ZONE} ($n)" -ret=0 -cp ns2/secondary.kasp.db.in2 ns2/secondary.kasp.db -rndccmd 10.53.0.2 reload "$ZONE" >/dev/null || log_error "rndc reload zone ${ZONE} failed" - -_wait_for_done_subdomains() { - ret=0 - dig_with_opts "a.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.a" || return 1 - grep "status: NOERROR" "dig.out.$DIR.test$n.a" >/dev/null || return 1 - grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" "dig.out.$DIR.test$n.a" >/dev/null || return 1 - check_signatures $_qtype "dig.out.$DIR.test$n.a" "ZSK" - if [ $ret -gt 0 ]; then return $ret; fi - - dig_with_opts "d.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.d" || return 1 - grep "status: NOERROR" "dig.out.$DIR.test$n.d" >/dev/null || return 1 - grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" "dig.out.$DIR.test$n.d" >/dev/null || return 1 - check_signatures $_qtype "dig.out.$DIR.test$n.d" "ZSK" - return $ret -} -retry_quiet 5 _wait_for_done_subdomains || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - # TODO: we might want to test: # - configuring a zone with too many active keys (should trigger retire). # - configuring a zone with keys not matching the policy. @@ -593,10 +552,10 @@ set_keytimes_autosign_policy() { } # -# Zone: expired-sigs.autosign. +# Zone: zsk-retired.autosign. # -set_zone "expired-sigs.autosign" -set_policy "autosign" "2" "300" +set_zone "zsk-retired.autosign" +set_policy "autosign" "3" "300" set_server "ns3" "10.53.0.3" # Key properties. key_clear "KEY1" @@ -625,181 +584,6 @@ set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent" # Expect only two keys. key_clear "KEY3" key_clear "KEY4" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Verify all signatures have been refreshed. -check_rrsig_refresh() { - # Apex. - _qtypes="DNSKEY SOA NS NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_qtype} rrsig is refreshed 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}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - # If this exact RRSIG is also in the zone file it is not refreshed. - _rrsig=$(cat "rrsig.out.$ZONE.$_qtype") - grep "${_rrsig}" "${DIR}/${ZONE}.db" >/dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - - # Below apex. - _labels="a b c ns3" - for _label in $_labels; do - _qtypes="A NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_label} ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" - ret=0 - dig_with_opts "${_label}.${ZONE}" "@${SERVER}" "$_qtype" >"dig.out.$DIR.test$n" || log_error "dig ${_label}.${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - _rrsig=$(cat "rrsig.out.$ZONE.$_qtype") - grep "${_rrsig}" "${DIR}/${ZONE}.db" >/dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - done -} - -check_rrsig_refresh - -# -# Zone: fresh-sigs.autosign. -# -set_zone "fresh-sigs.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Verify signature reuse. -check_rrsig_reuse() { - # Apex. - _qtypes="NS NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_qtype} rrsig is reused 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}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - # If this exact RRSIG is also in the signed zone file it is not refreshed. - _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' <"rrsig.out.$ZONE.$_qtype") - $CHECKZONE -f raw -F text -s full -o zone.out.${ZONE}.test$n "${ZONE}" "${DIR}/${ZONE}.db.signed" >/dev/null - grep "${_rrsig}" zone.out.${ZONE}.test$n >/dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - - # Below apex. - _labels="a b c ns3" - for _label in $_labels; do - _qtypes="A NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_label} ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" - ret=0 - dig_with_opts "${_label}.${ZONE}" "@${SERVER}" "$_qtype" >"dig.out.$DIR.test$n" || log_error "dig ${_label}.${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - # If this exact RRSIG is also in the signed zone file it is not refreshed. - _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' <"rrsig.out.$ZONE.$_qtype") - $CHECKZONE -f raw -F text -s full -o zone.out.${ZONE}.test$n "${ZONE}" "${DIR}/${ZONE}.db.signed" >/dev/null - grep "${_rrsig}" zone.out.${ZONE}.test$n >/dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - done -} - -check_rrsig_reuse - -# -# Zone: unfresh-sigs.autosign. -# -set_zone "unfresh-sigs.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify -check_rrsig_refresh - -# -# Zone: ksk-missing.autosign. -# -set_zone "ksk-missing.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. -# Skip checking the private file, because it is missing. -key_set "KEY1" "PRIVATE" "no" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -check_apex -check_subdomain -dnssec_verify - -# Restore the PRIVATE variable. -key_set "KEY1" "PRIVATE" "yes" - -# -# Zone: zsk-missing.autosign. -# -set_zone "zsk-missing.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. -# Skip checking the private file, because it is missing. -key_set "KEY2" "PRIVATE" "no" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -# For the apex, we expect the SOA to be signed with the KSK because the ZSK is -# offline. Temporary treat KEY1 as a zone signing key too. -set_keyrole "KEY1" "csk" -set_zonesigning "KEY1" "yes" -set_zonesigning "KEY2" "no" -check_apex -set_keyrole "KEY1" "ksk" -set_zonesigning "KEY1" "no" -set_zonesigning "KEY2" "yes" -check_subdomain -dnssec_verify - -# Restore the PRIVATE variable. -key_set "KEY2" "PRIVATE" "yes" - -# -# Zone: zsk-retired.autosign. -# -set_zone "zsk-retired.autosign" -set_policy "autosign" "3" "300" -set_server "ns3" "10.53.0.3" # The third key is not yet expected to be signing. set_keyrole "KEY3" "zsk" set_keylifetime "KEY3" "31536000" @@ -846,7 +630,7 @@ check_keytimes check_apex check_subdomain dnssec_verify -check_rrsig_refresh +#check_rrsig_refresh # Load again, make sure the purged key is not an issue when verifying keys. echo_i "load keys for $ZONE, making sure a recently purged key is not an issue when verifying keys ($n)" @@ -857,164 +641,6 @@ grep "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) -# -# Zone: legacy-keys.kasp. -# -set_zone "legacy-keys.kasp" -# This zone has two active keys and two old keys left in key directory, so -# expect 4 key files. -set_policy "migrate-to-dnssec-policy" "4" "1234" -set_server "ns3" "10.53.0.3" - -# Key properties. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "16070400" -set_keyalgorithm "KEY1" "8" "RSASHA256" "2048" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "16070400" -set_keyalgorithm "KEY2" "8" "RSASHA256" "2048" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" -# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. -# ZSK: DNSKEY, RRSIG (zsk) published. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "rumoured" -set_keystate "KEY1" "STATE_KRRSIG" "rumoured" -set_keystate "KEY1" "STATE_DS" "hidden" - -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "rumoured" -set_keystate "KEY2" "STATE_ZRRSIG" "rumoured" -# Two keys only. -key_clear "KEY3" -key_clear "KEY4" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - -# Make sure the correct legacy keys were used (and not the removed predecessor -# keys). -n=$((n + 1)) -echo_i "check correct keys were used when migrating zone ${ZONE} to dnssec-policy ($n)" -ret=0 -kskfile=$(cat ns3/legacy-keys.kasp.ksk) -basefile=$(key_get KEY1 BASEFILE) -echo_i "filename: $basefile (expect $kskfile)" -test "$DIR/$kskfile" = "$basefile" || ret=1 -zskfile=$(cat ns3/legacy-keys.kasp.zsk) -basefile=$(key_get KEY2 BASEFILE) -echo_i "filename: $basefile (expect $zskfile)" -test "$DIR/$zskfile" = "$basefile" || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# KSK times. -created=$(key_get KEY1 CREATED) -keyfile=$(key_get KEY1 BASEFILE) -grep "; Publish:" "${keyfile}.key" >published.test${n}.key1 -published=$(awk '{print $3}' published.test${n}.key2 -published=$(awk '{print $3}' /dev/null || log_error "rndc loadkeys zone ${ZONE} failed" -wait_for_log 3 "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" $DIR/named.run || ret=1 -# Check keys again, make sure no new keys are created. -set_policy "autosign" "0" "300" -key_clear "KEY1" -key_clear "KEY2" -check_keys -# Zone is still signed correctly. -dnssec_verify - # # Test dnssec-policy inheritance. # diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 0f93a690ee..eba8e73277 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -80,9 +80,11 @@ pytestmark = pytest.mark.extra_artifacts( ) -def check_all(server, zone, policy, ksks, zsks, tsig=None): +def check_all(server, zone, policy, ksks, zsks, zsk_missing=False, tsig=None): isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy) - isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig) + isctest.kasp.check_apex( + server, zone, ksks, zsks, zsk_missing=zsk_missing, tsig=tsig + ) isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig) isctest.kasp.check_dnssec_verify(server, zone, tsig=tsig) @@ -169,6 +171,128 @@ def test_kasp_cases(servers): f"zsk {lifetime['P1Y']} {alg} {sizes[2]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ] + # Additional test functions. + def test_ixfr_is_signed( + expected_updates, zone=None, policy=None, ksks=None, zsks=None + ): + isctest.log.info(f"check that the zone {zone} is correctly signed after ixfr") + isctest.log.debug( + f"expected updates {expected_updates} policy {policy} ksks {ksks} zsks {zsks}" + ) + + shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db") + servers["ns2"].rndc(f"reload {zone}", log=False) + + def update_is_signed(): + parts = update.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + rdata = parts[2] + return isctest.kasp.verify_update_is_signed( + server, zone, qname, qtype, rdata, ksks, zsks + ) + + for update in expected_updates: + isctest.run.retry_with_timeout(update_is_signed, timeout=5) + + def test_rrsig_refresh(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info(f"check that the zone {zone} refreshes expired signatures") + + def rrsig_is_refreshed(): + parts = query.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + return isctest.kasp.check_rrsig_is_refreshed( + server, zone, f"ns3/{zone}.db.signed", qname, qtype, ksks, zsks + ) + + queries = [ + f"{zone} DNSKEY", + f"{zone} SOA", + f"{zone} NS", + f"{zone} NSEC", + f"a.{zone} A", + f"a.{zone} NSEC", + f"b.{zone} A", + f"b.{zone} NSEC", + f"c.{zone} A", + f"c.{zone} NSEC", + f"ns3.{zone} A", + f"ns3.{zone} NSEC", + ] + + for query in queries: + isctest.run.retry_with_timeout(rrsig_is_refreshed, timeout=5) + + def test_rrsig_reuse(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info(f"check that the zone {zone} reuses fresh signatures") + + def rrsig_is_reused(): + parts = query.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + return isctest.kasp.check_rrsig_is_reused( + server, zone, f"{keydir}/{zone}.db.signed", qname, qtype, ksks, zsks + ) + + queries = [ + f"{zone} NS", + f"{zone} NSEC", + f"a.{zone} A", + f"a.{zone} NSEC", + f"b.{zone} A", + f"b.{zone} NSEC", + f"c.{zone} A", + f"c.{zone} NSEC", + f"ns3.{zone} A", + f"ns3.{zone} NSEC", + ] + + for query in queries: + rrsig_is_reused() + + def test_legacy_keys(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info(f"check that the zone {zone} uses correct legacy keys") + + assert len(ksks) == 1 + assert len(zsks) == 1 + + # This assumes the zone has a policy that dictates one KSK and one ZSK. + # The right keys to be used are stored in "{zone}.ksk" and "{zone}.zsk". + with open(f"{keydir}/{zone}.ksk", "r", encoding="utf-8") as file: + kskfile = file.read() + with open(f"{keydir}/{zone}.zsk", "r", encoding="utf-8") as file: + zskfile = file.read() + + assert f"{keydir}/{kskfile}".strip() == ksks[0].path + assert f"{keydir}/{zskfile}".strip() == zsks[0].path + + def test_remove_keyfiles(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info( + "check that removing key files does not create new keys to be generated" + ) + + for k in ksks + zsks: + os.remove(k.keyfile) + os.remove(k.privatefile) + os.remove(k.statefile) + + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line( + f"zone {zone}/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" + ) + + # Check keys again, make sure no new keys are created. + keys = isctest.kasp.keydir_to_keylist(zone, keydir) + isctest.kasp.check_keys(zone, keys, []) + # Zone is still signed correctly. + isctest.kasp.check_dnssec_verify(server, zone) + # Test case function. def test_case(): zone = test["zone"] @@ -177,6 +301,7 @@ def test_kasp_cases(servers): pregenerated = False if test.get("pregenerated"): pregenerated = test["pregenerated"] + zsk_missing = zone == "zsk-missing.autosign" isctest.log.info(f"check test case zone {zone} policy {policy}") @@ -203,7 +328,13 @@ def test_kasp_cases(servers): isctest.kasp.check_keytimes(keys, expected) - check_all(server, zone, policy, ksks, zsks) + check_all(server, zone, policy, ksks, zsks, zsk_missing=zsk_missing) + + if "additional-tests" in test: + for additional_test in test["additional-tests"]: + callback = additional_test["callback"] + arguments = additional_test["arguments"] + callback(*arguments, zone=zone, policy=policy, ksks=ksks, zsks=zsks) # Test cases. rsa_cases = [] @@ -231,6 +362,78 @@ def test_kasp_cases(servers): "offset": -timedelta(days=30 * 6), "key-properties": autosign_properties, }, + { + "zone": "expired-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_rrsig_refresh, + "arguments": [], + }, + ], + }, + { + "zone": "fresh-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_rrsig_reuse, + "arguments": [], + }, + ], + }, + { + "zone": "unfresh-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_rrsig_refresh, + "arguments": [], + }, + ], + }, + { + "zone": "keyfiles-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_remove_keyfiles, + "arguments": [], + }, + ], + }, + { + "zone": "ksk-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": [ + f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing", + f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ], + }, + { + "zone": "zsk-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": [ + f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing", + ], + }, { "zone": "dnssec-keygen.kasp", "policy": "rsasha256", @@ -255,6 +458,22 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(8), }, + { + "zone": "legacy-keys.kasp", + "policy": "migrate-to-dnssec-policy", + "config": kasp_config, + "pregenerated": True, + "key-properties": [ + "ksk 16070400 8 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + "zsk 16070400 8 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + "additional-tests": [ + { + "callback": test_legacy_keys, + "arguments": [], + }, + ], + }, { "zone": "pregenerated.kasp", "policy": "rsasha256", @@ -274,6 +493,23 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(10), }, + { + "zone": "secondary.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + "additional-tests": [ + { + "callback": test_ixfr_is_signed, + "arguments": [ + [ + "a.secondary.kasp. A 10.0.0.11", + "d.secondary.kasp. A 10.0.0.4", + ], + ], + }, + ], + }, { "zone": "some-keys.kasp", "policy": "rsasha256", From 242e26ff4c3e1aea4f83565a2a6d20611ddb0627 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 17 Mar 2025 15:32:43 +0100 Subject: [PATCH 5/6] Convert keystore and rumoured kasp test cases For 'keystore.kasp', a setting 'key-directories' is used. If set, this will expect a list of two directories, the first one is where the KSKs will be stored, the second in the list is the ZSK key directory. This may be expanded in the future to test more complex key storage cases. The 'rumoured.kasp' zone is weird, the key timings can never match those key states. But it is a regression test for an early day bug, so we convert it, but skip the expected key times check. (cherry picked from commit ee7120eb34c889d571cca15ec1a3fb068e6e7545) --- bin/tests/system/kasp/tests.sh | 114 ---------------------------- bin/tests/system/kasp/tests_kasp.py | 48 ++++++++++-- 2 files changed, 42 insertions(+), 120 deletions(-) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index ad2b49fb0d..11335fdfa4 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -400,120 +400,6 @@ set_keytimes_algorithm_policy() { set_addkeytime "KEY3" "REMOVED" "${retired}" 867900 } -# -# Zone: keystore.kasp. -# -set_zone "keystore.kasp" -set_policy "keystore" "2" "303" -set_server "ns3" "10.53.0.3" -# Key properties. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "0" -set_keydir "KEY1" "ns3/ksk" -set_keyalgorithm "KEY1" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "0" -set_keydir "KEY2" "ns3/zsk" -set_keyalgorithm "KEY2" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" - -# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. -# ZSK: DNSKEY, RRSIG (zsk) published. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "rumoured" -set_keystate "KEY1" "STATE_KRRSIG" "rumoured" -set_keystate "KEY1" "STATE_DS" "hidden" - -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "rumoured" -set_keystate "KEY2" "STATE_ZRRSIG" "rumoured" -# Two keys only. -key_clear "KEY3" -key_clear "KEY4" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -# Reuse set_keytimes_csk_policy to set the KEY1 keytimes. -set_keytimes_csk_policy -created=$(key_get KEY2 CREATED) -set_keytime "KEY2" "PUBLISHED" "${created}" -set_keytime "KEY2" "ACTIVE" "${created}" -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Key properties for tests below. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "315360000" -set_keyalgorithm "KEY1" "8" "RSASHA256" "2048" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "157680000" -set_keyalgorithm "KEY2" "8" "RSASHA256" "2048" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" - -key_clear "KEY3" -set_keyrole "KEY3" "zsk" -set_keylifetime "KEY3" "31536000" -set_keyalgorithm "KEY3" "8" "RSASHA256" "3072" -set_keysigning "KEY3" "no" -set_zonesigning "KEY3" "yes" -# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. -# ZSK: DNSKEY, RRSIG (zsk) published. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "rumoured" -set_keystate "KEY1" "STATE_KRRSIG" "rumoured" -set_keystate "KEY1" "STATE_DS" "hidden" - -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "rumoured" -set_keystate "KEY2" "STATE_ZRRSIG" "rumoured" - -set_keystate "KEY3" "GOAL" "omnipresent" -set_keystate "KEY3" "STATE_DNSKEY" "rumoured" -set_keystate "KEY3" "STATE_ZRRSIG" "rumoured" -# Three keys only. -key_clear "KEY4" - -# -# Zone: rumoured.kasp. -# -# There are three keys in rumoured state. -set_zone "rumoured.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -# Activation date is a day later. -set_addkeytime "KEY1" "ACTIVE" $(key_get KEY1 ACTIVE) 86400 -set_addkeytime "KEY1" "RETIRED" $(key_get KEY1 RETIRED) 86400 -set_addkeytime "KEY1" "REMOVED" $(key_get KEY1 REMOVED) 86400 -set_addkeytime "KEY2" "ACTIVE" $(key_get KEY2 ACTIVE) 86400 -set_addkeytime "KEY2" "RETIRED" $(key_get KEY2 RETIRED) 86400 -set_addkeytime "KEY2" "REMOVED" $(key_get KEY2 REMOVED) 86400 -set_addkeytime "KEY3" "ACTIVE" $(key_get KEY3 ACTIVE) 86400 -set_addkeytime "KEY3" "RETIRED" $(key_get KEY3 RETIRED) 86400 -set_addkeytime "KEY3" "REMOVED" $(key_get KEY3 REMOVED) 86400 -check_keytimes -check_apex -check_subdomain -dnssec_verify - # TODO: we might want to test: # - configuring a zone with too many active keys (should trigger retire). # - configuring a zone with keys not matching the policy. diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index eba8e73277..05f4e4254b 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -310,11 +310,18 @@ def test_kasp_cases(servers): ttl=ttl, keys=test["key-properties"] ) # Key files. - keys = isctest.kasp.keydir_to_keylist( - zone, test["config"]["key-directory"], in_use=pregenerated - ) - ksks = [k for k in keys if k.is_ksk()] - zsks = [k for k in keys if not k.is_ksk()] + if "key-directories" in test: + kdir = test["key-directories"][0] + ksks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated) + kdir = test["key-directories"][1] + zsks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated) + keys = ksks + zsks + else: + keys = isctest.kasp.keydir_to_keylist( + zone, test["config"]["key-directory"], in_use=pregenerated + ) + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] isctest.kasp.check_zone_is_signed(server, zone) isctest.kasp.check_keys(zone, keys, expected) @@ -326,7 +333,8 @@ def test_kasp_cases(servers): test["config"], offset=offset, pregenerated=pregenerated ) - isctest.kasp.check_keytimes(keys, expected) + if "rumoured" not in test: + isctest.kasp.check_keytimes(keys, expected) check_all(server, zone, policy, ksks, zsks, zsk_missing=zsk_missing) @@ -458,6 +466,27 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(8), }, + { + "zone": "keystore.kasp", + "policy": "keystore", + "config": { + "dnskey-ttl": timedelta(seconds=303), + "ds-ttl": timedelta(days=1), + "key-directory": keydir, + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), + }, + "key-directories": [f"{keydir}/ksk", f"{keydir}/zsk"], + "key-properties": [ + f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + }, { "zone": "legacy-keys.kasp", "policy": "migrate-to-dnssec-policy", @@ -493,6 +522,13 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(10), }, + { + "zone": "rumoured.kasp", + "policy": "rsasha256", + "config": kasp_config, + "rumoured": True, + "key-properties": fips_properties(8), + }, { "zone": "secondary.kasp", "policy": "rsasha256", From 3d1a763dff63ba75ce5629570f769b8f705453ba Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 22 Apr 2025 12:06:14 +0200 Subject: [PATCH 6/6] Parametrize the default kasp test cases Make use of pytest.mark.parametrize to split up the many default kasp test cases into separate tests. (cherry picked from commit 7d670b7fe73619330132075c6fe8ad23520c0de9) --- bin/tests/system/isctest/kasp.py | 7 +- bin/tests/system/isctest/mark.py | 6 + bin/tests/system/kasp/tests_kasp.py | 956 +++++++++++++++------------- 3 files changed, 535 insertions(+), 434 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index fc00a4df0c..51bf22f089 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -16,7 +16,7 @@ from pathlib import Path import re import subprocess import time -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Tuple, Union from datetime import datetime, timedelta, timezone @@ -332,7 +332,9 @@ class Key: ) return value - def get_signing_state(self, offline_ksk=False, zsk_missing=False) -> (bool, bool): + def get_signing_state( + self, offline_ksk=False, zsk_missing=False + ) -> Tuple[bool, bool]: """ This returns the signing state derived from the key states, KRRSIGState and ZRRSIGState. @@ -348,6 +350,7 @@ class Key: # Fetch key timing metadata. now = KeyTimingMetadata.now() activate = self.get_timing("Activate") + assert activate is not None # to silence mypy - its implied by line above inactive = self.get_timing("Inactive", must_exist=False) active = now >= activate diff --git a/bin/tests/system/isctest/mark.py b/bin/tests/system/isctest/mark.py index b1a720227a..facf1038d5 100644 --- a/bin/tests/system/isctest/mark.py +++ b/bin/tests/system/isctest/mark.py @@ -56,6 +56,12 @@ def with_tsan(*args): # pylint: disable=unused-argument return feature_test("--tsan") +def with_algorithm(name: str): + key = f"{name}_SUPPORTED" + assert key in os.environ, f"{key} env variable undefined" + return pytest.mark.skipif(os.getenv(key) != "1", reason=f"{name} is not supported") + + without_fips = pytest.mark.skipif( feature_test("--have-fips-mode"), reason="FIPS support enabled in the build" ) diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 05f4e4254b..63efab4dd5 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -21,6 +21,7 @@ import pytest pytest.importorskip("dns", minversion="2.0.0") import isctest +import isctest.mark from isctest.kasp import ( KeyProperties, KeyTimingMetadata, @@ -80,6 +81,69 @@ pytestmark = pytest.mark.extra_artifacts( ) +kasp_config = { + "dnskey-ttl": timedelta(seconds=1234), + "ds-ttl": timedelta(days=1), + "key-directory": "{keydir}", + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), +} + +autosign_config = { + "dnskey-ttl": timedelta(seconds=300), + "ds-ttl": timedelta(days=1), + "key-directory": "{keydir}", + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=7), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), +} + +lifetime = { + "P10Y": int(timedelta(days=10 * 365).total_seconds()), + "P5Y": int(timedelta(days=5 * 365).total_seconds()), + "P2Y": int(timedelta(days=2 * 365).total_seconds()), + "P1Y": int(timedelta(days=365).total_seconds()), + "P30D": int(timedelta(days=30).total_seconds()), + "P6M": int(timedelta(days=31 * 6).total_seconds()), +} + + +def autosign_properties(alg, size): + return [ + f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P1Y']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + + +def rsa1_properties(alg): + return [ + f"ksk {lifetime['P10Y']} {alg} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk {lifetime['P5Y']} {alg} 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"zsk {lifetime['P1Y']} {alg} 2000 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ] + + +def fips_properties(alg, bits=None): + sizes = [2048, 2048, 3072] + if bits is not None: + sizes = [bits, bits, bits] + + return [ + f"ksk {lifetime['P10Y']} {alg} {sizes[0]} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk {lifetime['P5Y']} {alg} {sizes[1]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + f"zsk {lifetime['P1Y']} {alg} {sizes[2]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ] + + def check_all(server, zone, policy, ksks, zsks, zsk_missing=False, tsig=None): isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy) isctest.kasp.check_apex( @@ -105,487 +169,520 @@ def set_keytimes_default_policy(kp): kp.timing["ZRRSIGChange"] = kp.timing["Active"] -def test_kasp_cases(servers): - # Test many different configurations and expected keys and states after - # initial startup. - server = servers["ns3"] - keydir = server.identifier - alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] - size = os.environ["DEFAULT_BITS"] +def cb_ixfr_is_signed(expected_updates, params, ksks=None, zsks=None): + zone = params["zone"] + policy = params["policy"] + servers = params["servers"] - kasp_config = { - "dnskey-ttl": timedelta(seconds=1234), - "ds-ttl": timedelta(days=1), - "key-directory": keydir, - "max-zone-ttl": timedelta(days=1), - "parent-propagation-delay": timedelta(hours=1), - "publish-safety": timedelta(hours=1), - "retire-safety": timedelta(hours=1), - "signatures-refresh": timedelta(days=5), - "signatures-validity": timedelta(days=14), - "zone-propagation-delay": timedelta(minutes=5), - } + isctest.log.info(f"check that the zone {zone} is correctly signed after ixfr") + isctest.log.debug( + f"expected updates {expected_updates} policy {policy} ksks {ksks} zsks {zsks}" + ) + shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db") + servers["ns2"].rndc(f"reload {zone}", log=False) - autosign_config = { - "dnskey-ttl": timedelta(seconds=300), - "ds-ttl": timedelta(days=1), - "key-directory": keydir, - "max-zone-ttl": timedelta(days=1), - "parent-propagation-delay": timedelta(hours=1), - "publish-safety": timedelta(hours=1), - "retire-safety": timedelta(hours=1), - "signatures-refresh": timedelta(days=7), - "signatures-validity": timedelta(days=14), - "zone-propagation-delay": timedelta(minutes=5), - } + def update_is_signed(): + parts = update.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + rdata = parts[2] + return isctest.kasp.verify_update_is_signed( + servers["ns3"], zone, qname, qtype, rdata, ksks, zsks + ) - lifetime = { - "P10Y": int(timedelta(days=10 * 365).total_seconds()), - "P5Y": int(timedelta(days=5 * 365).total_seconds()), - "P2Y": int(timedelta(days=2 * 365).total_seconds()), - "P1Y": int(timedelta(days=365).total_seconds()), - "P30D": int(timedelta(days=30).total_seconds()), - "P6M": int(timedelta(days=31 * 6).total_seconds()), - } + for update in expected_updates: + isctest.run.retry_with_timeout(update_is_signed, timeout=5) - autosign_properties = [ - f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk {lifetime['P1Y']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + +def cb_rrsig_refresh(params, ksks=None, zsks=None): + zone = params["zone"] + servers = params["servers"] + + isctest.log.info(f"check that the zone {zone} refreshes expired signatures") + + def rrsig_is_refreshed(): + parts = query.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + return isctest.kasp.verify_rrsig_is_refreshed( + servers["ns3"], zone, f"ns3/{zone}.db.signed", qname, qtype, ksks, zsks + ) + + queries = [ + f"{zone} DNSKEY", + f"{zone} SOA", + f"{zone} NS", + f"{zone} NSEC", + f"a.{zone} A", + f"a.{zone} NSEC", + f"b.{zone} A", + f"b.{zone} NSEC", + f"c.{zone} A", + f"c.{zone} NSEC", + f"ns3.{zone} A", + f"ns3.{zone} NSEC", ] - def rsa1_properties(alg): - return [ - f"ksk {lifetime['P10Y']} {alg} 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk {lifetime['P5Y']} {alg} 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", - f"zsk {lifetime['P1Y']} {alg} 2000 goal:omnipresent dnskey:rumoured zrrsig:rumoured", - ] + for query in queries: + isctest.run.retry_with_timeout(rrsig_is_refreshed, timeout=5) - def fips_properties(alg, bits=None): - sizes = [2048, 2048, 3072] - if bits is not None: - sizes = [bits, bits, bits] - return [ - f"ksk {lifetime['P10Y']} {alg} {sizes[0]} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk {lifetime['P5Y']} {alg} {sizes[1]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", - f"zsk {lifetime['P1Y']} {alg} {sizes[2]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", - ] +def cb_rrsig_reuse(params, ksks=None, zsks=None): + zone = params["zone"] + servers = params["servers"] - # Additional test functions. - def test_ixfr_is_signed( - expected_updates, zone=None, policy=None, ksks=None, zsks=None - ): - isctest.log.info(f"check that the zone {zone} is correctly signed after ixfr") - isctest.log.debug( - f"expected updates {expected_updates} policy {policy} ksks {ksks} zsks {zsks}" + isctest.log.info(f"check that the zone {zone} reuses fresh signatures") + + def rrsig_is_reused(): + parts = query.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + return isctest.kasp.verify_rrsig_is_reused( + servers["ns3"], zone, f"ns3/{zone}.db.signed", qname, qtype, ksks, zsks ) - shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db") - servers["ns2"].rndc(f"reload {zone}", log=False) + queries = [ + f"{zone} NS", + f"{zone} NSEC", + f"a.{zone} A", + f"a.{zone} NSEC", + f"b.{zone} A", + f"b.{zone} NSEC", + f"c.{zone} A", + f"c.{zone} NSEC", + f"ns3.{zone} A", + f"ns3.{zone} NSEC", + ] - def update_is_signed(): - parts = update.split() - qname = parts[0] - qtype = dns.rdatatype.from_text(parts[1]) - rdata = parts[2] - return isctest.kasp.verify_update_is_signed( - server, zone, qname, qtype, rdata, ksks, zsks - ) + for query in queries: + rrsig_is_reused() - for update in expected_updates: - isctest.run.retry_with_timeout(update_is_signed, timeout=5) - def test_rrsig_refresh(zone=None, policy=None, ksks=None, zsks=None): - # pylint: disable=unused-argument - isctest.log.info(f"check that the zone {zone} refreshes expired signatures") +def cb_legacy_keys(params, ksks=None, zsks=None): + zone = params["zone"] + keydir = params["config"]["key-directory"] - def rrsig_is_refreshed(): - parts = query.split() - qname = parts[0] - qtype = dns.rdatatype.from_text(parts[1]) - return isctest.kasp.check_rrsig_is_refreshed( - server, zone, f"ns3/{zone}.db.signed", qname, qtype, ksks, zsks - ) + isctest.log.info(f"check that the zone {zone} uses correct legacy keys") - queries = [ - f"{zone} DNSKEY", - f"{zone} SOA", - f"{zone} NS", - f"{zone} NSEC", - f"a.{zone} A", - f"a.{zone} NSEC", - f"b.{zone} A", - f"b.{zone} NSEC", - f"c.{zone} A", - f"c.{zone} NSEC", - f"ns3.{zone} A", - f"ns3.{zone} NSEC", - ] + assert len(ksks) == 1 + assert len(zsks) == 1 - for query in queries: - isctest.run.retry_with_timeout(rrsig_is_refreshed, timeout=5) + # This assumes the zone has a policy that dictates one KSK and one ZSK. + # The right keys to be used are stored in "{zone}.ksk" and "{zone}.zsk". + with open(f"{keydir}/{zone}.ksk", "r", encoding="utf-8") as file: + kskfile = file.read() + with open(f"{keydir}/{zone}.zsk", "r", encoding="utf-8") as file: + zskfile = file.read() - def test_rrsig_reuse(zone=None, policy=None, ksks=None, zsks=None): - # pylint: disable=unused-argument - isctest.log.info(f"check that the zone {zone} reuses fresh signatures") + assert f"{keydir}/{kskfile}".strip() == ksks[0].path + assert f"{keydir}/{zskfile}".strip() == zsks[0].path - def rrsig_is_reused(): - parts = query.split() - qname = parts[0] - qtype = dns.rdatatype.from_text(parts[1]) - return isctest.kasp.check_rrsig_is_reused( - server, zone, f"{keydir}/{zone}.db.signed", qname, qtype, ksks, zsks - ) - queries = [ - f"{zone} NS", - f"{zone} NSEC", - f"a.{zone} A", - f"a.{zone} NSEC", - f"b.{zone} A", - f"b.{zone} NSEC", - f"c.{zone} A", - f"c.{zone} NSEC", - f"ns3.{zone} A", - f"ns3.{zone} NSEC", - ] +def cb_remove_keyfiles(params, ksks=None, zsks=None): + zone = params["zone"] + servers = params["servers"] + keydir = params["config"]["key-directory"] - for query in queries: - rrsig_is_reused() + isctest.log.info( + "check that removing key files does not create new keys to be generated" + ) - def test_legacy_keys(zone=None, policy=None, ksks=None, zsks=None): - # pylint: disable=unused-argument - isctest.log.info(f"check that the zone {zone} uses correct legacy keys") + for k in ksks + zsks: + os.remove(k.keyfile) + os.remove(k.privatefile) + os.remove(k.statefile) - assert len(ksks) == 1 - assert len(zsks) == 1 - - # This assumes the zone has a policy that dictates one KSK and one ZSK. - # The right keys to be used are stored in "{zone}.ksk" and "{zone}.zsk". - with open(f"{keydir}/{zone}.ksk", "r", encoding="utf-8") as file: - kskfile = file.read() - with open(f"{keydir}/{zone}.zsk", "r", encoding="utf-8") as file: - zskfile = file.read() - - assert f"{keydir}/{kskfile}".strip() == ksks[0].path - assert f"{keydir}/{zskfile}".strip() == zsks[0].path - - def test_remove_keyfiles(zone=None, policy=None, ksks=None, zsks=None): - # pylint: disable=unused-argument - isctest.log.info( - "check that removing key files does not create new keys to be generated" + with servers["ns3"].watch_log_from_here() as watcher: + servers["ns3"].rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line( + f"zone {zone}/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" ) - for k in ksks + zsks: - os.remove(k.keyfile) - os.remove(k.privatefile) - os.remove(k.statefile) + # Check keys again, make sure no new keys are created. + keys = isctest.kasp.keydir_to_keylist(zone, keydir) + isctest.kasp.check_keys(zone, keys, []) + # Zone is still signed correctly. + isctest.kasp.check_dnssec_verify(servers["ns3"], zone) - with server.watch_log_from_here() as watcher: - server.rndc(f"loadkeys {zone}", log=False) - watcher.wait_for_line( - f"zone {zone}/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" - ) - # Check keys again, make sure no new keys are created. - keys = isctest.kasp.keydir_to_keylist(zone, keydir) - isctest.kasp.check_keys(zone, keys, []) - # Zone is still signed correctly. - isctest.kasp.check_dnssec_verify(server, zone) - - # Test case function. - def test_case(): - zone = test["zone"] - policy = test["policy"] - ttl = int(test["config"]["dnskey-ttl"].total_seconds()) - pregenerated = False - if test.get("pregenerated"): - pregenerated = test["pregenerated"] - zsk_missing = zone == "zsk-missing.autosign" - - isctest.log.info(f"check test case zone {zone} policy {policy}") - - # Key properties. - expected = isctest.kasp.policy_to_properties( - ttl=ttl, keys=test["key-properties"] - ) - # Key files. - if "key-directories" in test: - kdir = test["key-directories"][0] - ksks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated) - kdir = test["key-directories"][1] - zsks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated) - keys = ksks + zsks - else: - keys = isctest.kasp.keydir_to_keylist( - zone, test["config"]["key-directory"], in_use=pregenerated - ) - ksks = [k for k in keys if k.is_ksk()] - zsks = [k for k in keys if not k.is_ksk()] - - isctest.kasp.check_zone_is_signed(server, zone) - isctest.kasp.check_keys(zone, keys, expected) - - offset = test["offset"] if "offset" in test else None - - for kp in expected: - kp.set_expected_keytimes( - test["config"], offset=offset, pregenerated=pregenerated - ) - - if "rumoured" not in test: - isctest.kasp.check_keytimes(keys, expected) - - check_all(server, zone, policy, ksks, zsks, zsk_missing=zsk_missing) - - if "additional-tests" in test: - for additional_test in test["additional-tests"]: - callback = additional_test["callback"] - arguments = additional_test["arguments"] - callback(*arguments, zone=zone, policy=policy, ksks=ksks, zsks=zsks) - - # Test cases. - rsa_cases = [] - if os.environ["RSASHA1_SUPPORTED"] == 1: - rsa_cases = [ +@pytest.mark.parametrize( + "params", + [ + pytest.param( { "zone": "rsasha1.kasp", "policy": "rsasha1", "config": kasp_config, "key-properties": rsa1_properties(5), }, + id="rsasha1.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( { "zone": "rsasha1-nsec3.kasp", "policy": "rsasha1", "config": kasp_config, "key-properties": rsa1_properties(7), }, - ] - - fips_cases = [ - { - "zone": "dnskey-ttl-mismatch.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties, - }, - { - "zone": "expired-sigs.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties, - "additional-tests": [ - { - "callback": test_rrsig_refresh, - "arguments": [], - }, - ], - }, - { - "zone": "fresh-sigs.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties, - "additional-tests": [ - { - "callback": test_rrsig_reuse, - "arguments": [], - }, - ], - }, - { - "zone": "unfresh-sigs.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties, - "additional-tests": [ - { - "callback": test_rrsig_refresh, - "arguments": [], - }, - ], - }, - { - "zone": "keyfiles-missing.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": autosign_properties, - "additional-tests": [ - { - "callback": test_remove_keyfiles, - "arguments": [], - }, - ], - }, - { - "zone": "ksk-missing.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": [ - f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing", - f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", - ], - }, - { - "zone": "zsk-missing.autosign", - "policy": "autosign", - "config": autosign_config, - "offset": -timedelta(days=30 * 6), - "key-properties": [ - f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", - f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing", - ], - }, - { - "zone": "dnssec-keygen.kasp", - "policy": "rsasha256", - "config": kasp_config, - "key-properties": fips_properties(8), - }, - { - "zone": "ecdsa256.kasp", - "policy": "ecdsa256", - "config": kasp_config, - "key-properties": fips_properties(13, bits=256), - }, - { - "zone": "ecdsa384.kasp", - "policy": "ecdsa384", - "config": kasp_config, - "key-properties": fips_properties(14, bits=384), - }, - { - "zone": "inherit.kasp", - "policy": "rsasha256", - "config": kasp_config, - "key-properties": fips_properties(8), - }, - { - "zone": "keystore.kasp", - "policy": "keystore", - "config": { - "dnskey-ttl": timedelta(seconds=303), - "ds-ttl": timedelta(days=1), - "key-directory": keydir, - "max-zone-ttl": timedelta(days=1), - "parent-propagation-delay": timedelta(hours=1), - "publish-safety": timedelta(hours=1), - "retire-safety": timedelta(hours=1), - "signatures-refresh": timedelta(days=5), - "signatures-validity": timedelta(days=14), - "zone-propagation-delay": timedelta(minutes=5), + id="rsasha1-nsec3.kasp", + marks=isctest.mark.with_algorithm("RSASHA1"), + ), + pytest.param( + { + "zone": "dnskey-ttl-mismatch.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties( + os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] + ), }, - "key-directories": [f"{keydir}/ksk", f"{keydir}/zsk"], - "key-properties": [ - f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured", - ], - }, - { - "zone": "legacy-keys.kasp", - "policy": "migrate-to-dnssec-policy", - "config": kasp_config, - "pregenerated": True, - "key-properties": [ - "ksk 16070400 8 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", - "zsk 16070400 8 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", - ], - "additional-tests": [ - { - "callback": test_legacy_keys, - "arguments": [], + id="dnskey-ttl-mismatch.autosign", + ), + pytest.param( + { + "zone": "expired-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties( + os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] + ), + "additional-tests": [ + { + "callback": cb_rrsig_refresh, + "arguments": [], + }, + ], + }, + id="expired-sigs.autosign", + ), + pytest.param( + { + "zone": "fresh-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties( + os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] + ), + "additional-tests": [ + { + "callback": cb_rrsig_reuse, + "arguments": [], + }, + ], + }, + id="fresh-sigs.autosign", + ), + pytest.param( + { + "zone": "unfresh-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties( + os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] + ), + "additional-tests": [ + { + "callback": cb_rrsig_refresh, + "arguments": [], + }, + ], + }, + id="unfresh-sigs.autosign", + ), + pytest.param( + { + "zone": "keyfiles-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties( + os.environ["DEFAULT_ALGORITHM_NUMBER"], os.environ["DEFAULT_BITS"] + ), + "additional-tests": [ + { + "callback": cb_remove_keyfiles, + "arguments": [], + }, + ], + }, + id="keyfiles-missing.autosign", + ), + pytest.param( + { + "zone": "ksk-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": [ + f"ksk 63072000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing", + f"zsk 31536000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ], + }, + id="ksk-missing.autosign", + ), + pytest.param( + { + "zone": "zsk-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": [ + f"ksk 63072000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 31536000 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing", + ], + }, + id="zsk-missing.autosign", + ), + pytest.param( + { + "zone": "dnssec-keygen.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + }, + id="dnssec-keygen.kasp", + ), + pytest.param( + { + "zone": "ecdsa256.kasp", + "policy": "ecdsa256", + "config": kasp_config, + "key-properties": fips_properties(13, bits=256), + }, + id="ecdsa256.kasp", + ), + pytest.param( + { + "zone": "ecdsa384.kasp", + "policy": "ecdsa384", + "config": kasp_config, + "key-properties": fips_properties(14, bits=384), + }, + id="ecdsa384.kasp", + ), + pytest.param( + { + "zone": "inherit.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + }, + id="inherit.kasp", + ), + pytest.param( + { + "zone": "keystore.kasp", + "policy": "keystore", + "config": { + "dnskey-ttl": timedelta(seconds=303), + "ds-ttl": timedelta(days=1), + "key-directory": "{keydir}", + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), }, - ], - }, - { - "zone": "pregenerated.kasp", - "policy": "rsasha256", - "config": kasp_config, - "pregenerated": True, - "key-properties": fips_properties(8), - }, - { - "zone": "rsasha256.kasp", - "policy": "rsasha256", - "config": kasp_config, - "key-properties": fips_properties(8), - }, - { - "zone": "rsasha512.kasp", - "policy": "rsasha512", - "config": kasp_config, - "key-properties": fips_properties(10), - }, - { - "zone": "rumoured.kasp", - "policy": "rsasha256", - "config": kasp_config, - "rumoured": True, - "key-properties": fips_properties(8), - }, - { - "zone": "secondary.kasp", - "policy": "rsasha256", - "config": kasp_config, - "key-properties": fips_properties(8), - "additional-tests": [ - { - "callback": test_ixfr_is_signed, - "arguments": [ - [ - "a.secondary.kasp. A 10.0.0.11", - "d.secondary.kasp. A 10.0.0.4", + "key-directories": ["{keydir}/ksk", "{keydir}/zsk"], + "key-properties": [ + f"ksk unlimited {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + }, + id="keystore.kasp", + ), + pytest.param( + { + "zone": "legacy-keys.kasp", + "policy": "migrate-to-dnssec-policy", + "config": kasp_config, + "pregenerated": True, + "key-properties": [ + "ksk 16070400 8 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + "zsk 16070400 8 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + "additional-tests": [ + { + "callback": cb_legacy_keys, + "arguments": [], + }, + ], + }, + id="legacy-keys.kasp", + ), + pytest.param( + { + "zone": "pregenerated.kasp", + "policy": "rsasha256", + "config": kasp_config, + "pregenerated": True, + "key-properties": fips_properties(8), + }, + id="pregenerated.kasp", + ), + pytest.param( + { + "zone": "rsasha256.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + }, + id="rsasha256.kasp", + ), + pytest.param( + { + "zone": "rsasha512.kasp", + "policy": "rsasha512", + "config": kasp_config, + "key-properties": fips_properties(10), + }, + id="rsasha512.kasp", + ), + pytest.param( + { + "zone": "rumoured.kasp", + "policy": "rsasha256", + "config": kasp_config, + "rumoured": True, + "key-properties": fips_properties(8), + }, + id="rumoured.kasp", + ), + pytest.param( + { + "zone": "secondary.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + "additional-tests": [ + { + "callback": cb_ixfr_is_signed, + "arguments": [ + [ + "a.secondary.kasp. A 10.0.0.11", + "d.secondary.kasp. A 10.0.0.4", + ], ], - ], - }, - ], - }, - { - "zone": "some-keys.kasp", - "policy": "rsasha256", - "config": kasp_config, - "pregenerated": True, - "key-properties": fips_properties(8), - }, - { - "zone": "unlimited.kasp", - "policy": "unlimited", - "config": kasp_config, - "key-properties": [ - f"csk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", - ], - }, - ] - - if os.environ["ED25519_SUPPORTED"] == 1: - fips_cases.append( + }, + ], + }, + id="secondary.kasp", + ), + pytest.param( + { + "zone": "some-keys.kasp", + "policy": "rsasha256", + "config": kasp_config, + "pregenerated": True, + "key-properties": fips_properties(8), + }, + id="some-keys.kasp", + ), + pytest.param( + { + "zone": "unlimited.kasp", + "policy": "unlimited", + "config": kasp_config, + "key-properties": [ + f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ], + }, + id="unlimited.kasp", + ), + pytest.param( { "zone": "ed25519.kasp", "policy": "ed25519", "config": kasp_config, "key-properties": fips_properties(15, bits=256), - } - ) - - if os.environ["ED448_SUPPORTED"] == 1: - fips_cases.append( + }, + id="ed25519.kasp", + marks=isctest.mark.with_algorithm("ED25519"), + ), + pytest.param( { "zone": "ed448.kasp", "policy": "ed448", "config": kasp_config, "key-properties": fips_properties(16, bits=456), - } + }, + id="ed448.kasp", + marks=isctest.mark.with_algorithm("ED448"), + ), + ], +) +def test_kasp_case(servers, params): + # Test many different configurations and expected keys and states after + # initial startup. + server = servers["ns3"] + keydir = server.identifier + + # Get test parameters. + zone = params["zone"] + policy = params["policy"] + + params["config"]["key-directory"] = params["config"]["key-directory"].replace( + "{keydir}", keydir + ) + if "key-directories" in params: + for i, val in enumerate(params["key-directories"]): + params["key-directories"][i] = val.replace("{keydir}", keydir) + + ttl = int(params["config"]["dnskey-ttl"].total_seconds()) + pregenerated = False + if params.get("pregenerated"): + pregenerated = params["pregenerated"] + zsk_missing = zone == "zsk-missing.autosign" + + # Test case. + isctest.log.info(f"check test case zone {zone} policy {policy}") + + # First make sure the zone is signed. + isctest.kasp.check_zone_is_signed(server, zone) + + # Key properties. + expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=params["key-properties"]) + # Key files. + if "key-directories" in params: + kdir = params["key-directories"][0] + ksks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated) + kdir = params["key-directories"][1] + zsks = isctest.kasp.keydir_to_keylist(zone, kdir, in_use=pregenerated) + keys = ksks + zsks + else: + keys = isctest.kasp.keydir_to_keylist( + zone, params["config"]["key-directory"], in_use=pregenerated + ) + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] + + isctest.kasp.check_keys(zone, keys, expected) + + offset = params["offset"] if "offset" in params else None + + for kp in expected: + kp.set_expected_keytimes( + params["config"], offset=offset, pregenerated=pregenerated ) - test_cases = rsa_cases + fips_cases - for test in test_cases: - test_case() + if "rumoured" not in params: + isctest.kasp.check_keytimes(keys, expected) + + check_all(server, zone, policy, ksks, zsks, zsk_missing=zsk_missing) + + if "additional-tests" in params: + params["servers"] = servers + for additional_test in params["additional-tests"]: + callback = additional_test["callback"] + arguments = additional_test["arguments"] + callback(*arguments, params=params, ksks=ksks, zsks=zsks) def test_kasp_default(servers): @@ -889,11 +986,6 @@ def test_kasp_dnssec_keygen(): return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8") # check that 'dnssec-keygen -k' (configured policy) creates valid files. - lifetime = { - "P1Y": int(timedelta(days=365).total_seconds()), - "P30D": int(timedelta(days=30).total_seconds()), - "P6M": int(timedelta(days=31 * 6).total_seconds()), - } keyprops = [ f"csk {lifetime['P1Y']} 13 256", f"ksk {lifetime['P1Y']} 8 2048",