def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
return [Key(name, keydir) for name in keystr.split()]
+
+
+def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]:
+ """
+ Get the policies from a list of specially formatted strings.
+ The splitted line should result in the following items:
+ line[0]: Role
+ line[1]: Lifetime
+ line[2]: Algorithm
+ line[3]: Length
+ 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
+ - "offset", an offset for testing key rollover timings
+ """
+ proplist = []
+ count = 0
+ for key in keys:
+ count += 1
+ line = key.split()
+ keyprop = KeyProperties(f"KEY{count}", {}, {}, {})
+ keyprop.properties["expect"] = True
+ keyprop.properties["private"] = True
+ keyprop.properties["legacy"] = False
+ keyprop.properties["offset"] = timedelta(0)
+ keyprop.properties["role"] = line[0]
+ if line[0] == "zsk":
+ keyprop.properties["role_full"] = "zone-signing"
+ keyprop.properties["flags"] = 256
+ keyprop.metadata["ZSK"] = "yes"
+ keyprop.metadata["KSK"] = "no"
+ else:
+ keyprop.properties["role_full"] = "key-signing"
+ keyprop.properties["flags"] = 257
+ keyprop.metadata["ZSK"] = "yes" if line[0] == "csk" else "no"
+ keyprop.metadata["KSK"] = "yes"
+
+ keyprop.properties["dnskey_ttl"] = ttl
+ keyprop.metadata["Algorithm"] = line[2]
+ keyprop.metadata["Length"] = line[3]
+ keyprop.metadata["Lifetime"] = 0
+ if line[1] != "unlimited":
+ keyprop.metadata["Lifetime"] = int(line[1])
+
+ for i in range(4, len(line)):
+ if line[i].startswith("goal:"):
+ keyval = line[i].split(":")
+ keyprop.metadata["GoalState"] = keyval[1]
+ elif line[i].startswith("dnskey:"):
+ keyval = line[i].split(":")
+ keyprop.metadata["DNSKEYState"] = keyval[1]
+ elif line[i].startswith("krrsig:"):
+ keyval = line[i].split(":")
+ keyprop.metadata["KRRSIGState"] = keyval[1]
+ elif line[i].startswith("zrrsig:"):
+ keyval = line[i].split(":")
+ keyprop.metadata["ZRRSIGState"] = keyval[1]
+ elif line[i].startswith("ds:"):
+ keyval = line[i].split(":")
+ keyprop.metadata["DSState"] = keyval[1]
+ elif line[i].startswith("offset:"):
+ keyval = line[i].split(":")
+ keyprop.properties["offset"] = timedelta(seconds=int(keyval[1]))
+ else:
+ assert False, f"undefined optional data {line[i]}"
+
+ proplist.append(keyprop)
+
+ return proplist
# Tests #
###############################################################################
-#
-# dnssec-keygen
-#
-set_zone "kasp"
-set_policy "kasp" "4" "200"
-set_server "keys" "10.53.0.1"
-
-n=$((n + 1))
-echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)"
-ret=0
-$KEYGEN -K keys -k "$POLICY" -l kasp.conf "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
-lines=$(wc -l <"keygen.out.$POLICY.test$n")
-test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy kasp: $lines"
-# Temporarily don't log errors because we are searching multiple files.
-disable_logerror
-
-# Key properties.
-set_keyrole "KEY1" "csk"
-set_keylifetime "KEY1" "31536000"
-set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY1" "yes"
-set_zonesigning "KEY1" "yes"
-
-set_keyrole "KEY2" "ksk"
-set_keylifetime "KEY2" "31536000"
-set_keyalgorithm "KEY2" "8" "RSASHA256" "2048"
-set_keysigning "KEY2" "yes"
-set_zonesigning "KEY2" "no"
-
-set_keyrole "KEY3" "zsk"
-set_keylifetime "KEY3" "2592000"
-set_keyalgorithm "KEY3" "8" "RSASHA256" "2048"
-set_keysigning "KEY3" "no"
-set_zonesigning "KEY3" "yes"
-
-set_keyrole "KEY4" "zsk"
-set_keylifetime "KEY4" "16070400"
-set_keyalgorithm "KEY4" "8" "RSASHA256" "3072"
-set_keysigning "KEY4" "no"
-set_zonesigning "KEY4" "yes"
-
-lines=$(get_keyids "$DIR" "$ZONE" | wc -l)
-test "$lines" -eq $NUM_KEYS || log_error "bad number of key ids"
-status=$((status + ret))
-
-ids=$(get_keyids "$DIR" "$ZONE")
-for id in $ids; do
- # There are four key files with the same algorithm.
- # Check them until a match is found.
- ret=0 && check_key "KEY1" "$id"
- test "$ret" -eq 0 && continue
-
- ret=0 && check_key "KEY2" "$id"
- test "$ret" -eq 0 && continue
-
- ret=0 && check_key "KEY3" "$id"
- test "$ret" -eq 0 && continue
-
- ret=0 && check_key "KEY4" "$id"
-
- # If ret is still non-zero, non of the files matched.
- test "$ret" -eq 0 || echo_i "failed"
- status=$((status + ret))
-done
-# Turn error logs on again.
-enable_logerror
-
-n=$((n + 1))
-echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
-ret=0
-set_zone "kasp"
-set_policy "default" "1" "3600"
-set_server "." "10.53.0.1"
-# Key properties.
-key_clear "KEY1"
-set_keyrole "KEY1" "csk"
-set_keylifetime "KEY1" "0"
-set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
-set_keysigning "KEY1" "yes"
-set_zonesigning "KEY1" "yes"
-
-key_clear "KEY2"
-key_clear "KEY3"
-key_clear "KEY4"
-
-$KEYGEN -G -k "$POLICY" "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
-lines=$(wc -l <"keygen.out.$POLICY.test$n")
-test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy default: $lines"
-# Temporarily adjust max search depth for this test
-MAXDEPTH=1
-ids=$(get_keyids "$DIR" "$ZONE")
-MAXDEPTH=3
-echo_i "found in dir $DIR for zone $ZONE the following keytags: $ids"
-for id in $ids; do
- check_key "KEY1" "$id"
- test "$ret" -eq 0 && key_save KEY1
- check_keytimes
-done
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-#
-# dnssec-settime
-#
-
-# These test builds upon the latest created key with dnssec-keygen and uses the
-# environment variables BASE_FILE, KEY_FILE, PRIVATE_FILE and STATE_FILE.
-CMP_FILE="${BASE_FILE}.cmp"
-n=$((n + 1))
-echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)"
-ret=0
-cp "$STATE_FILE" "$CMP_FILE"
-$SETTIME -P +3600 "$BASE_FILE" >/dev/null || log_error "settime failed"
-grep "; Publish: " "$KEY_FILE" >/dev/null || log_error "mismatch published in $KEY_FILE"
-grep "Publish: " "$PRIVATE_FILE" >/dev/null || log_error "mismatch published in $PRIVATE_FILE"
-diff "$CMP_FILE" "$STATE_FILE" || log_error "unexpected file change in $STATE_FILE"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-settime -s' also sets publish time metadata and states in key state file ($n)"
-ret=0
-cp "$STATE_FILE" "$CMP_FILE"
-now=$(date +%Y%m%d%H%M%S)
-$SETTIME -s -P "$now" -g "omnipresent" -k "rumoured" "$now" -z "omnipresent" "$now" -r "rumoured" "$now" -d "hidden" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
-set_keystate "KEY1" "GOAL" "omnipresent"
-set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
-set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
-set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
-set_keystate "KEY1" "STATE_DS" "hidden"
-check_key "KEY1" "$id"
-test "$ret" -eq 0 && key_save KEY1
-set_keytime "KEY1" "PUBLISHED" "${now}"
-check_keytimes
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-settime -s' also unsets publish time metadata and states in key state file ($n)"
-ret=0
-cp "$STATE_FILE" "$CMP_FILE"
-$SETTIME -s -P "none" -g "none" -k "none" "$now" -z "none" "$now" -r "none" "$now" -d "none" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
-set_keystate "KEY1" "GOAL" "none"
-set_keystate "KEY1" "STATE_DNSKEY" "none"
-set_keystate "KEY1" "STATE_KRRSIG" "none"
-set_keystate "KEY1" "STATE_ZRRSIG" "none"
-set_keystate "KEY1" "STATE_DS" "none"
-check_key "KEY1" "$id"
-test "$ret" -eq 0 && key_save KEY1
-set_keytime "KEY1" "PUBLISHED" "none"
-check_keytimes
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase) ($n)"
-ret=0
-cp "$STATE_FILE" "$CMP_FILE"
-now=$(date +%Y%m%d%H%M%S)
-$SETTIME -s -A "$now" -g "HIDDEN" -k "UNRETENTIVE" "$now" -z "UNRETENTIVE" "$now" -r "OMNIPRESENT" "$now" -d "OMNIPRESENT" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
-set_keystate "KEY1" "GOAL" "hidden"
-set_keystate "KEY1" "STATE_DNSKEY" "unretentive"
-set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
-set_keystate "KEY1" "STATE_ZRRSIG" "unretentive"
-set_keystate "KEY1" "STATE_DS" "omnipresent"
-check_key "KEY1" "$id"
-test "$ret" -eq 0 && key_save KEY1
-set_keytime "KEY1" "ACTIVE" "${now}"
-check_keytimes
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
#
# named
#
--- /dev/null
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+import os
+import shutil
+
+from datetime import timedelta
+
+import pytest
+
+import isctest
+from isctest.kasp import (
+ KeyProperties,
+ KeyTimingMetadata,
+)
+
+pytestmark = pytest.mark.extra_artifacts(
+ [
+ "K*.private",
+ "K*.backup",
+ "K*.cmp",
+ "K*.key",
+ "K*.state",
+ "*.created",
+ "dig.out*",
+ "keyevent.out.*",
+ "keygen.out.*",
+ "keys",
+ "published.test*",
+ "python.out.*",
+ "retired.test*",
+ "rndc.dnssec.*.out.*",
+ "rndc.zonestatus.out.*",
+ "rrsig.out.*",
+ "created.key-*",
+ "unused.key-*",
+ "verify.out.*",
+ "zone.out.*",
+ "ns*/K*.private",
+ "ns*/K*.key",
+ "ns*/K*.state",
+ "ns*/*.db",
+ "ns*/*.db.infile",
+ "ns*/*.db.signed",
+ "ns*/*.jbk",
+ "ns*/*.jnl",
+ "ns*/dsset-*",
+ "ns*/keygen.out.*",
+ "ns*/keys",
+ "ns*/ksk",
+ "ns*/ksk/K*",
+ "ns*/zsk",
+ "ns*/zsk",
+ "ns*/zsk/K*",
+ "ns*/named-fips.conf",
+ "ns*/settime.out.*",
+ "ns*/signer.out.*",
+ "ns*/zones",
+ "ns*/policies/*.conf",
+ "ns*/*.zsk1",
+ "ns*/*.zsk2",
+ "ns3/legacy-keys.*",
+ "ns3/dynamic-signed-inline-signing.kasp.db.signed.signed",
+ ]
+)
+
+
+def test_kasp_dnssec_keygen():
+ def keygen(zone, policy, keydir=None):
+ if keydir is None:
+ keydir = "."
+
+ keygen_command = [
+ os.environ.get("KEYGEN"),
+ "-K",
+ keydir,
+ "-k",
+ policy,
+ "-l",
+ "kasp.conf",
+ zone,
+ ]
+
+ 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",
+ f"zsk {lifetime['P30D']} 8 2048",
+ f"zsk {lifetime['P6M']} 8 3072",
+ ]
+ keydir="keys"
+ out = keygen("kasp", "kasp", keydir)
+ keys = isctest.kasp.keystr_to_keylist(out, keydir)
+ expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)
+ isctest.kasp.check_keys("kasp", keys, expected)
+
+ # check that 'dnssec-keygen -k' (default policy) creates valid files.
+ keyprops = ["csk 0 13 256"]
+ out = keygen("kasp", "default")
+ keys = isctest.kasp.keystr_to_keylist(out)
+ expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+ isctest.kasp.check_keys("kasp", keys, expected)
+
+ # check that 'dnssec-settime' by default does not edit key state file.
+ key = keys[0]
+ shutil.copyfile(key.privatefile, f"{key.privatefile}.backup")
+ shutil.copyfile(key.keyfile, f"{key.keyfile}.backup")
+ shutil.copyfile(key.statefile, f"{key.statefile}.backup")
+
+ created = key.get_timing("Created")
+ publish = key.get_timing("Publish") + timedelta(hours=1)
+ settime = [
+ os.environ.get("SETTIME"),
+ "-P",
+ str(publish),
+ key.path,
+ ]
+ out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+
+ isctest.check.file_contents_equal(f"{key.statefile}", f"{key.statefile}.backup")
+ assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
+ assert key.get_metadata("Publish", file=key.keyfile, comment=True) == str(publish)
+
+ # check that 'dnssec-settime -s' also sets publish time metadata and
+ # states in key state file.
+ now = KeyTimingMetadata.now()
+ goal = "omnipresent"
+ dnskey = "rumoured"
+ krrsig = "rumoured"
+ zrrsig = "omnipresent"
+ ds = "hidden"
+ keyprops = [
+ f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
+ ]
+ expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+ expected[0].timing = {
+ "Generated": created,
+ "Published": now,
+ "Active": created,
+ "DNSKEYChange": now,
+ "KRRSIGChange": now,
+ "ZRRSIGChange": now,
+ "DSChange": now,
+ }
+
+ settime = [
+ os.environ.get("SETTIME"),
+ "-s",
+ "-P",
+ str(now),
+ "-g",
+ goal,
+ "-k",
+ dnskey,
+ str(now),
+ "-r",
+ krrsig,
+ str(now),
+ "-z",
+ zrrsig,
+ str(now),
+ "-d",
+ ds,
+ str(now),
+ key.path,
+ ]
+ out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+ isctest.kasp.check_keys("kasp", keys, expected)
+ isctest.kasp.check_keytimes(keys, expected)
+
+ # check that 'dnssec-settime -s' also unsets publish time metadata and
+ # states in key state file.
+ now = KeyTimingMetadata.now()
+ keyprops = ["csk 0 13 256"]
+ expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+ expected[0].timing = {
+ "Generated": created,
+ "Active": created,
+ }
+
+ settime = [
+ os.environ.get("SETTIME"),
+ "-s",
+ "-P",
+ "none",
+ "-g",
+ "none",
+ "-k",
+ "none",
+ str(now),
+ "-z",
+ "none",
+ str(now),
+ "-r",
+ "none",
+ str(now),
+ "-d",
+ "none",
+ str(now),
+ key.path,
+ ]
+ out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+ isctest.kasp.check_keys("kasp", keys, expected)
+ isctest.kasp.check_keytimes(keys, expected)
+
+ # check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase)
+ soon = now + timedelta(hours=2)
+ goal = "hidden"
+ dnskey = "unretentive"
+ krrsig = "omnipresent"
+ zrrsig = "unretentive"
+ ds = "omnipresent"
+ keyprops = [
+ f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
+ ]
+ expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
+ expected[0].timing = {
+ "Generated": created,
+ "Active": soon,
+ "DNSKEYChange": soon,
+ "KRRSIGChange": soon,
+ "ZRRSIGChange": soon,
+ "DSChange": soon,
+ }
+
+ settime = [
+ os.environ.get("SETTIME"),
+ "-s",
+ "-A",
+ str(soon),
+ "-g",
+ "HIDDEN",
+ "-k",
+ "UNRETENTIVE",
+ str(soon),
+ "-z",
+ "UNRETENTIVE",
+ str(soon),
+ "-r",
+ "OMNIPRESENT",
+ str(soon),
+ "-d",
+ "OMNIPRESENT",
+ str(soon),
+ key.path,
+ ]
+ out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
+ isctest.kasp.check_keys("kasp", keys, expected)
+ isctest.kasp.check_keytimes(keys, expected)