]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
rollover-zsk-prepub: From setup.sh to pytest bootstrap
authorMatthijs Mekking <matthijs@isc.org>
Fri, 28 Nov 2025 12:42:28 +0000 (13:42 +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.

Since the bootstrapping is done before the templates are rendered
automatically, replace @DEFAULT_ALGORITHM@ in ns3/kasp.conf.j2 to
ecdsa256 and rename to ns3/kasp.conf.

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

diff --git a/bin/tests/system/rollover-zsk-prepub/ns1 b/bin/tests/system/rollover-zsk-prepub/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-zsk-prepub/ns2 b/bin/tests/system/rollover-zsk-prepub/ns2
new file mode 120000 (symlink)
index 0000000..41a09bb
--- /dev/null
@@ -0,0 +1 @@
+../rollover/ns2
\ No newline at end of file
similarity index 78%
rename from bin/tests/system/rollover-zsk-prepub/ns3/kasp.conf.j2
rename to bin/tests/system/rollover-zsk-prepub/ns3/kasp.conf
index eac3293a6e2fd3e17818233ac8031d21988bf67a..c7a1bb5b22bb77c6c2fcc3494a2dc5e9f64f1ee7 100644 (file)
@@ -22,8 +22,8 @@ dnssec-policy "zsk-prepub-autosign" {
        purge-keys PT1H;
 
        keys {
-               ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
-               zsk key-directory lifetime P30D algorithm @DEFAULT_ALGORITHM@;
+               ksk key-directory lifetime unlimited algorithm ecdsa256;
+               zsk key-directory lifetime P30D algorithm ecdsa256;
        };
 
        zone-propagation-delay PT1H;
@@ -43,8 +43,8 @@ dnssec-policy "zsk-prepub-manual" {
        purge-keys PT1H;
 
        keys {
-               ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
-               zsk key-directory lifetime P30D algorithm @DEFAULT_ALGORITHM@;
+               ksk key-directory lifetime unlimited algorithm ecdsa256;
+               zsk key-directory lifetime P30D algorithm ecdsa256;
        };
 
        zone-propagation-delay PT1H;
diff --git a/bin/tests/system/rollover-zsk-prepub/ns3/template.db.in b/bin/tests/system/rollover-zsk-prepub/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-zsk-prepub/ns3/template.db.j2.manual b/bin/tests/system/rollover-zsk-prepub/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-zsk-prepub/ns3/trusted.conf.j2 b/bin/tests/system/rollover-zsk-prepub/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-zsk-prepub/setup.sh b/bin/tests/system/rollover-zsk-prepub/setup.sh
deleted file mode 100644 (file)
index 86bea47..0000000
+++ /dev/null
@@ -1,218 +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"
-  echo "$zone" >>zones
-}
-
-# Set in the key state files the Predecessor/Successor fields.
-# Key $1 is the predecessor of key $2.
-key_successor() {
-  id1=$(keyfile_to_key_id "$1")
-  id2=$(keyfile_to_key_id "$2")
-  echo "Predecessor: ${id1}" >>"${2}.state"
-  echo "Successor: ${id2}" >>"${1}.state"
-}
-
-# Make lines shorter by storing key states in environment variables.
-H="HIDDEN"
-R="RUMOURED"
-O="OMNIPRESENT"
-U="UNRETENTIVE"
-
-#
-# The zones at zsk-prepub.$tld represent the various steps of a ZSK
-# Pre-Publication rollover.
-#
-
-for tld in autosign manual; do
-  # Step 1:
-  # Introduce the first key. This will immediately be active.
-  setup step1.zsk-prepub.$tld
-  TactN="now-7d"
-  keytimes="-P ${TactN} -A ${TactN}"
-  KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
-  ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $keytimes $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 $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$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:
-  # It is time to pre-publish the successor ZSK.
-  setup step2.zsk-prepub.$tld
-  # According to RFC 7583:
-  # Tact(N) = now + Ipub - Lzsk = now + 26h - 30d
-  #         = now + 26h - 30d = now − 694h
-  TactN="now-694h"
-  keytimes="-P ${TactN} -A ${TactN}"
-  KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
-  ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $keytimes $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 $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$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 3:
-  # After the publication interval has passed the DNSKEY of the successor ZSK
-  # is OMNIPRESENT and the zone can thus be signed with the successor ZSK.
-  setup step3.zsk-prepub.$tld
-  # According to RFC 7583:
-  # Tpub(N+1) <= Tact(N) + Lzsk - Ipub
-  # Tact(N+1) = Tact(N) + Lzsk
-  #
-  # Tact(N)   = now - Lzsk = now - 30d
-  # Tpub(N+1) = now - Ipub = now - 26h
-  # Tact(N+1) = now
-  # Tret(N) = now
-  # Trem(N) = now + Iret = now + Dsign + Dprp + TTLsig + retire-safety = 8d1h = now + 241h
-  TactN="now-30d"
-  TpubN1="now-26h"
-  TactN1="now"
-  TremN="now+241h"
-  keytimes="-P ${TactN}  -A ${TactN}"
-  oldtimes="-P ${TactN}  -A ${TactN} -I ${TactN1} -D ${TremN}"
-  newtimes="-P ${TpubN1} -A ${TactN1}"
-  KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $oldtimes $zone 2>keygen.out.$zone.2)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $newtimes $zone 2>keygen.out.$zone.3)
-  $SETTIME -s -g $O -k $O $TactN -r $O $TactN -d $O $TactN "$KSK" >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 -z $H $TpubN1 "$ZSK2" >settime.out.$zone.3 2>&1
-  # Set key rollover relationship.
-  key_successor $ZSK1 $ZSK2
-  # Sign zone.
-  cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK1" >>"$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:
-  # After the retire interval has passed the predecessor DNSKEY can be
-  # removed from the zone.
-  setup step4.zsk-prepub.$tld
-  # Lzsk:          30d
-  # Ipub:          26h
-  # Dsgn:          1w
-  # Dprp:          1h
-  # TTLsig:        1d
-  # retire-safety: 2d
-  #
-  # According to RFC 7583:
-  # Iret      = Dsgn + Dprp + TTLsig (+retire-safety)
-  # Iret      = 1w + 1h + 1d + 2d = 10d1h = 241h
-  #
-  # Tact(N)   = now - Iret - Lzsk
-  #           = now - 241h - 30d = now - 241h - 720h
-  #           = now - 961h
-  # Tpub(N+1) = now - Iret - Ipub
-  #           = now - 241h - 26h
-  #           = now - 267h
-  # Tact(N+1) = now - Iret = now - 241h
-  TactN="now-961h"
-  TpubN1="now-267h"
-  TactN1="now-241h"
-  TremN="now"
-  keytimes="-P ${TactN}  -A ${TactN}"
-  oldtimes="-P ${TactN}  -A ${TactN} -I ${TactN1} -D ${TremN}"
-  newtimes="-P ${TpubN1} -A ${TactN1}"
-  KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $oldtimes $zone 2>keygen.out.$zone.2)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $newtimes $zone 2>keygen.out.$zone.3)
-  $SETTIME -s -g $O -k $O $TactN -r $O $TactN -d $O $TactN "$KSK" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $O $TactN -z $U $TactN1 "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TactN1 -z $R $TactN1 "$ZSK2" >settime.out.$zone.3 2>&1
-  # Set key rollover relationship.
-  key_successor $ZSK1 $ZSK2
-  # Sign zone.
-  cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" >"$infile"
-  cp $infile $zonefile
-  $SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
-
-  # Step 5:
-  # The predecessor DNSKEY is removed long enough that is has become HIDDEN.
-  setup step5.zsk-prepub.$tld
-  # Subtract DNSKEY TTL + zone-propagation-delay from all the times (2h).
-  # Tact(N)   = now - 961h - 2h = now - 963h
-  # Tpub(N+1) = now - 267h - 2h = now - 269h
-  # Tact(N+1) = now - 241h - 2h = now - 243h
-  # Trem(N)   = Tact(N+1) + Iret = now -2h
-  TactN="now-963h"
-  TremN="now-2h"
-  TpubN1="now-269h"
-  TactN1="now-243h"
-  TremN="now-2h"
-  keytimes="-P ${TactN}  -A ${TactN}"
-  oldtimes="-P ${TactN}  -A ${TactN} -I ${TactN1} -D ${TremN}"
-  newtimes="-P ${TpubN1} -A ${TactN1}"
-  KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $oldtimes $zone 2>keygen.out.$zone.2)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $newtimes $zone 2>keygen.out.$zone.3)
-  $SETTIME -s -g $O -k $O $TactN -r $O $TactN -d $O $TactN "$KSK" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $U $TremN -z $H $TremN "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TactN1 -z $O $TremN "$ZSK2" >settime.out.$zone.3 2>&1
-  # Set key rollover relationship.
-  key_successor $ZSK1 $ZSK2
-  # Sign zone.
-  cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK1" >>"$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 predecessor DNSKEY can be purged.
-  setup step6.zsk-prepub.$tld
-  # Subtract purge-keys interval from all the times (1h).
-  TactN="now-964h"
-  TremN="now-3h"
-  TpubN1="now-270h"
-  TactN1="now-244h"
-  TremN="now-3h"
-  keytimes="-P ${TactN}  -A ${TactN}"
-  oldtimes="-P ${TactN}  -A ${TactN} -I ${TactN1} -D ${TremN}"
-  newtimes="-P ${TpubN1} -A ${TactN1}"
-  KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
-  ZSK1=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $oldtimes $zone 2>keygen.out.$zone.2)
-  ZSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $newtimes $zone 2>keygen.out.$zone.3)
-  $SETTIME -s -g $O -k $O $TactN -r $O $TactN -d $O $TactN "$KSK" >settime.out.$zone.1 2>&1
-  $SETTIME -s -g $H -k $H $TremN -z $H $TremN "$ZSK1" >settime.out.$zone.2 2>&1
-  $SETTIME -s -g $O -k $O $TactN1 -z $O $TremN "$ZSK2" >settime.out.$zone.3 2>&1
-  # Set key rollover relationship.
-  key_successor $ZSK1 $ZSK2
-  # Sign zone.
-  cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" >"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile"
-  private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK1" >>"$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 f0201296fca342283d661c0ee5051a330a502d24..2720275c66b1b27e28a097dc8b2e12d2f91801d1 100644 (file)
@@ -24,6 +24,11 @@ from rollover.common import (
     size,
     TIMEDELTA,
 )
+from rollover.setup import (
+    configure_root,
+    configure_tld,
+    configure_zsk_prepub,
+)
 
 CONFIG = {
     "dnskey-ttl": TIMEDELTA["PT1H"],
@@ -57,6 +62,30 @@ OFFSETS["step6-p"] = OFFSETS["step5-p"] - int(CONFIG["purge-keys"].total_seconds
 OFFSETS["step6-s"] = OFFSETS["step5-s"] - int(CONFIG["purge-keys"].total_seconds())
 
 
+def bootstrap():
+    data = {
+        "tlds": [],
+        "trust_anchors": [],
+    }
+
+    tlds = []
+    for tld_name in [
+        "autosign",
+        "manual",
+    ]:
+        delegations = configure_zsk_prepub(tld_name)
+
+        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(
     "tld",
     [
index 047a59dd6e48f3e47b2575f85e353f122c78dcf2..48f038101c8c7a03bfcf6314aaf508bae8b57e5d 100644 (file)
@@ -1720,3 +1720,224 @@ def configure_ksk_3crowd(tld: str) -> List[Zone]:
     )
 
     return zones
+
+
+def configure_zsk_prepub(tld: str) -> List[Zone]:
+    # The zones at zsk-prepub.$tld represent the various steps of a ZSK
+    # Pre-Publication rollover.
+    zones = []
+    zone = f"zsk-prepub.{tld}"
+    keygen = CmdHelper("KEYGEN", "-a ECDSAP256SHA256 -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}")
+    # Timing metadata.
+    TactN = "now-7d"
+    keytimes = f"-P {TactN} -A {TactN}"
+    # Key generation.
+    ksk_name = keygen(f"-f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk_name = keygen(f"{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])
+
+    # Step 2:
+    # It is time to pre-publish the successor ZSK.
+    zonename = f"step2.{zone}"
+    zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+    isctest.log.info(f"setup {zonename}")
+    # According to RFC 7583:
+    # Tact(N) = now + Ipub - Lzsk = now + 26h - 30d
+    #         = now + 26h - 30d = now − 694h
+    TactN = "now-694h"
+    keytimes = f"-P {TactN} -A {TactN}"
+    # Key generation.
+    ksk_name = keygen(f"-f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk_name = keygen(f"{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])
+
+    # Step 3:
+    # After the publication interval has passed the DNSKEY of the successor ZSK
+    # is OMNIPRESENT and the zone can thus be signed with the successor ZSK.
+    zonename = f"step3.{zone}"
+    zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+    isctest.log.info(f"setup {zonename}")
+    # According to RFC 7583:
+    # Tpub(N+1) <= Tact(N) + Lzsk - Ipub
+    # Tact(N+1) = Tact(N) + Lzsk
+    #
+    # Tact(N)   = now - Lzsk = now - 30d
+    # Tpub(N+1) = now - Ipub = now - 26h
+    # Tact(N+1) = now
+    # Tret(N) = now
+    # Trem(N) = now + Iret = now + Dsign + Dprp + TTLsig + retire-safety = 8d1h = now + 241h
+    TactN = "now-30d"
+    TpubN1 = "now-26h"
+    TactN1 = "now"
+    TremN = "now+241h"
+    keytimes = f"-P {TactN} -A {TactN}"
+    oldtimes = f"-P {TactN} -A {TactN} -I {TactN1} -D {TremN}"
+    newtimes = f"-P {TpubN1} -A {TactN1}"
+    # Key generation.
+    ksk_name = keygen(f"-f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk1_name = keygen(f"{oldtimes} {zonename}", cwd="ns3").strip()
+    zsk2_name = keygen(f"{newtimes} {zonename}", cwd="ns3").strip()
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk_name}",
+        cwd="ns3",
+    )
+    settime(
+        f"-g HIDDEN -k OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} {zsk1_name}",
+        cwd="ns3",
+    )
+    settime(
+        f"-g OMNIPRESENT -k RUMOURED {TpubN1} -z HIDDEN {TpubN1} {zsk2_name}", cwd="ns3"
+    )
+    # Set key rollover relationship.
+    set_key_relationship(zsk1_name, zsk2_name)
+    # Signing.
+    render_and_sign_zone(zonename, [ksk_name, zsk1_name, zsk2_name])
+
+    # Step 4:
+    # After the retire interval has passed the predecessor DNSKEY can be
+    # removed from the zone.
+    zonename = f"step4.{zone}"
+    zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+    isctest.log.info(f"setup {zonename}")
+    # Lzsk:          30d
+    # Ipub:          26h
+    # Dsgn:          1w
+    # Dprp:          1h
+    # TTLsig:        1d
+    # retire-safety: 2d
+    #
+    # According to RFC 7583:
+    # Iret      = Dsgn + Dprp + TTLsig (+retire-safety)
+    # Iret      = 1w + 1h + 1d + 2d = 10d1h = 241h
+    #
+    # Tact(N)   = now - Iret - Lzsk
+    #           = now - 241h - 30d = now - 241h - 720h
+    #           = now - 961h
+    # Tpub(N+1) = now - Iret - Ipub
+    #           = now - 241h - 26h
+    #           = now - 267h
+    # Tact(N+1) = now - Iret = now - 241h
+    TactN = "now-961h"
+    TpubN1 = "now-267h"
+    TactN1 = "now-241h"
+    TremN = "now"
+    keytimes = f"-P {TactN} -A {TactN}"
+    oldtimes = f"-P {TactN} -A {TactN} -I {TactN1} -D {TremN}"
+    newtimes = f"-P {TpubN1} -A {TactN1}"
+    # Key generation.
+    ksk_name = keygen(f"-f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk1_name = keygen(f"{oldtimes} {zonename}", cwd="ns3").strip()
+    zsk2_name = keygen(f"{newtimes} {zonename}", cwd="ns3").strip()
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk_name}",
+        cwd="ns3",
+    )
+    settime(
+        f"-g HIDDEN -k OMNIPRESENT {TactN} -z UNRETENTIVE {TactN1} {zsk1_name}",
+        cwd="ns3",
+    )
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN1} -z RUMOURED {TactN1} {zsk2_name}",
+        cwd="ns3",
+    )
+    # Set key rollover relationship.
+    set_key_relationship(zsk1_name, zsk2_name)
+    # Signing.
+    render_and_sign_zone(zonename, [ksk_name, zsk1_name, zsk2_name])
+
+    # Step 5:
+    # The predecessor DNSKEY is removed long enough that is has become HIDDEN.
+    zonename = f"step5.{zone}"
+    zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+    isctest.log.info(f"setup {zonename}")
+    # Subtract DNSKEY TTL + zone-propagation-delay from all the times (2h).
+    # Tact(N)   = now - 961h - 2h = now - 963h
+    # Tpub(N+1) = now - 267h - 2h = now - 269h
+    # Tact(N+1) = now - 241h - 2h = now - 243h
+    # Trem(N)   = Tact(N+1) + Iret = now -2h
+    TactN = "now-963h"
+    TremN = "now-2h"
+    TpubN1 = "now-269h"
+    TactN1 = "now-243h"
+    TremN = "now-2h"
+    keytimes = f"-P {TactN} -A {TactN}"
+    oldtimes = f"-P {TactN} -A {TactN} -I {TactN1} -D {TremN}"
+    newtimes = f"-P {TpubN1} -A {TactN1}"
+    # Key generation.
+    ksk_name = keygen(f"-f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk1_name = keygen(f"{oldtimes} {zonename}", cwd="ns3").strip()
+    zsk2_name = keygen(f"{newtimes} {zonename}", cwd="ns3").strip()
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk_name}",
+        cwd="ns3",
+    )
+    settime(
+        f"-g HIDDEN -k UNRETENTIVE {TremN} -z HIDDEN {TremN} {zsk1_name}", cwd="ns3"
+    )
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN1} -z OMNIPRESENT {TremN} {zsk2_name}",
+        cwd="ns3",
+    )
+    # Set key rollover relationship.
+    set_key_relationship(zsk1_name, zsk2_name)
+    # Signing.
+    render_and_sign_zone(zonename, [ksk_name, zsk1_name, zsk2_name])
+
+    # Step 6:
+    # The predecessor DNSKEY can be purged.
+    zonename = f"step6.{zone}"
+    zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
+    isctest.log.info(f"setup {zonename}")
+    # Subtract purge-keys interval from all the times (1h).
+    TactN = "now-964h"
+    TremN = "now-3h"
+    TpubN1 = "now-270h"
+    TactN1 = "now-244h"
+    TremN = "now-3h"
+    keytimes = f"-P {TactN} -A {TactN}"
+    oldtimes = f"-P {TactN} -A {TactN} -I {TactN1} -D {TremN}"
+    newtimes = f"-P {TpubN1} -A {TactN1}"
+    # Key generation.
+    ksk_name = keygen(f"-f KSK {keytimes} {zonename}", cwd="ns3").strip()
+    zsk1_name = keygen(f"{oldtimes} {zonename}", cwd="ns3").strip()
+    zsk2_name = keygen(f"{newtimes} {zonename}", cwd="ns3").strip()
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {ksk_name}",
+        cwd="ns3",
+    )
+    settime(f"-g HIDDEN -k HIDDEN {TremN} -z HIDDEN {TremN} {zsk1_name}", cwd="ns3")
+    settime(
+        f"-g OMNIPRESENT -k OMNIPRESENT {TactN1} -z OMNIPRESENT {TremN} {zsk2_name}",
+        cwd="ns3",
+    )
+    # Set key rollover relationship.
+    set_key_relationship(zsk1_name, zsk2_name)
+    # Signing.
+    render_and_sign_zone(zonename, [ksk_name, zsk1_name, zsk2_name])
+
+    return zones