]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
rollover-algo-ksk-zsk: From setup.sh to pytest bootstrap
authorMatthijs Mekking <matthijs@isc.org>
Thu, 27 Nov 2025 11:11:35 +0000 (12:11 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Fri, 19 Dec 2025 10:47:50 +0000 (11:47 +0100)
Symlink ns1 and ns2 to rollover/ns1 and rollover/ns2.
Symlink ns3/template.db.j2.manual to rollover/ns3/template.db.j2.manual.

The RSASHA256 keys are generated with dnssec-keygen, without a policy
provided. Thus we have to fake the lifetime for these keys.

Signing has to be done without the -z option, because the KSK should
not sign all records in case of a KSK/ZSK split. Update the signing
code to allow for extra options when signing with CSK only.

bin/tests/system/rollover-algo-ksk-zsk/ns1 [new symlink]
bin/tests/system/rollover-algo-ksk-zsk/ns2 [new symlink]
bin/tests/system/rollover-algo-ksk-zsk/ns3/kasp.conf [moved from bin/tests/system/rollover-algo-ksk-zsk/ns3/kasp.conf.j2 with 100% similarity]
bin/tests/system/rollover-algo-ksk-zsk/ns3/template.db.in [deleted symlink]
bin/tests/system/rollover-algo-ksk-zsk/ns3/template.db.j2.manual [new symlink]
bin/tests/system/rollover-algo-ksk-zsk/ns3/trusted.conf.j2 [new symlink]
bin/tests/system/rollover-algo-ksk-zsk/setup.sh [deleted file]
bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_initial.py
bin/tests/system/rollover-algo-ksk-zsk/tests_rollover_algo_ksk_zsk_reconfig.py
bin/tests/system/rollover/setup.py

diff --git a/bin/tests/system/rollover-algo-ksk-zsk/ns1 b/bin/tests/system/rollover-algo-ksk-zsk/ns1
new file mode 120000 (symlink)
index 0000000..76608be
--- /dev/null
@@ -0,0 +1 @@
+../rollover/ns1
\ No newline at end of file
diff --git a/bin/tests/system/rollover-algo-ksk-zsk/ns2 b/bin/tests/system/rollover-algo-ksk-zsk/ns2
new file mode 120000 (symlink)
index 0000000..41a09bb
--- /dev/null
@@ -0,0 +1 @@
+../rollover/ns2
\ No newline at end of file
diff --git a/bin/tests/system/rollover-algo-ksk-zsk/ns3/template.db.in b/bin/tests/system/rollover-algo-ksk-zsk/ns3/template.db.in
deleted file mode 120000 (symlink)
index ce6d526..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../../rollover/ns3/template.db.in
\ No newline at end of file
diff --git a/bin/tests/system/rollover-algo-ksk-zsk/ns3/template.db.j2.manual b/bin/tests/system/rollover-algo-ksk-zsk/ns3/template.db.j2.manual
new file mode 120000 (symlink)
index 0000000..38619a0
--- /dev/null
@@ -0,0 +1 @@
+../../rollover/ns3/template.db.j2.manual
\ No newline at end of file
diff --git a/bin/tests/system/rollover-algo-ksk-zsk/ns3/trusted.conf.j2 b/bin/tests/system/rollover-algo-ksk-zsk/ns3/trusted.conf.j2
new file mode 120000 (symlink)
index 0000000..cb0be77
--- /dev/null
@@ -0,0 +1 @@
+../../_common/trusted.conf.j2
\ No newline at end of file
diff --git a/bin/tests/system/rollover-algo-ksk-zsk/setup.sh b/bin/tests/system/rollover-algo-ksk-zsk/setup.sh
deleted file mode 100644 (file)
index bc651b9..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/bin/sh -e
-
-# 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.
-
-# shellcheck source=conf.sh
-. ../conf.sh
-
-cd "ns3"
-
-setup() {
-  zone="$1"
-  echo_i "setting up zone: $zone"
-  zonefile="${zone}.db"
-  infile="${zone}.db.infile"
-}
-
-# Make lines shorter by storing key states in environment variables.
-H="HIDDEN"
-R="RUMOURED"
-O="OMNIPRESENT"
-U="UNRETENTIVE"
-
-#
-# The zones at algorithm-roll.$tld represent the various steps of a ZSK/KSK
-# algorithm rollover.
-#
-
-for tld in kasp manual; do
-  # Step 1:
-  # Introduce the first key. This will immediately be active.
-  setup step1.algorithm-roll.$tld
-  echo "$zone" >>zones
-  TactN="now-7d"
-  TsbmN="now-161h"
-  ksktimes="-P ${TactN} -A ${TactN}"
-  zsktimes="-P ${TactN} -A ${TactN}"
-  KSK=$($KEYGEN -a RSASHA256 -L 3600 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
-  ZSK=$($KEYGEN -a RSASHA256 -L 3600 $zsktimes $zone 2>keygen.out.$zone.2)
-  $SETTIME -s -g $O -k $O $TactN -r $O $TactN -d $O $TactN "$KSK" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $O -k $O $TactN -z $O $TactN "$ZSK" >settime.out.$zone.2 2>&1
-  cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile"
-  private_type_record $zone 8 "$KSK" >>"$infile"
-  private_type_record $zone 8 "$ZSK" >>"$infile"
-  cp $infile $zonefile
-  $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-
-  # Step 2:
-  # After the publication interval has passed the DNSKEY is OMNIPRESENT.
-  setup step2.algorithm-roll.$tld
-  # The time passed since the new algorithm keys have been introduced is 3 hours.
-  TpubN1="now-3h"
-  # Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp = now - 3h + 6h + 1h = now + 4h
-  TsbmN1="now+4h"
-  ksk1times="-P ${TactN}  -A ${TactN}  -P sync ${TsbmN} -I ${TsbmN1}"
-  zsk1times="-P ${TactN}  -A ${TactN}  -I ${TsbmN1}"
-  ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
-  zsk2times="-P ${TpubN1} -A ${TpubN1}"
-  KSK1=$($KEYGEN -a RSASHA256 -L 3600 -f KSK $ksk1times $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a RSASHA256 -L 3600 $zsk1times $zone 2>keygen.out.$zone.2)
-  KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $ksk2times $zone 2>keygen.out.$zone.3)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsk2times $zone 2>keygen.out.$zone.4)
-  $SETTIME -s -g $H -k $O $TactN -r $O $TactN -d $O $TactN "$KSK1" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $O $TactN -z $O $TactN "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 "$KSK2" >settime.out.$zone.3 2>&1
-  $SETTIME -s -g $O -k $R $TpubN1 -z $R $TpubN1 "$ZSK2" >settime.out.$zone.4 2>&1
-  # Fake lifetime of old algorithm keys.
-  echo "Lifetime: 0" >>"${KSK1}.state"
-  echo "Lifetime: 0" >>"${ZSK1}.state"
-  cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone 8 "$KSK1" >>"$infile"
-  private_type_record $zone 8 "$ZSK1" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK2" >>"$infile"
-  cp $infile $zonefile
-  $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-
-  # Step 3:
-  # The zone signatures are also OMNIPRESENT.
-  setup step3.algorithm-roll.$tld
-  # The time passed since the new algorithm keys have been introduced is 7 hours.
-  TpubN1="now-7h"
-  TsbmN1="now"
-  ksk1times="-P ${TactN}  -A ${TactN}  -P sync ${TsbmN}  -I ${TsbmN1}"
-  zsk1times="-P ${TactN}  -A ${TactN}  -I ${TsbmN1}"
-  ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
-  zsk2times="-P ${TpubN1} -A ${TpubN1}"
-  KSK1=$($KEYGEN -a RSASHA256 -L 3600 -f KSK $ksk1times $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a RSASHA256 -L 3600 $zsk1times $zone 2>keygen.out.$zone.2)
-  KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $ksk2times $zone 2>keygen.out.$zone.3)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsk2times $zone 2>keygen.out.$zone.4)
-  $SETTIME -s -g $H -k $O $TactN -r $O $TactN -d $O $TactN "$KSK1" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $O $TactN -z $O $TactN "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -d $H $TpubN1 "$KSK2" >settime.out.$zone.3 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" >settime.out.$zone.4 2>&1
-  # Fake lifetime of old algorithm keys.
-  echo "Lifetime: 0" >>"${KSK1}.state"
-  echo "Lifetime: 0" >>"${ZSK1}.state"
-  cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone 8 "$KSK1" >>"$infile"
-  private_type_record $zone 8 "$ZSK1" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK2" >>"$infile"
-  cp $infile $zonefile
-  $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-
-  # Step 4:
-  # The DS is swapped and can become OMNIPRESENT.
-  setup step4.algorithm-roll.$tld
-  # The time passed since the DS has been swapped is 3 hours.
-  TpubN1="now-10h"
-  TsbmN1="now-3h"
-  ksk1times="-P ${TactN}  -A ${TactN}  -P sync ${TsbmN}  -I ${TsbmN1}"
-  zsk1times="-P ${TactN}  -A ${TactN}  -I ${TsbmN1}"
-  ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
-  zsk2times="-P ${TpubN1} -A ${TpubN1}"
-  KSK1=$($KEYGEN -a RSASHA256 -L 3600 -f KSK $ksk1times $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a RSASHA256 -L 3600 $zsk1times $zone 2>keygen.out.$zone.2)
-  KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $ksk2times $zone 2>keygen.out.$zone.3)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsk2times $zone 2>keygen.out.$zone.4)
-  $SETTIME -s -g $H -k $O $TactN -r $O $TactN -d $U $TsbmN1 -D ds $TsbmN1 "$KSK1" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $O $TactN -z $O $TactN "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -d $R $TsbmN1 -P ds $TsbmN1 "$KSK2" >settime.out.$zone.3 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" >settime.out.$zone.4 2>&1
-  # Fake lifetime of old algorithm keys.
-  echo "Lifetime: 0" >>"${KSK1}.state"
-  echo "Lifetime: 0" >>"${ZSK1}.state"
-  cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone 8 "$KSK1" >>"$infile"
-  private_type_record $zone 8 "$ZSK1" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK2" >>"$infile"
-  cp $infile $zonefile
-  $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-
-  # Step 5:
-  # The DNSKEY is removed long enough to be HIDDEN.
-  setup step5.algorithm-roll.$tld
-  # The time passed since the DNSKEY has been removed is 2 hours.
-  TpubN1="now-12h"
-  TsbmN1="now-5h"
-  ksk1times="-P ${TactN}  -A ${TactN}  -P sync ${TsbmN}  -I ${TsbmN1}"
-  zsk1times="-P ${TactN}  -A ${TactN}  -I ${TsbmN1}"
-  ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
-  zsk2times="-P ${TpubN1} -A ${TpubN1}"
-  KSK1=$($KEYGEN -a RSASHA256 -L 3600 -f KSK $ksk1times $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a RSASHA256 -L 3600 $zsk1times $zone 2>keygen.out.$zone.2)
-  KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $ksk2times $zone 2>keygen.out.$zone.3)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsk2times $zone 2>keygen.out.$zone.4)
-  $SETTIME -s -g $H -k $U $TsbmN1 -r $U $TsbmN1 -d $H $TsbmN1 "$KSK1" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $U $TsbmN1 -z $U $TsbmN1 "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -d $O $TsbmN1 "$KSK2" >settime.out.$zone.3 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" >settime.out.$zone.4 2>&1
-  # Fake lifetime of old algorithm keys.
-  echo "Lifetime: 0" >>"${KSK1}.state"
-  echo "Lifetime: 0" >>"${ZSK1}.state"
-  cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone 8 "$KSK1" >>"$infile"
-  private_type_record $zone 8 "$ZSK1" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK2" >>"$infile"
-  cp $infile $zonefile
-  $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-
-  # Step 6:
-  # The RRSIGs have been removed long enough to be HIDDEN.
-  setup step6.algorithm-roll.$tld
-  # Additional time passed: 7h.
-  TpubN1="now-19h"
-  TsbmN1="now-12h"
-  ksk1times="-P ${TactN}  -A ${TactN}  -P sync ${TsbmN}  -I ${TsbmN1}"
-  zsk1times="-P ${TactN}  -A ${TactN}  -I ${TsbmN1}"
-  ksk2times="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
-  zsk2times="-P ${TpubN1} -A ${TpubN1}"
-  KSK1=$($KEYGEN -a RSASHA256 -L 3600 -f KSK $ksk1times $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a RSASHA256 -L 3600 $zsk1times $zone 2>keygen.out.$zone.2)
-  KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $ksk2times $zone 2>keygen.out.$zone.3)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsk2times $zone 2>keygen.out.$zone.4)
-  $SETTIME -s -g $H -k $H $TsbmN1 -r $U $TsbmN1 -d $H $TsbmN1 "$KSK1" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $H $TsbmN1 -z $U $TsbmN1 "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -d $O $TsbmN1 "$KSK2" >settime.out.$zone.3 2>&1
-  $SETTIME -s -g $O -k $O $TpubN1 -z $R $TpubN1 "$ZSK2" >settime.out.$zone.4 2>&1
-  # Fake lifetime of old algorithm keys.
-  echo "Lifetime: 0" >>"${KSK1}.state"
-  echo "Lifetime: 0" >>"${ZSK1}.state"
-  cat template.db.in "${KSK1}.key" "${ZSK1}.key" "${KSK2}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone 8 "$KSK1" >>"$infile"
-  private_type_record $zone 8 "$ZSK1" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK2" >>"$infile"
-  cp $infile $zonefile
-  $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-done
index 32eea7d014ae988d50634d38303628fd350b082e..2f74cd0fa42a7be0af4a6c925d2fa93c31bc3e59 100644 (file)
@@ -22,6 +22,35 @@ from rollover.common import (
     TIMEDELTA,
     ALGOROLL_CONFIG,
 )
+from rollover.setup import (
+    configure_root,
+    configure_tld,
+    configure_algo_ksk_zsk,
+)
+
+
+def bootstrap():
+    data = {
+        "tlds": [],
+        "trust_anchors": [],
+    }
+
+    tlds = []
+    for tld_name in [
+        "kasp",
+        "manual",
+    ]:
+        delegations = configure_algo_ksk_zsk(tld_name, reconfig=False)
+
+        tld = configure_tld(tld_name, delegations)
+        tlds.append(tld)
+
+        data["tlds"].append(tld_name)
+
+    ta = configure_root(tlds)
+    data["trust_anchors"].append(ta)
+
+    return data
 
 
 @pytest.mark.parametrize(
@@ -31,7 +60,7 @@ from rollover.common import (
         param("manual"),
     ],
 )
-def test_algoroll_ksk_zsk_initial(ns3, tld):
+def test_algoroll_ksk_zsk_initial(tld, ns3):
     config = ALGOROLL_CONFIG
     policy = f"rsasha256-{tld}"
     zone = f"step1.algorithm-roll.{tld}"
index 15cf0f9ddd263f4f4afd4980fd59aa501fc9d8d7..67de084d461f1b199aeba0ae98ad6401b4040003 100644 (file)
@@ -32,12 +32,41 @@ from rollover.common import (
     DURATION,
     TIMEDELTA,
 )
+from rollover.setup import (
+    configure_root,
+    configure_tld,
+    configure_algo_ksk_zsk,
+)
 
 CONFIG = ALGOROLL_CONFIG
 POLICY = "ecdsa256"
 TIME_PASSED = 0  # set in reconfigure() fixture
 
 
+def bootstrap():
+    data = {
+        "tlds": [],
+        "trust_anchors": [],
+    }
+
+    tlds = []
+    for tld_name in [
+        "kasp",
+        "manual",
+    ]:
+        delegations = configure_algo_ksk_zsk(tld_name, reconfig=True)
+
+        tld = configure_tld(tld_name, delegations)
+        tlds.append(tld)
+
+        data["tlds"].append(tld_name)
+
+    ta = configure_root(tlds)
+    data["trust_anchors"].append(ta)
+
+    return data
+
+
 @pytest.fixture(scope="module", autouse=True)
 def after_servers_start(ns3, templates):
     global TIME_PASSED  # pylint: disable=global-statement
index 5124bd46ab4ae0333af7b7316cae45e710c0d50c..f52135a83db3abd648b6899d808eaa1c1385d35b 100644 (file)
@@ -90,7 +90,16 @@ def configure_root(delegations: List[Zone]) -> TrustAnchor:
     return ksk.into_ta("static-ds")
 
 
-def render_and_sign_zone(zonename: str, keys: List[str]):
+def fake_lifetime(keys: List[str]):
+    """
+    Fake lifetime of old algorithm keys.
+    """
+    for key in keys:
+        with open(f"ns3/{key}.state", "a") as statefile:
+            statefile.write("Lifetime: 0\n")
+
+
+def render_and_sign_zone(zonename: str, keys: List[str], extra_options: str = ""):
     dnskeys = []
     privaterrs = []
     for key_name in keys:
@@ -109,8 +118,8 @@ def render_and_sign_zone(zonename: str, keys: List[str]):
     }
     templates.render(f"ns3/{outfile}", tdata, template=f"ns3/{template}")
 
-    signer = CmdHelper("SIGNER", "-S -g -x -z -s now-1h -e now+2w -O raw")
-    signer(f"-o {zonename} -f {outfile}.signed {outfile}", cwd="ns3")
+    signer = CmdHelper("SIGNER", "-S -g -x -s now-1h -e now+2w -O raw")
+    signer(f"{extra_options} -o {zonename} -f {outfile}.signed {outfile}", cwd="ns3")
 
 
 def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zone]:
@@ -136,7 +145,7 @@ def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zo
         cwd="ns3",
     )
     # Signing.
-    render_and_sign_zone(zonename, [csk_name])
+    render_and_sign_zone(zonename, [csk_name], extra_options="-z")
 
     if reconfig:
         # Step 2:
@@ -160,7 +169,7 @@ def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zo
             cwd="ns3",
         )
         # Signing.
-        render_and_sign_zone(zonename, [csk1_name, csk2_name])
+        render_and_sign_zone(zonename, [csk1_name, csk2_name], extra_options="-z")
 
         # Step 3:
         # The zone signatures are also OMNIPRESENT.
@@ -184,7 +193,7 @@ def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zo
             cwd="ns3",
         )
         # Signing.
-        render_and_sign_zone(zonename, [csk1_name, csk2_name])
+        render_and_sign_zone(zonename, [csk1_name, csk2_name], extra_options="-z")
 
         # Step 4:
         # The DS is swapped and can become OMNIPRESENT.
@@ -208,7 +217,7 @@ def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zo
             cwd="ns3",
         )
         # Signing.
-        render_and_sign_zone(zonename, [csk1_name, csk2_name])
+        render_and_sign_zone(zonename, [csk1_name, csk2_name], extra_options="-z")
 
         # Step 5:
         # The DNSKEY is removed long enough to be HIDDEN.
@@ -232,7 +241,7 @@ def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zo
             cwd="ns3",
         )
         # Signing.
-        render_and_sign_zone(zonename, [csk1_name, csk2_name])
+        render_and_sign_zone(zonename, [csk1_name, csk2_name], extra_options="-z")
 
         # Step 6:
         # The RRSIGs have been removed long enough to be HIDDEN.
@@ -256,6 +265,245 @@ def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zo
             cwd="ns3",
         )
         # Signing.
-        render_and_sign_zone(zonename, [csk1_name, csk2_name])
+        render_and_sign_zone(zonename, [csk1_name, csk2_name], extra_options="-z")
+
+    return zones
+
+
+def configure_algo_ksk_zsk(tld: str, reconfig: bool = False) -> List[Zone]:
+    # The zones at algorithm-roll.$tld represent the various steps of a ZSK/KSK
+    # algorithm rollover.
+    zones = []
+    zone = f"algorithm-roll.{tld}"
+    keygen = CmdHelper("KEYGEN", "-L 3600")
+    settime = CmdHelper("SETTIME", "-s")
+
+    # Step 1:
+    # Introduce the first key. This will immediately be active.
+    zonename = f"step1.{zone}"
+    zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+    isctest.log.info(f"setup {zonename}")
+    TactN = "now-7d"
+    TsbmN = "now-161h"
+    keytimes = f"-P {TactN} -A {TactN}"
+    # Key generation.
+    ksk_name = keygen(f"-a RSASHA256 -f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk_name = keygen(f"-a RSASHA256 {keytimes} {zonename}", cwd="ns3").strip()
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk_name}",
+        cwd="ns3",
+    )
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} {zsk_name}",
+        cwd="ns3",
+    )
+    # Signing.
+    render_and_sign_zone(zonename, [ksk_name, zsk_name])
+
+    if reconfig:
+        # Step 2:
+        # After the publication interval has passed the DNSKEY is OMNIPRESENT.
+        zonename = f"step2.{zone}"
+        zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+        isctest.log.info(f"setup {zonename}")
+        # The time passed since the new algorithm keys have been introduced is 3 hours.
+        # Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp = now - 3h + 6h + 1h = now + 4h
+        TpubN1 = "now-3h"
+        TsbmN1 = "now+4h"
+        ksk1times = f"-P {TactN}  -A {TactN}  -P sync {TsbmN} -I {TsbmN1}"
+        zsk1times = f"-P {TactN}  -A {TactN}  -I {TsbmN1}"
+        ksk2times = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
+        zsk2times = f"-P {TpubN1} -A {TpubN1}"
+        # Key generation.
+        ksk1_name = keygen(
+            f"-a RSASHA256 -f KSK {ksk1times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk1_name = keygen(f"-a RSASHA256 {zsk1times} {zonename}", cwd="ns3").strip()
+        ksk2_name = keygen(
+            f"-a ECDSA256 -f KSK {ksk2times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk2_name = keygen(f"-a ECDSA256 {zsk2times} {zonename}", cwd="ns3").strip()
+        settime(
+            f"-g HIDDEN -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g HIDDEN -k OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} {zsk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k RUMOURED {TpubN1} -r RUMOURED {TpubN1} -d HIDDEN {TpubN1} {ksk2_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k RUMOURED {TpubN1} -z RUMOURED {TpubN1} {zsk2_name}",
+            cwd="ns3",
+        )
+        # Signing.
+        fake_lifetime([ksk1_name, zsk1_name])
+        render_and_sign_zone(zonename, [ksk1_name, zsk1_name, ksk2_name, zsk2_name])
+
+        # Step 3:
+        # The zone signatures are also OMNIPRESENT.
+        zonename = f"step3.{zone}"
+        zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+        isctest.log.info(f"setup {zonename}")
+        # The time passed since the new algorithm keys have been introduced is 7 hours.
+        TpubN1 = "now-7h"
+        TsbmN1 = "now"
+        ksk1times = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
+        zsk1times = f"-P {TactN} -A {TactN} -I {TsbmN1}"
+        ksk2times = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
+        zsk2times = f"-P {TpubN1} -A {TpubN1}"
+        # Key generation.
+        ksk1_name = keygen(
+            f"-a RSASHA256 -f KSK {ksk1times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk1_name = keygen(f"-a RSASHA256 {zsk1times} {zonename}", cwd="ns3").strip()
+        ksk2_name = keygen(
+            f"-a ECDSA256 -f KSK {ksk2times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk2_name = keygen(f"-a ECDSA256 {zsk2times} {zonename}", cwd="ns3").strip()
+        settime(
+            f"-g HIDDEN -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g HIDDEN -k OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} {zsk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -d HIDDEN {TpubN1} {ksk2_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -z RUMOURED {TpubN1} {zsk2_name}",
+            cwd="ns3",
+        )
+        # Signing.
+        fake_lifetime([ksk1_name, zsk1_name])
+        render_and_sign_zone(zonename, [ksk1_name, zsk1_name, ksk2_name, zsk2_name])
+
+        # Step 4:
+        # The DS is swapped and can become OMNIPRESENT.
+        zonename = f"step4.{zone}"
+        zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+        isctest.log.info(f"setup {zonename}")
+        # The time passed since the DS has been swapped is 3 hours.
+        TpubN1 = "now-10h"
+        TsbmN1 = "now-3h"
+        ksk1times = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
+        zsk1times = f"-P {TactN} -A {TactN} -I {TsbmN1}"
+        ksk2times = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
+        zsk2times = f"-P {TpubN1} -A {TpubN1}"
+        # Key generation.
+        ksk1_name = keygen(
+            f"-a RSASHA256 -f KSK {ksk1times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk1_name = keygen(f"-a RSASHA256 {zsk1times} {zonename}", cwd="ns3").strip()
+        ksk2_name = keygen(
+            f"-a ECDSA256 -f KSK {ksk2times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk2_name = keygen(f"-a ECDSA256 {zsk2times} {zonename}", cwd="ns3").strip()
+        settime(
+            f"-g HIDDEN -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d UNRETENTIVE {TsbmN1} -D ds {TsbmN1} {ksk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g HIDDEN -k OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} {zsk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -d RUMOURED {TsbmN1} -P ds {TsbmN1} {ksk2_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -z RUMOURED {TpubN1} {zsk2_name}",
+            cwd="ns3",
+        )
+        # Signing.
+        fake_lifetime([ksk1_name, zsk1_name])
+        render_and_sign_zone(zonename, [ksk1_name, zsk1_name, ksk2_name, zsk2_name])
+
+        # Step 5:
+        # The DNSKEY is removed long enough to be HIDDEN.
+        zonename = f"step5.{zone}"
+        zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+        isctest.log.info(f"setup {zonename}")
+        # The time passed since the DNSKEY has been removed is 2 hours.
+        TpubN1 = "now-12h"
+        TsbmN1 = "now-5h"
+        ksk1times = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
+        zsk1times = f"-P {TactN} -A {TactN} -I {TsbmN1}"
+        ksk2times = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
+        zsk2times = f"-P {TpubN1} -A {TpubN1}"
+        # Key generation.
+        ksk1_name = keygen(
+            f"-a RSASHA256 -f KSK {ksk1times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk1_name = keygen(f"-a RSASHA256 {zsk1times} {zonename}", cwd="ns3").strip()
+        ksk2_name = keygen(
+            f"-a ECDSA256 -f KSK {ksk2times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk2_name = keygen(f"-a ECDSA256 {zsk2times} {zonename}", cwd="ns3").strip()
+        settime(
+            f"-g HIDDEN -k UNRETENTIVE {TsbmN1} -r UNRETENTIVE {TsbmN1} -d HIDDEN {TsbmN1} {ksk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g HIDDEN -k UNRETENTIVE {TsbmN1} -z UNRETENTIVE {TsbmN1} {zsk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -d OMNIPRESENT {TsbmN1} {ksk2_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -z RUMOURED {TpubN1} {zsk2_name}",
+            cwd="ns3",
+        )
+        # Signing.
+        fake_lifetime([ksk1_name, zsk1_name])
+        render_and_sign_zone(zonename, [ksk1_name, zsk1_name, ksk2_name, zsk2_name])
+
+        # Step 6:
+        # The RRSIGs have been removed long enough to be HIDDEN.
+        zonename = f"step6.{zone}"
+        zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+        isctest.log.info(f"setup {zonename}")
+        # Additional time passed: 7h.
+        TpubN1 = "now-19h"
+        TsbmN1 = "now-12h"
+        ksk1times = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
+        zsk1times = f"-P {TactN} -A {TactN} -I {TsbmN1}"
+        ksk2times = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
+        zsk2times = f"-P {TpubN1} -A {TpubN1}"
+        ksk1_name = keygen(
+            f"-a RSASHA256 -f KSK {ksk1times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk1_name = keygen(f"-a RSASHA256 {zsk1times} {zonename}", cwd="ns3").strip()
+        ksk2_name = keygen(
+            f"-a ECDSA256 -f KSK {ksk2times} {zonename}", cwd="ns3"
+        ).strip()
+        zsk2_name = keygen(f"-a ECDSA256 {zsk2times} {zonename}", cwd="ns3").strip()
+        settime(
+            f"-g HIDDEN -k HIDDEN {TsbmN1} -r UNRETENTIVE {TsbmN1} -d HIDDEN {TsbmN1} {ksk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g HIDDEN -k HIDDEN {TsbmN1} -z UNRETENTIVE {TsbmN1} {zsk1_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -d OMNIPRESENT {TsbmN1} {ksk2_name}",
+            cwd="ns3",
+        )
+        settime(
+            f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -z RUMOURED {TpubN1} {zsk2_name}",
+            cwd="ns3",
+        )
+        # Signing.
+        fake_lifetime([ksk1_name, zsk1_name])
+        render_and_sign_zone(zonename, [ksk1_name, zsk1_name, ksk2_name, zsk2_name])
 
     return zones