2025-02-28 15:52:20 +01:00
|
|
|
# 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
|
|
|
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
pytest.importorskip("dns", minversion="2.0.0")
|
|
|
|
import dns.update
|
|
|
|
|
|
|
|
import isctest
|
2025-06-02 17:20:06 +02:00
|
|
|
from isctest.kasp import KeyTimingMetadata, Ipub, IpubC, Iret
|
2025-02-28 15:52:20 +01:00
|
|
|
|
|
|
|
pytestmark = pytest.mark.extra_artifacts(
|
|
|
|
[
|
|
|
|
"*.axfr*",
|
|
|
|
"dig.out*",
|
2025-03-18 08:41:02 +01:00
|
|
|
"K*.key*",
|
|
|
|
"K*.private*",
|
2025-02-28 15:52:20 +01:00
|
|
|
"ns*/*.db",
|
|
|
|
"ns*/*.db.infile",
|
2025-03-18 08:41:02 +01:00
|
|
|
"ns*/*.db.jnl",
|
2025-02-28 15:52:20 +01:00
|
|
|
"ns*/*.db.jbk",
|
|
|
|
"ns*/*.db.signed",
|
|
|
|
"ns*/*.db.signed.jnl",
|
|
|
|
"ns*/*.conf",
|
|
|
|
"ns*/dsset-*",
|
|
|
|
"ns*/K*.key",
|
|
|
|
"ns*/K*.private",
|
|
|
|
"ns*/K*.state",
|
|
|
|
"ns*/keygen.out.*",
|
|
|
|
"ns*/settime.out.*",
|
|
|
|
"ns*/signer.out.*",
|
|
|
|
"ns*/zones",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_manual(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "manual-rollover"
|
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"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),
|
|
|
|
}
|
|
|
|
ttl = int(config["dnskey-ttl"].total_seconds())
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
zone = "manual-rollover.kasp"
|
2025-03-19 10:10:13 +01:00
|
|
|
|
2025-06-17 14:32:49 +10:00
|
|
|
with server.watch_log_from_start() as watcher:
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-02-28 15:52:20 +01:00
|
|
|
key_properties = [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
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 = -timedelta(days=7)
|
|
|
|
for kp in expected:
|
|
|
|
kp.set_expected_keytimes(config, offset=offset)
|
|
|
|
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
|
|
|
|
# Schedule KSK rollover in six months.
|
|
|
|
assert len(ksks) == 1
|
|
|
|
ksk = ksks[0]
|
|
|
|
startroll = expected[0].timing["Active"] + timedelta(days=30 * 6)
|
|
|
|
expected[0].timing["Retired"] = startroll + Ipub(config)
|
|
|
|
expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret(
|
|
|
|
config, zsk=False, ksk=True
|
|
|
|
)
|
|
|
|
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"dnssec -rollover -key {ksk.tag} -when {startroll} {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
2025-02-28 15:52:20 +01:00
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
|
|
|
|
# Schedule KSK rollover now.
|
|
|
|
now = KeyTimingMetadata.now()
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"dnssec -rollover -key {ksk.tag} -when {now} {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-02-28 15:52:20 +01:00
|
|
|
key_properties = [
|
|
|
|
f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
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)
|
|
|
|
|
|
|
|
expected[0].metadata["Successor"] = expected[1].key.tag
|
|
|
|
expected[1].metadata["Predecessor"] = expected[0].key.tag
|
|
|
|
isctest.kasp.check_keyrelationships(keys, expected)
|
|
|
|
|
|
|
|
for kp in expected:
|
|
|
|
off = offset
|
|
|
|
if "Predecessor" in kp.metadata:
|
|
|
|
off = 0
|
|
|
|
kp.set_expected_keytimes(config, offset=off)
|
|
|
|
|
|
|
|
expected[0].timing["Retired"] = now + Ipub(config)
|
|
|
|
expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret(
|
|
|
|
config, zsk=False, ksk=True
|
|
|
|
)
|
|
|
|
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
|
|
|
|
# Schedule ZSK rollover now.
|
|
|
|
assert len(zsks) == 1
|
|
|
|
zsk = zsks[0]
|
|
|
|
now = KeyTimingMetadata.now()
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"dnssec -rollover -key {zsk.tag} -when {now} {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-02-28 15:52:20 +01:00
|
|
|
key_properties = [
|
|
|
|
f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
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)
|
|
|
|
|
|
|
|
expected[0].metadata["Successor"] = expected[1].key.tag
|
|
|
|
expected[1].metadata["Predecessor"] = expected[0].key.tag
|
|
|
|
expected[2].metadata["Successor"] = expected[3].key.tag
|
|
|
|
expected[3].metadata["Predecessor"] = expected[2].key.tag
|
|
|
|
isctest.kasp.check_keyrelationships(keys, expected)
|
|
|
|
|
|
|
|
# Try to schedule a ZSK rollover for an inactive key (should fail).
|
|
|
|
zsk = expected[3].key
|
|
|
|
response = server.rndc(f"dnssec -rollover -key {zsk.tag} {zone}")
|
|
|
|
assert "key is not actively signing" in response
|
2025-03-18 08:41:02 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_multisigner(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "multisigner-model2"
|
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"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),
|
|
|
|
}
|
|
|
|
ttl = int(config["dnskey-ttl"].total_seconds())
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
|
|
|
|
offset = -timedelta(days=7)
|
|
|
|
offval = int(offset.total_seconds())
|
|
|
|
|
|
|
|
def keygen(zone):
|
|
|
|
keygen_command = [
|
|
|
|
os.environ.get("KEYGEN"),
|
|
|
|
"-a",
|
|
|
|
alg,
|
|
|
|
"-L",
|
|
|
|
"3600",
|
|
|
|
"-M",
|
|
|
|
"0:32767",
|
|
|
|
zone,
|
|
|
|
]
|
|
|
|
|
2025-06-26 16:18:01 +02:00
|
|
|
return isctest.run.cmd(keygen_command).stdout.decode("utf-8")
|
2025-03-18 08:41:02 +01:00
|
|
|
|
|
|
|
zone = "multisigner-model2.kasp"
|
2025-03-19 10:10:13 +01:00
|
|
|
|
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-03-18 08:41:02 +01:00
|
|
|
key_properties = [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured tag-range:32768-65535",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
|
|
|
|
|
|
|
|
newprops = [f"zsk unlimited {alg} {size} tag-range:0-32767"]
|
|
|
|
expected2 = isctest.kasp.policy_to_properties(ttl, newprops)
|
|
|
|
expected2[0].properties["private"] = False
|
|
|
|
expected2[0].properties["legacy"] = True
|
|
|
|
expected = expected + expected2
|
|
|
|
|
|
|
|
ownkeys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
extkeys = isctest.kasp.keydir_to_keylist(zone)
|
|
|
|
keys = ownkeys + extkeys
|
|
|
|
ksks = [k for k in ownkeys if k.is_ksk()]
|
|
|
|
zsks = [k for k in ownkeys if not k.is_ksk()]
|
|
|
|
zsks = zsks + extkeys
|
|
|
|
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
for kp in expected:
|
|
|
|
kp.set_expected_keytimes(config)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
|
|
|
|
# Update zone with ZSK from another provider for zone.
|
|
|
|
out = keygen(zone)
|
|
|
|
newkeys = isctest.kasp.keystr_to_keylist(out)
|
|
|
|
newprops = [f"zsk unlimited {alg} {size} tag-range:0-32767"]
|
|
|
|
expected2 = isctest.kasp.policy_to_properties(ttl, newprops)
|
|
|
|
expected2[0].properties["private"] = False
|
|
|
|
expected2[0].properties["legacy"] = True
|
|
|
|
expected = expected + expected2
|
|
|
|
|
|
|
|
dnskey = newkeys[0].dnskey().split()
|
|
|
|
rdata = " ".join(dnskey[4:])
|
|
|
|
|
2025-05-26 17:10:15 +02:00
|
|
|
update_msg = dns.update.UpdateMessage(zone)
|
|
|
|
update_msg.add(f"{dnskey[0]}", 3600, "DNSKEY", rdata)
|
|
|
|
server.nsupdate(update_msg)
|
2025-03-18 08:41:02 +01:00
|
|
|
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-03-18 08:41:02 +01:00
|
|
|
keys = keys + newkeys
|
|
|
|
zsks = zsks + newkeys
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
|
|
|
|
# Remove ZSKs from the other providers for zone.
|
|
|
|
dnskey2 = extkeys[0].dnskey().split()
|
|
|
|
rdata2 = " ".join(dnskey2[4:])
|
2025-05-26 17:10:15 +02:00
|
|
|
update_msg = dns.update.UpdateMessage(zone)
|
|
|
|
update_msg.delete(f"{dnskey[0]}", "DNSKEY", rdata)
|
|
|
|
update_msg.delete(f"{dnskey2[0]}", "DNSKEY", rdata2)
|
|
|
|
server.nsupdate(update_msg)
|
2025-03-18 08:41:02 +01:00
|
|
|
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-03-18 08:41:02 +01:00
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
|
|
|
|
keys = ownkeys
|
|
|
|
ksks = [k for k in ownkeys if k.is_ksk()]
|
|
|
|
zsks = [k for k in ownkeys if not k.is_ksk()]
|
|
|
|
isctest.kasp.check_keys(zone, keys, expected)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
|
|
|
|
# A zone transitioning from single-signed to multi-signed. We should have
|
|
|
|
# the old omnipresent keys outside of the desired key range and the new
|
|
|
|
# keys in the desired key range.
|
|
|
|
zone = "single-to-multisigner.kasp"
|
2025-03-19 10:10:13 +01:00
|
|
|
|
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
|
|
|
|
2025-03-18 08:41:02 +01:00
|
|
|
key_properties = [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden tag-range:32768-65535",
|
|
|
|
f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent tag-range:0-32767 offset:{offval}",
|
|
|
|
f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent tag-range:0-32767 offset:{offval}",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
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)
|
|
|
|
|
|
|
|
for kp in expected:
|
|
|
|
kp.set_expected_keytimes(config)
|
|
|
|
|
|
|
|
start = expected[0].key.get_timing("Created")
|
|
|
|
expected[2].timing["Retired"] = start
|
|
|
|
expected[2].timing["Removed"] = expected[2].timing["Retired"] + Iret(
|
|
|
|
config, zsk=False, ksk=True
|
|
|
|
)
|
|
|
|
expected[3].timing["Retired"] = start
|
|
|
|
expected[3].timing["Removed"] = expected[3].timing["Retired"] + Iret(
|
|
|
|
config, zsk=True, ksk=False
|
|
|
|
)
|
|
|
|
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
2025-03-18 10:34:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_enable_dnssec(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "enable-dnssec"
|
2025-03-18 14:20:54 +01:00
|
|
|
cdss = ["CDNSKEY", "CDS (SHA-256)"]
|
2025-03-18 10:34:53 +01:00
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(seconds=300),
|
|
|
|
"ds-ttl": timedelta(hours=2),
|
|
|
|
"max-zone-ttl": timedelta(hours=12),
|
|
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
|
|
"publish-safety": timedelta(minutes=5),
|
|
|
|
"retire-safety": timedelta(minutes=20),
|
|
|
|
"signatures-refresh": timedelta(days=7),
|
|
|
|
"signatures-validity": timedelta(days=14),
|
|
|
|
"zone-propagation-delay": timedelta(minutes=5),
|
|
|
|
}
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
|
|
|
|
ipub = Ipub(config)
|
|
|
|
ipubC = IpubC(config, rollover=False)
|
|
|
|
iretZSK = Iret(config, rollover=False)
|
|
|
|
iretKSK = Iret(config, zsk=False, ksk=True, rollover=False)
|
|
|
|
offsets = {
|
|
|
|
"step1": 0,
|
|
|
|
"step2": -int(ipub.total_seconds()),
|
|
|
|
"step3": -int(iretZSK.total_seconds()),
|
|
|
|
"step4": -int(ipubC.total_seconds() + iretKSK.total_seconds()),
|
|
|
|
}
|
|
|
|
|
|
|
|
steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
"zone": "step1.enable-dnssec.autosign",
|
2025-03-18 14:20:54 +01:00
|
|
|
"cdss": cdss,
|
2025-03-18 10:34:53 +01:00
|
|
|
"keyprops": [
|
|
|
|
f"csk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden offset:{offsets['step1']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DNSKEY RRset becomes OMNIPRESENT,
|
|
|
|
# after the publication interval.
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
"zone": "step2.enable-dnssec.autosign",
|
2025-03-18 14:20:54 +01:00
|
|
|
"cdss": cdss,
|
2025-03-18 10:34:53 +01:00
|
|
|
# The DNSKEY is omnipresent, but the zone signatures not yet.
|
|
|
|
# Thus, the DS remains hidden.
|
|
|
|
# dnskey: rumoured -> omnipresent
|
|
|
|
# krrsig: rumoured -> omnipresent
|
|
|
|
"keyprops": [
|
|
|
|
f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{offsets['step2']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the zone signatures become OMNIPRESENT,
|
|
|
|
# Minus the time already elapsed.
|
|
|
|
"nextev": iretZSK - ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
"zone": "step3.enable-dnssec.autosign",
|
2025-03-18 14:20:54 +01:00
|
|
|
"cdss": cdss,
|
2025-03-18 10:34:53 +01:00
|
|
|
# All signatures should be omnipresent, so the DS can be submitted.
|
|
|
|
# zrrsig: rumoured -> omnipresent
|
|
|
|
# ds: hidden -> rumoured
|
|
|
|
"keyprops": [
|
|
|
|
f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{offsets['step3']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DS can move to the OMNIPRESENT state.
|
|
|
|
# This is after the retire interval.
|
|
|
|
"nextev": iretKSK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
"zone": "step4.enable-dnssec.autosign",
|
2025-03-18 14:20:54 +01:00
|
|
|
"cdss": cdss,
|
2025-03-18 10:34:53 +01:00
|
|
|
# DS has been published long enough.
|
|
|
|
# ds: rumoured -> omnipresent
|
|
|
|
"keyprops": [
|
|
|
|
f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step4']}",
|
|
|
|
],
|
|
|
|
# Next key event is never, the zone dnssec-policy has been
|
|
|
|
# established. So we fall back to the default loadkeys interval.
|
|
|
|
"nextev": timedelta(hours=1),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, config, policy, step)
|
2025-03-18 12:18:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_zsk_prepublication(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "zsk-prepub"
|
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(seconds=3600),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"max-zone-ttl": timedelta(days=1),
|
|
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
|
|
"publish-safety": timedelta(days=1),
|
|
|
|
"purge-keys": timedelta(hours=1),
|
|
|
|
"retire-safety": timedelta(days=2),
|
|
|
|
"signatures-refresh": timedelta(days=7),
|
|
|
|
"signatures-validity": timedelta(days=14),
|
|
|
|
"zone-propagation-delay": timedelta(hours=1),
|
|
|
|
}
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
zsk_lifetime = timedelta(days=30)
|
|
|
|
lifetime_policy = int(zsk_lifetime.total_seconds())
|
|
|
|
|
|
|
|
ipub = Ipub(config)
|
|
|
|
iret = Iret(config, rollover=True)
|
|
|
|
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
|
|
|
|
offsets = {}
|
|
|
|
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
|
|
|
|
offsets["step2-p"] = -int(zsk_lifetime.total_seconds() - ipub.total_seconds())
|
|
|
|
offsets["step2-s"] = 0
|
|
|
|
offsets["step3-p"] = -int(zsk_lifetime.total_seconds())
|
|
|
|
offsets["step3-s"] = -int(ipub.total_seconds())
|
|
|
|
offsets["step4-p"] = offsets["step3-p"] - int(iret.total_seconds())
|
|
|
|
offsets["step4-s"] = offsets["step3-s"] - int(iret.total_seconds())
|
|
|
|
offsets["step5-p"] = offsets["step4-p"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step5-s"] = offsets["step4-s"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step6-p"] = offsets["step5-p"] - int(config["purge-keys"].total_seconds())
|
|
|
|
offsets["step6-s"] = offsets["step5-s"] - int(config["purge-keys"].total_seconds())
|
|
|
|
|
|
|
|
steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
# Introduce the first key. This will immediately be active.
|
|
|
|
"zone": "step1.zsk-prepub.autosign",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step1-p']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the successor ZSK needs to be published.
|
|
|
|
# That is the ZSK lifetime - prepublication time (minus time
|
|
|
|
# already passed).
|
|
|
|
"nextev": zsk_lifetime - ipub - timedelta(days=7),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
# It is time to pre-publish the successor ZSK.
|
|
|
|
# ZSK1 goal: omnipresent -> hidden
|
|
|
|
# ZSK2 goal: hidden -> omnipresent
|
|
|
|
# ZSK2 dnskey: hidden -> rumoured
|
|
|
|
"zone": "step2.zsk-prepub.autosign",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step2-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:{offsets['step2-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the successor ZSK becomes OMNIPRESENT.
|
|
|
|
# That is the DNSKEY TTL plus the zone propagation delay
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
# Predecessor ZSK is no longer actively signing. Successor ZSK is
|
|
|
|
# now actively signing.
|
|
|
|
# ZSK1 zrrsig: omnipresent -> unretentive
|
|
|
|
# ZSK2 dnskey: rumoured -> omnipresent
|
|
|
|
# ZSK2 zrrsig: hidden -> rumoured
|
|
|
|
"zone": "step3.zsk-prepub.autosign",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step3-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:unretentive offset:{offsets['step3-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{offsets['step3-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when all the RRSIG records have been replaced
|
|
|
|
# with signatures of the new ZSK, in other words when ZRRSIG
|
|
|
|
# becomes OMNIPRESENT.
|
|
|
|
"nextev": iret,
|
|
|
|
# Set 'smooth' to true so expected signatures of subdomain are
|
|
|
|
# from the predecessor ZSK.
|
|
|
|
"smooth": True,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
# Predecessor ZSK is no longer needed. All RRsets are signed with
|
|
|
|
# the successor ZSK.
|
|
|
|
# ZSK1 dnskey: omnipresent -> unretentive
|
|
|
|
# ZSK1 zrrsig: unretentive -> hidden
|
|
|
|
# ZSK2 zrrsig: rumoured -> omnipresent
|
|
|
|
"zone": "step4.zsk-prepub.autosign",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step4-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive zrrsig:hidden offset:{offsets['step4-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step4-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the DNSKEY enters the HIDDEN state.
|
|
|
|
# This is the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 5.
|
|
|
|
# Predecessor ZSK is now removed.
|
|
|
|
# ZSK1 dnskey: unretentive -> hidden
|
|
|
|
"zone": "step5.zsk-prepub.autosign",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step5-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden zrrsig:hidden offset:{offsets['step5-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step5-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the new successor needs to be published.
|
|
|
|
# This is the ZSK lifetime minus Iret minus Ipub minus time
|
|
|
|
# elapsed.
|
|
|
|
"nextev": zsk_lifetime - iret - ipub - keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 6.
|
|
|
|
# Predecessor ZSK is now purged.
|
|
|
|
"zone": "step6.zsk-prepub.autosign",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step6-p']}",
|
|
|
|
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step6-s']}",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, config, policy, step)
|
2025-03-18 14:20:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_ksk_doubleksk(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "ksk-doubleksk"
|
|
|
|
cdss = ["CDS (SHA-256)"]
|
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=2),
|
|
|
|
"ds-ttl": timedelta(seconds=3600),
|
|
|
|
"max-zone-ttl": timedelta(days=1),
|
|
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
|
|
"publish-safety": timedelta(days=1),
|
|
|
|
"purge-keys": timedelta(hours=1),
|
|
|
|
"retire-safety": timedelta(days=2),
|
|
|
|
"signatures-refresh": timedelta(days=7),
|
|
|
|
"signatures-validity": timedelta(days=14),
|
|
|
|
"zone-propagation-delay": timedelta(hours=1),
|
|
|
|
}
|
|
|
|
ttl = int(config["dnskey-ttl"].total_seconds())
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
ksk_lifetime = timedelta(days=60)
|
|
|
|
lifetime_policy = int(ksk_lifetime.total_seconds())
|
|
|
|
|
|
|
|
ipub = Ipub(config)
|
|
|
|
ipubc = IpubC(config)
|
|
|
|
iret = Iret(config, zsk=False, ksk=True)
|
|
|
|
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
|
|
|
|
offsets = {}
|
|
|
|
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
|
|
|
|
offsets["step2-p"] = -int(ksk_lifetime.total_seconds() - ipubc.total_seconds())
|
|
|
|
offsets["step2-s"] = 0
|
|
|
|
offsets["step3-p"] = -int(ksk_lifetime.total_seconds())
|
|
|
|
offsets["step3-s"] = -int(ipubc.total_seconds())
|
|
|
|
offsets["step4-p"] = offsets["step3-p"] - int(iret.total_seconds())
|
|
|
|
offsets["step4-s"] = offsets["step3-s"] - int(iret.total_seconds())
|
|
|
|
offsets["step5-p"] = offsets["step4-p"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step5-s"] = offsets["step4-s"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step6-p"] = offsets["step5-p"] - int(config["purge-keys"].total_seconds())
|
|
|
|
offsets["step6-s"] = offsets["step5-s"] - int(config["purge-keys"].total_seconds())
|
|
|
|
|
|
|
|
steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
# Introduce the first key. This will immediately be active.
|
|
|
|
"zone": "step1.ksk-doubleksk.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step1-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the successor KSK needs to be published.
|
|
|
|
# That is the KSK lifetime - prepublication time (minus time
|
|
|
|
# already passed).
|
|
|
|
"nextev": ksk_lifetime - ipub - timedelta(days=7),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
# Successor KSK is prepublished (and signs DNSKEY RRset).
|
|
|
|
# KSK1 goal: omnipresent -> hidden
|
|
|
|
# KSK2 goal: hidden -> omnipresent
|
|
|
|
# KSK2 dnskey: hidden -> rumoured
|
|
|
|
# KSK2 krrsig: hidden -> rumoured
|
|
|
|
"zone": "step2.ksk-doubleksk.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step2-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:{offsets['step2-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the successor KSK becomes OMNIPRESENT.
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
# The successor DNSKEY RRset has become omnipresent. The
|
|
|
|
# predecessor DS can be withdrawn and the successor DS can be
|
|
|
|
# introduced.
|
|
|
|
# KSK1 ds: omnipresent -> unretentive
|
|
|
|
# KSK2 dnskey: rumoured -> omnipresent
|
|
|
|
# KSK2 krrsig: rumoured -> omnipresent
|
|
|
|
# KSK2 ds: hidden -> rumoured
|
|
|
|
"zone": "step3.ksk-doubleksk.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step3-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offsets['step3-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{offsets['step3-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the predecessor DS has been replaced with
|
|
|
|
# the successor DS and enough time has passed such that the all
|
|
|
|
# validators that have this DS RRset cached only know about the
|
|
|
|
# successor DS. This is the the retire interval.
|
|
|
|
"nextev": iret,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
# The predecessor DNSKEY may be removed, the successor DS is
|
|
|
|
# omnipresent.
|
|
|
|
# KSK1 dnskey: omnipresent -> unretentive
|
|
|
|
# KSK1 krrsig: omnipresent -> unretentive
|
|
|
|
# KSK1 ds: unretentive -> hidden
|
|
|
|
# KSK2 ds: rumoured -> omnipresent
|
|
|
|
"zone": "step4.ksk-doubleksk.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step4-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{offsets['step4-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step4-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the DNSKEY enters the HIDDEN state.
|
|
|
|
# This is the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 5.
|
|
|
|
# The predecessor DNSKEY is long enough removed from the zone it
|
|
|
|
# has become hidden.
|
|
|
|
# KSK1 dnskey: unretentive -> hidden
|
|
|
|
# KSK1 krrsig: unretentive -> hidden
|
|
|
|
"zone": "step5.ksk-doubleksk.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step5-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{offsets['step5-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step5-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [1, 2],
|
|
|
|
# Next key event is when the new successor needs to be published.
|
|
|
|
# This is the KSK lifetime minus Ipub minus Iret minus time elapsed.
|
|
|
|
"nextev": ksk_lifetime - ipub - iret - keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 6.
|
|
|
|
# Predecessor KSK is now purged.
|
|
|
|
"zone": "step6.ksk-doubleksk.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step6-p']}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step6-s']}",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, config, policy, step)
|
2025-03-18 15:13:17 +01:00
|
|
|
|
|
|
|
# Test #2375: Scheduled rollovers are happening faster than they can finish.
|
|
|
|
zone = "three-is-a-crowd.kasp"
|
|
|
|
isctest.log.info(
|
|
|
|
"check that fast rollovers do not remove dependent keys from zone (#2375)"
|
|
|
|
)
|
|
|
|
offset1 = -int(timedelta(days=60).total_seconds())
|
|
|
|
offset2 = -int(timedelta(hours=27).total_seconds())
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
2025-03-18 15:13:17 +01:00
|
|
|
keyprops = [
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offset1}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{offset2}",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offset1}",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, keyprops)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
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)
|
|
|
|
expected[0].metadata["Successor"] = expected[1].key.tag
|
|
|
|
expected[1].metadata["Predecessor"] = expected[0].key.tag
|
|
|
|
isctest.kasp.check_keyrelationships(keys, expected)
|
|
|
|
for kp in expected:
|
|
|
|
kp.set_expected_keytimes(config, offset=None)
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks, cdss=cdss)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
|
|
|
# Rollover successor KSK (with DS in rumoured state).
|
|
|
|
key = expected[1].key
|
|
|
|
now = KeyTimingMetadata.now()
|
|
|
|
with server.watch_log_from_here() as watcher:
|
|
|
|
server.rndc(f"dnssec -rollover -key {key.tag} -when {now} {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
2025-03-19 10:10:13 +01:00
|
|
|
isctest.kasp.check_dnssec_verify(server, zone)
|
2025-03-18 15:13:17 +01:00
|
|
|
# We now expect four keys (3x KSK, 1x ZSK).
|
|
|
|
keyprops = [
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offset1}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{offset2}",
|
|
|
|
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:0",
|
|
|
|
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offset1}",
|
|
|
|
]
|
|
|
|
expected = isctest.kasp.policy_to_properties(ttl, keyprops)
|
|
|
|
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
|
|
|
|
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)
|
|
|
|
expected[0].metadata["Successor"] = expected[1].key.tag
|
|
|
|
expected[1].metadata["Predecessor"] = expected[0].key.tag
|
|
|
|
# Three is a crowd scenario.
|
|
|
|
expected[1].metadata["Successor"] = expected[2].key.tag
|
|
|
|
expected[2].metadata["Predecessor"] = expected[1].key.tag
|
|
|
|
isctest.kasp.check_keyrelationships(keys, expected)
|
|
|
|
for kp in expected:
|
|
|
|
kp.set_expected_keytimes(config, offset=None)
|
|
|
|
# The first successor KSK is already being retired.
|
|
|
|
expected[1].timing["Retired"] = now + ipub
|
|
|
|
expected[1].timing["Removed"] = now + ipub + iret
|
|
|
|
isctest.kasp.check_keytimes(keys, expected)
|
|
|
|
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
|
|
|
|
isctest.kasp.check_apex(server, zone, ksks, zsks, cdss=cdss)
|
|
|
|
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
|
2025-03-19 10:10:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_csk_roll1(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "csk-roll1"
|
|
|
|
cdss = ["CDNSKEY", "CDS (SHA-384)"]
|
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
|
|
"ds-ttl": timedelta(seconds=3600),
|
|
|
|
"max-zone-ttl": timedelta(days=1),
|
|
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
|
|
"publish-safety": timedelta(hours=1),
|
|
|
|
"purge-keys": timedelta(hours=1),
|
|
|
|
"retire-safety": timedelta(hours=2),
|
|
|
|
"signatures-refresh": timedelta(days=5),
|
|
|
|
"signatures-validity": timedelta(days=30),
|
|
|
|
"zone-propagation-delay": timedelta(hours=1),
|
|
|
|
}
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
csk_lifetime = timedelta(days=31 * 6)
|
|
|
|
lifetime_policy = int(csk_lifetime.total_seconds())
|
|
|
|
|
|
|
|
ipub = Ipub(config)
|
|
|
|
iretZSK = Iret(config)
|
|
|
|
iretKSK = Iret(config, zsk=False, ksk=True)
|
|
|
|
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
|
|
|
|
signdelay = iretZSK - iretKSK - keyttlprop
|
|
|
|
offsets = {}
|
|
|
|
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
|
|
|
|
offsets["step2-p"] = -int(csk_lifetime.total_seconds() - ipub.total_seconds())
|
|
|
|
offsets["step2-s"] = 0
|
|
|
|
offsets["step3-p"] = -int(csk_lifetime.total_seconds())
|
|
|
|
offsets["step3-s"] = -int(ipub.total_seconds())
|
|
|
|
offsets["step4-p"] = offsets["step3-p"] - int(iretKSK.total_seconds())
|
|
|
|
offsets["step4-s"] = offsets["step3-s"] - int(iretKSK.total_seconds())
|
|
|
|
offsets["step5-p"] = offsets["step4-p"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step5-s"] = offsets["step4-s"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step6-p"] = offsets["step5-p"] - int(signdelay.total_seconds())
|
|
|
|
offsets["step6-s"] = offsets["step5-s"] - int(signdelay.total_seconds())
|
|
|
|
offsets["step7-p"] = offsets["step6-p"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step7-s"] = offsets["step6-s"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step8-p"] = offsets["step7-p"] - int(config["purge-keys"].total_seconds())
|
|
|
|
offsets["step8-s"] = offsets["step7-s"] - int(config["purge-keys"].total_seconds())
|
|
|
|
|
|
|
|
steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
# Introduce the first key. This will immediately be active.
|
|
|
|
"zone": "step1.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the successor CSK needs to be published
|
|
|
|
# minus time already elapsed. This is Lcsk - Ipub + Dreg (we ignore
|
|
|
|
# registration delay).
|
|
|
|
"nextev": csk_lifetime - ipub - timedelta(days=7),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
# Successor CSK is prepublished (signs DNSKEY RRset, but not yet
|
|
|
|
# other RRsets).
|
|
|
|
# CSK1 goal: omnipresent -> hidden
|
|
|
|
# CSK2 goal: hidden -> omnipresent
|
|
|
|
# CSK2 dnskey: hidden -> rumoured
|
|
|
|
# CSK2 krrsig: hidden -> rumoured
|
|
|
|
"zone": "step2.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{offsets['step2-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the successor CSK becomes OMNIPRESENT.
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
# Successor CSK becomes omnipresent, meaning we can start signing
|
|
|
|
# the remainder of the zone with the successor CSK, and we can
|
|
|
|
# submit the DS.
|
|
|
|
"zone": "step3.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# Predecessor CSK will be removed, so moving to UNRETENTIVE.
|
|
|
|
# CSK1 zrrsig: omnipresent -> unretentive
|
|
|
|
# Successor CSK DNSKEY is OMNIPRESENT, so moving ZRRSIG to RUMOURED.
|
|
|
|
# CSK2 dnskey: rumoured -> omnipresent
|
|
|
|
# CSK2 krrsig: rumoured -> omnipresent
|
|
|
|
# CSK2 zrrsig: hidden -> rumoured
|
|
|
|
# The predecessor DS can be withdrawn and the successor DS can be
|
|
|
|
# introduced.
|
|
|
|
# CSK1 ds: omnipresent -> unretentive
|
|
|
|
# CSK2 ds: hidden -> rumoured
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{offsets['step3-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{offsets['step3-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the predecessor DS has been replaced with
|
|
|
|
# the successor DS and enough time has passed such that the all
|
|
|
|
# validators that have this DS RRset cached only know about the
|
|
|
|
# successor DS. This is the the retire interval.
|
|
|
|
"nextev": iretKSK,
|
|
|
|
# Set 'smooth' to true so expected signatures of subdomain are
|
|
|
|
# from the predecessor ZSK.
|
|
|
|
"smooth": True,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
"zone": "step4.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor CSK is no longer signing the DNSKEY RRset.
|
|
|
|
# CSK1 krrsig: omnipresent -> unretentive
|
|
|
|
# The predecessor DS is hidden. The successor DS is now omnipresent.
|
|
|
|
# CSK1 ds: unretentive -> hidden
|
|
|
|
# CSK2 ds: rumoured -> omnipresent
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:unretentive zrrsig:unretentive ds:hidden offset:{offsets['step4-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{offsets['step4-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the KRRSIG enters the HIDDEN state.
|
|
|
|
# This is the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
# We already swapped the DS in the previous step, so disable ds-swap.
|
|
|
|
"ds-swap": False,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 5.
|
|
|
|
"zone": "step5.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor KRRSIG records are now all hidden.
|
|
|
|
# CSK1 krrsig: unretentive -> hidden
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:hidden zrrsig:unretentive ds:hidden offset:{offsets['step5-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{offsets['step5-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the DNSKEY can be removed. This is when
|
|
|
|
# all ZRRSIG records have been replaced with signatures of the new
|
|
|
|
# CSK.
|
|
|
|
"nextev": signdelay,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 6.
|
|
|
|
"zone": "step6.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor ZRRSIG records are now all hidden (so the DNSKEY
|
|
|
|
# can be removed).
|
|
|
|
# CSK1 dnskey: omnipresent -> unretentive
|
|
|
|
# CSK1 zrrsig: unretentive -> hidden
|
|
|
|
# CSK2 zrrsig: rumoured -> omnipresent
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step6-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step6-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the DNSKEY enters the HIDDEN state.
|
|
|
|
# This is the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 7.
|
|
|
|
"zone": "step7.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor CSK is now completely HIDDEN.
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step7-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step7-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the new successor needs to be published.
|
|
|
|
# This is the Lcsk, minus time passed since the key started signing,
|
|
|
|
# minus the prepublication time.
|
|
|
|
"nextev": csk_lifetime - iretZSK - ipub - keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 8.
|
|
|
|
# Predecessor CSK is now purged.
|
|
|
|
"zone": "step8.csk-roll1.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step8-s']}",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, config, policy, step)
|
2025-03-19 10:10:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_rollover_csk_roll2(servers):
|
|
|
|
server = servers["ns3"]
|
|
|
|
policy = "csk-roll2"
|
|
|
|
cdss = ["CDNSKEY", "CDS (SHA-256)", "CDS (SHA-384)"]
|
|
|
|
config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
|
|
"ds-ttl": timedelta(seconds=3600),
|
|
|
|
"max-zone-ttl": timedelta(days=1),
|
|
|
|
"parent-propagation-delay": timedelta(days=7),
|
|
|
|
"publish-safety": timedelta(hours=1),
|
|
|
|
"purge-keys": timedelta(0),
|
|
|
|
"retire-safety": timedelta(hours=1),
|
|
|
|
"signatures-refresh": timedelta(hours=12),
|
|
|
|
"signatures-validity": timedelta(days=1),
|
|
|
|
"zone-propagation-delay": timedelta(hours=1),
|
|
|
|
}
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
csk_lifetime = timedelta(days=31 * 6)
|
|
|
|
lifetime_policy = int(csk_lifetime.total_seconds())
|
|
|
|
|
|
|
|
ipub = Ipub(config)
|
|
|
|
iret = Iret(config, zsk=True, ksk=True)
|
|
|
|
iretZSK = Iret(config)
|
|
|
|
iretKSK = Iret(config, ksk=True)
|
|
|
|
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
|
|
|
|
offsets = {}
|
|
|
|
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
|
|
|
|
offsets["step2-p"] = -int(csk_lifetime.total_seconds() - ipub.total_seconds())
|
|
|
|
offsets["step2-s"] = 0
|
|
|
|
offsets["step3-p"] = -int(csk_lifetime.total_seconds())
|
|
|
|
offsets["step3-s"] = -int(ipub.total_seconds())
|
|
|
|
offsets["step4-p"] = offsets["step3-p"] - int(iretZSK.total_seconds())
|
|
|
|
offsets["step4-s"] = offsets["step3-s"] - int(iretZSK.total_seconds())
|
|
|
|
offsets["step5-p"] = offsets["step4-p"] - int(
|
|
|
|
iretKSK.total_seconds() - iretZSK.total_seconds()
|
|
|
|
)
|
|
|
|
offsets["step5-s"] = offsets["step4-s"] - int(
|
|
|
|
iretKSK.total_seconds() - iretZSK.total_seconds()
|
|
|
|
)
|
|
|
|
offsets["step6-p"] = offsets["step5-p"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step6-s"] = offsets["step5-s"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step7-p"] = offsets["step6-p"] - int(timedelta(days=90).total_seconds())
|
|
|
|
offsets["step7-s"] = offsets["step6-s"] - int(timedelta(days=90).total_seconds())
|
|
|
|
|
|
|
|
steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
# Introduce the first key. This will immediately be active.
|
|
|
|
"zone": "step1.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the successor CSK needs to be published
|
|
|
|
# minus time already elapsed. This is Lcsk - Ipub + Dreg (we ignore
|
|
|
|
# registration delay).
|
|
|
|
"nextev": csk_lifetime - ipub - timedelta(days=7),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
# Successor CSK is prepublished (signs DNSKEY RRset, but not yet
|
|
|
|
# other RRsets).
|
|
|
|
# CSK1 goal: omnipresent -> hidden
|
|
|
|
# CSK2 goal: hidden -> omnipresent
|
|
|
|
# CSK2 dnskey: hidden -> rumoured
|
|
|
|
# CSK2 krrsig: hidden -> rumoured
|
|
|
|
"zone": "step2.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{offsets['step2-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the successor CSK becomes OMNIPRESENT.
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
# Successor CSK becomes omnipresent, meaning we can start signing
|
|
|
|
# the remainder of the zone with the successor CSK, and we can
|
|
|
|
# submit the DS.
|
|
|
|
"zone": "step3.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# Predecessor CSK will be removed, so moving to UNRETENTIVE.
|
|
|
|
# CSK1 zrrsig: omnipresent -> unretentive
|
|
|
|
# Successor CSK DNSKEY is OMNIPRESENT, so moving ZRRSIG to RUMOURED.
|
|
|
|
# CSK2 dnskey: rumoured -> omnipresent
|
|
|
|
# CSK2 krrsig: rumoured -> omnipresent
|
|
|
|
# CSK2 zrrsig: hidden -> rumoured
|
|
|
|
# The predecessor DS can be withdrawn and the successor DS can be
|
|
|
|
# introduced.
|
|
|
|
# CSK1 ds: omnipresent -> unretentive
|
|
|
|
# CSK2 ds: hidden -> rumoured
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{offsets['step3-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{offsets['step3-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the predecessor DS has been replaced with
|
|
|
|
# the successor DS and enough time has passed such that the all
|
|
|
|
# validators that have this DS RRset cached only know about the
|
|
|
|
# successor DS. This is the the retire interval.
|
|
|
|
"nextev": iretZSK,
|
|
|
|
# Set 'smooth' to true so expected signatures of subdomain are
|
|
|
|
# from the predecessor ZSK.
|
|
|
|
"smooth": True,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
"zone": "step4.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor ZRRSIG is HIDDEN. The successor ZRRSIG is
|
|
|
|
# OMNIPRESENT.
|
|
|
|
# CSK1 zrrsig: unretentive -> hidden
|
|
|
|
# CSK2 zrrsig: rumoured -> omnipresent
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:unretentive offset:{offsets['step4-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{offsets['step4-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the predecessor DS has been replaced with
|
|
|
|
# the successor DS and enough time has passed such that the all
|
|
|
|
# validators that have this DS RRset cached only know about the
|
|
|
|
# successor DS. This is the retire interval of the KSK part (minus)
|
|
|
|
# time already elapsed).
|
|
|
|
"nextev": iret - iretZSK,
|
|
|
|
# We already swapped the DS in the previous step, so disable ds-swap.
|
|
|
|
"ds-swap": False,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 5.
|
|
|
|
"zone": "step5.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor DNSKEY can be removed.
|
|
|
|
# CSK1 dnskey: omnipresent -> unretentive
|
|
|
|
# CSK1 krrsig: omnipresent -> unretentive
|
|
|
|
# CSK1 ds: unretentive -> hidden
|
|
|
|
# The successor key is now fully OMNIPRESENT.
|
|
|
|
# CSK2 ds: rumoured -> omnipresent
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:hidden ds:hidden offset:{offsets['step5-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step5-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the DNSKEY enters the HIDDEN state.
|
|
|
|
# This is the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 6.
|
|
|
|
"zone": "step6.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor CSK is now completely HIDDEN.
|
|
|
|
# CSK1 dnskey: unretentive -> hidden
|
|
|
|
# CSK1 krrsig: unretentive -> hidden
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step6-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step6-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
# Next key event is when the new successor needs to be published.
|
|
|
|
# This is the Lcsk, minus time passed since the key was published.
|
|
|
|
"nextev": csk_lifetime - iret - ipub - keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 7.
|
|
|
|
"zone": "step7.csk-roll2.autosign",
|
|
|
|
"cdss": cdss,
|
|
|
|
# The predecessor CSK is now completely HIDDEN.
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step7-p']}",
|
|
|
|
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step7-s']}",
|
|
|
|
],
|
|
|
|
"keyrelationships": [0, 1],
|
|
|
|
"nextev": None,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, config, policy, step)
|
2025-03-19 11:35:18 +01:00
|
|
|
|
|
|
|
|
2025-05-30 17:21:36 +02:00
|
|
|
def test_rollover_policy_changes(servers, templates):
|
2025-03-19 11:35:18 +01:00
|
|
|
server = servers["ns6"]
|
|
|
|
cdss = ["CDNSKEY", "CDS (SHA-256)"]
|
|
|
|
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
|
|
|
|
size = os.environ["DEFAULT_BITS"]
|
|
|
|
|
|
|
|
default_config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
|
|
"ds-ttl": timedelta(days=1),
|
|
|
|
"max-zone-ttl": timedelta(days=1),
|
|
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
|
|
"publish-safety": timedelta(hours=1),
|
|
|
|
"purge-keys": timedelta(days=90),
|
|
|
|
"retire-safety": timedelta(hours=1),
|
|
|
|
"signatures-refresh": timedelta(days=5),
|
|
|
|
"signatures-validity": timedelta(days=14),
|
|
|
|
"zone-propagation-delay": timedelta(seconds=300),
|
|
|
|
}
|
|
|
|
|
2025-03-19 14:37:28 +01:00
|
|
|
unsigning_config = default_config.copy()
|
|
|
|
unsigning_config["dnskey-ttl"] = timedelta(seconds=7200)
|
|
|
|
|
2025-03-19 16:28:33 +01:00
|
|
|
algoroll_config = {
|
|
|
|
"dnskey-ttl": timedelta(hours=1),
|
|
|
|
"ds-ttl": timedelta(seconds=7200),
|
|
|
|
"max-zone-ttl": timedelta(hours=6),
|
|
|
|
"parent-propagation-delay": timedelta(hours=1),
|
|
|
|
"publish-safety": timedelta(hours=1),
|
|
|
|
"purge-keys": timedelta(days=90),
|
|
|
|
"retire-safety": timedelta(hours=2),
|
|
|
|
"signatures-refresh": timedelta(days=5),
|
|
|
|
"signatures-validity": timedelta(days=30),
|
|
|
|
"zone-propagation-delay": timedelta(seconds=3600),
|
|
|
|
}
|
|
|
|
|
2025-03-19 11:35:18 +01:00
|
|
|
start_time = KeyTimingMetadata.now()
|
|
|
|
|
|
|
|
# Test dynamic zones that switch to inline-signing.
|
|
|
|
isctest.log.info("check dynamic zone that switches to inline-signing")
|
|
|
|
d2i = {
|
|
|
|
"zone": "dynamic2inline.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": "default",
|
|
|
|
"keyprops": [
|
|
|
|
f"csk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
steps = [d2i]
|
|
|
|
|
|
|
|
# Test key lifetime changes.
|
|
|
|
isctest.log.info("check key lifetime changes are updated correctly")
|
|
|
|
lifetime = {
|
|
|
|
"P1Y": int(timedelta(days=365).total_seconds()),
|
|
|
|
"P6M": int(timedelta(days=31 * 6).total_seconds()),
|
2025-03-19 14:37:28 +01:00
|
|
|
"P60D": int(timedelta(days=60).total_seconds()),
|
2025-03-19 11:35:18 +01:00
|
|
|
}
|
|
|
|
lifetime_update_tests = [
|
|
|
|
{
|
|
|
|
"zone": "shorter-lifetime",
|
|
|
|
"policy": "long-lifetime",
|
|
|
|
"lifetime": lifetime["P1Y"],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "longer-lifetime",
|
|
|
|
"policy": "short-lifetime",
|
|
|
|
"lifetime": lifetime["P6M"],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "limit-lifetime",
|
|
|
|
"policy": "unlimited-lifetime",
|
|
|
|
"lifetime": 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "unlimit-lifetime",
|
|
|
|
"policy": "short-lifetime",
|
|
|
|
"lifetime": lifetime["P6M"],
|
|
|
|
},
|
|
|
|
]
|
|
|
|
for lut in lifetime_update_tests:
|
|
|
|
step = {
|
|
|
|
"zone": lut["zone"],
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": lut["policy"],
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lut['lifetime']} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
2025-03-19 14:37:28 +01:00
|
|
|
# Test going insecure.
|
|
|
|
isctest.log.info("check going insecure")
|
|
|
|
offset = -timedelta(days=10)
|
|
|
|
offval = int(offset.total_seconds())
|
|
|
|
zones = [
|
|
|
|
"step1.going-insecure.kasp",
|
|
|
|
"step1.going-insecure-dynamic.kasp",
|
|
|
|
]
|
|
|
|
for zone in zones:
|
|
|
|
step = {
|
|
|
|
"zone": zone,
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": unsigning_config,
|
|
|
|
"policy": "unsigning",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
f"zsk {lifetime['P60D']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
|
|
|
# Test going straight to none.
|
|
|
|
isctest.log.info("check going straight to none")
|
|
|
|
zones = [
|
|
|
|
"step1.going-straight-to-none.kasp",
|
|
|
|
"step1.going-straight-to-none-dynamic.kasp",
|
|
|
|
]
|
|
|
|
for zone in zones:
|
|
|
|
step = {
|
|
|
|
"zone": zone,
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": "default",
|
|
|
|
"keyprops": [
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
2025-03-19 16:28:33 +01:00
|
|
|
# Test algorithm rollover (KSK/ZSK split).
|
|
|
|
isctest.log.info("check algorithm rollover ksk/zsk split")
|
|
|
|
offset = -timedelta(days=7)
|
|
|
|
offval = int(offset.total_seconds())
|
|
|
|
step = {
|
|
|
|
"zone": "step1.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "rsasha256",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk 0 8 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
|
|
|
|
],
|
|
|
|
"nextev": timedelta(hours=1),
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
|
|
|
# Test algorithm rollover (CSK).
|
|
|
|
isctest.log.info("check algorithm rollover csk")
|
|
|
|
step = {
|
|
|
|
"zone": "step1.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
f"csk 0 8 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
],
|
|
|
|
"nextev": timedelta(hours=1),
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
2025-03-19 11:35:18 +01:00
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, step["config"], step["policy"], step)
|
2025-03-19 11:35:18 +01:00
|
|
|
|
|
|
|
# Reconfigure, changing DNSSEC policies and other configuration options,
|
|
|
|
# triggering algorithm rollovers and other dnssec-policy changes.
|
2025-05-30 17:21:36 +02:00
|
|
|
templates.render("ns6/named.conf", {"csk_roll": True})
|
2025-03-19 11:35:18 +01:00
|
|
|
server.rndc("reconfig")
|
|
|
|
# Calculate time passed to correctly check for next key events.
|
|
|
|
now = KeyTimingMetadata.now()
|
|
|
|
time_passed = now.value - start_time.value
|
|
|
|
|
|
|
|
# Test dynamic zones that switch to inline-signing (after reconfig).
|
|
|
|
steps = [d2i]
|
|
|
|
|
|
|
|
# Test key lifetime changes (after reconfig).
|
|
|
|
lifetime_update_tests = [
|
|
|
|
{
|
|
|
|
"zone": "shorter-lifetime",
|
|
|
|
"policy": "short-lifetime",
|
|
|
|
"lifetime": lifetime["P6M"],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "longer-lifetime",
|
|
|
|
"policy": "long-lifetime",
|
|
|
|
"lifetime": lifetime["P1Y"],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "limit-lifetime",
|
|
|
|
"policy": "short-lifetime",
|
|
|
|
"lifetime": lifetime["P6M"],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"zone": "unlimit-lifetime",
|
|
|
|
"policy": "unlimited-lifetime",
|
|
|
|
"lifetime": 0,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
for lut in lifetime_update_tests:
|
|
|
|
step = {
|
|
|
|
"zone": lut["zone"],
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": lut["policy"],
|
|
|
|
"keyprops": [
|
|
|
|
f"csk {lut['lifetime']} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
2025-03-19 14:37:28 +01:00
|
|
|
# Test going insecure (after reconfig).
|
|
|
|
isctest.log.info("check going insecure (after reconfig)")
|
|
|
|
oldttl = unsigning_config["dnskey-ttl"]
|
|
|
|
offset = -timedelta(days=10)
|
|
|
|
offval = int(offset.total_seconds())
|
|
|
|
zones = ["going-insecure.kasp", "going-insecure-dynamic.kasp"]
|
|
|
|
for parent in zones:
|
|
|
|
# Step 1.
|
|
|
|
# Key goal states should be HIDDEN.
|
|
|
|
# The DS may be removed if we are going insecure.
|
|
|
|
step = {
|
|
|
|
"zone": f"step1.{parent}",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": "insecure",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk 0 {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offval}",
|
|
|
|
f"zsk {lifetime['P60D']} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DS becomes HIDDEN. This
|
|
|
|
# happens after the# parent propagation delay plus DS TTL.
|
|
|
|
"nextev": default_config["ds-ttl"]
|
|
|
|
+ default_config["parent-propagation-delay"],
|
|
|
|
# Going insecure, check for CDS/CDNSKEY DELETE, and skip key timing checks.
|
|
|
|
"cds-delete": True,
|
|
|
|
"check-keytimes": False,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
|
|
|
# Step 2.
|
|
|
|
# The DS is long enough removed from the zone to be considered
|
|
|
|
# HIDDEN. This means the DNSKEY and the KSK signatures can be
|
|
|
|
# removed.
|
|
|
|
step = {
|
|
|
|
"zone": f"step2.{parent}",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": "insecure",
|
|
|
|
"keyprops": [
|
|
|
|
f"ksk 0 {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{offval}",
|
|
|
|
f"zsk {lifetime['P60D']} {alg} {size} goal:hidden dnskey:unretentive zrrsig:unretentive offset:{offval}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DNSKEY becomes HIDDEN.
|
|
|
|
# This happens after the propagation delay, plus DNSKEY TTL.
|
|
|
|
"nextev": oldttl + default_config["zone-propagation-delay"],
|
|
|
|
# Zone is no longer signed.
|
|
|
|
"zone-signed": False,
|
|
|
|
"check-keytimes": False,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
|
|
|
# Test going straight to none.
|
|
|
|
isctest.log.info("check going straight to none (after reconfig)")
|
|
|
|
zones = [
|
|
|
|
"step1.going-straight-to-none.kasp",
|
|
|
|
"step1.going-straight-to-none-dynamic.kasp",
|
|
|
|
]
|
|
|
|
for zone in zones:
|
|
|
|
step = {
|
|
|
|
"zone": zone,
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": default_config,
|
|
|
|
"policy": None,
|
|
|
|
# These zones will go bogus after signatures expire, but
|
|
|
|
# remain validly signed for now.
|
|
|
|
"keyprops": [
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
],
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
steps.append(step)
|
|
|
|
|
2025-03-19 16:28:33 +01:00
|
|
|
# Test algorithm rollover (KSK/ZSK split) (after reconfig).
|
|
|
|
isctest.log.info("check algorithm rollover ksk/zsk split (after reconfig)")
|
|
|
|
offset = -timedelta(days=7)
|
|
|
|
offval = int(offset.total_seconds())
|
|
|
|
ipub = Ipub(algoroll_config)
|
|
|
|
ipubc = IpubC(algoroll_config, rollover=False)
|
|
|
|
iret = Iret(algoroll_config, rollover=False)
|
|
|
|
iretKSK = Iret(algoroll_config, zsk=False, ksk=True, rollover=False)
|
|
|
|
keyttlprop = (
|
|
|
|
algoroll_config["dnskey-ttl"] + algoroll_config["zone-propagation-delay"]
|
|
|
|
)
|
|
|
|
offsets = {}
|
|
|
|
offsets["step2"] = -int(ipub.total_seconds())
|
|
|
|
offsets["step3"] = -int(iret.total_seconds())
|
|
|
|
offsets["step4"] = offsets["step3"] - int(iretKSK.total_seconds())
|
|
|
|
offsets["step5"] = offsets["step4"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step6"] = offsets["step5"] - int(iret.total_seconds())
|
|
|
|
algo_steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
"zone": "step1.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"keyprops": [
|
|
|
|
# The RSASHA keys are outroducing.
|
|
|
|
f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
|
|
|
|
# The ECDSAP256SHA256 keys are introducing.
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
|
|
|
|
f"zsk 0 {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured",
|
|
|
|
],
|
|
|
|
# Next key event is when the ecdsa256 keys have been propagated.
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
"zone": "step2.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"keyprops": [
|
|
|
|
# The RSASHA keys are outroducing, but need to stay present
|
|
|
|
# until the new algorithm chain of trust has been established.
|
|
|
|
# Thus the expected key states of these keys stay the same.
|
|
|
|
f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
|
|
|
|
# The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset is
|
|
|
|
# omnipresent, but the zone signatures are not.
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:hidden offset:{offsets['step2']}",
|
|
|
|
f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{offsets['step2']}",
|
|
|
|
],
|
|
|
|
# Next key event is when all zone signatures are signed with the new
|
|
|
|
# algorithm. This is the max-zone-ttl plus zone propagation delay. But
|
|
|
|
# the publication interval has already passed. Also, prevent intermittent
|
|
|
|
# false positives on slow platforms by subtracting the time passed between
|
|
|
|
# key creation and invoking 'rndc reconfig'.
|
|
|
|
"nextev": ipubc - ipub - time_passed,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
"zone": "step3.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"keyprops": [
|
|
|
|
# The DS can be swapped.
|
|
|
|
f"ksk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offval}",
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{offsets['step3']}",
|
|
|
|
f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step3']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DS becomes OMNIPRESENT. This happens
|
|
|
|
# after the retire interval.
|
|
|
|
"nextev": iretKSK - time_passed,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
"zone": "step4.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"keyprops": [
|
|
|
|
# The old DS is HIDDEN, we can remove the old algorithm records.
|
|
|
|
f"ksk 0 8 2048 goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:hidden dnskey:unretentive zrrsig:unretentive offset:{offval}",
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step4']}",
|
|
|
|
f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step4']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the old DNSKEY becomes HIDDEN.
|
|
|
|
# This happens after the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 5.
|
|
|
|
"zone": "step5.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"keyprops": [
|
|
|
|
# The DNSKEY becomes HIDDEN.
|
|
|
|
f"ksk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:hidden dnskey:hidden zrrsig:unretentive offset:{offval}",
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step5']}",
|
|
|
|
f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step5']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the RSASHA signatures become HIDDEN.
|
|
|
|
# This happens after the max-zone-ttl plus zone propagation delay
|
|
|
|
# minus the time already passed since the UNRETENTIVE state has
|
|
|
|
# been reached. Prevent intermittent false positives on slow
|
|
|
|
# platforms by subtracting the number of seconds which passed
|
|
|
|
# between key creation and invoking 'rndc reconfig'.
|
|
|
|
"nextev": iret - iretKSK - keyttlprop - time_passed,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 6.
|
|
|
|
"zone": "step6.algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "ecdsa256",
|
|
|
|
"keyprops": [
|
|
|
|
# The zone signatures are now HIDDEN.
|
|
|
|
f"ksk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{offval}",
|
|
|
|
f"zsk 0 8 2048 goal:hidden dnskey:hidden zrrsig:hidden offset:{offval}",
|
|
|
|
f"ksk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step6']}",
|
|
|
|
f"zsk 0 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step6']}",
|
|
|
|
],
|
|
|
|
# Next key event is never since we established the policy and the
|
|
|
|
# keys have an unlimited lifetime. Fallback to the default
|
|
|
|
# loadkeys interval.
|
|
|
|
"nextev": timedelta(hours=1),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
steps = steps + algo_steps
|
|
|
|
|
|
|
|
# Test algorithm rollover (CSK) (after reconfig).
|
|
|
|
isctest.log.info("check algorithm rollover csk (after reconfig)")
|
|
|
|
offsets = {}
|
|
|
|
offsets["step2"] = -int(ipub.total_seconds())
|
|
|
|
offsets["step3"] = -int(iret.total_seconds())
|
|
|
|
offsets["step4"] = offsets["step3"] - int(iretKSK.total_seconds())
|
|
|
|
offsets["step5"] = offsets["step4"] - int(keyttlprop.total_seconds())
|
|
|
|
offsets["step6"] = offsets["step5"] - int(iret.total_seconds())
|
|
|
|
algo_steps = [
|
|
|
|
{
|
|
|
|
# Step 1.
|
|
|
|
"zone": "step1.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
# The RSASHA keys are outroducing.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
# The ECDSAP256SHA256 keys are introducing.
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
|
|
|
],
|
|
|
|
# Next key event is when the ecdsa256 keys have been propagated.
|
|
|
|
"nextev": ipub,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 2.
|
|
|
|
"zone": "step2.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
# The RSASHA keys are outroducing, but need to stay present
|
|
|
|
# until the new algorithm chain of trust has been established.
|
|
|
|
# Thus the expected key states of these keys stay the same.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offval}",
|
|
|
|
# The ECDSAP256SHA256 keys are introducing. The DNSKEY RRset is
|
|
|
|
# omnipresent, but the zone signatures are not.
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{offsets['step2']}",
|
|
|
|
],
|
|
|
|
# Next key event is when all zone signatures are signed with the
|
|
|
|
# new algorithm. This is the child publication interval, minus
|
|
|
|
# the publication interval has already passed. Also, prevent
|
|
|
|
# intermittent false positives on slow platforms by subtracting
|
|
|
|
# the time passed between key creation and invoking 'rndc reconfig'.
|
|
|
|
"nextev": ipubc - ipub - time_passed,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 3.
|
|
|
|
"zone": "step3.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
# The DS can be swapped.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:unretentive offset:{offval}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{offsets['step3']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DS becomes OMNIPRESENT. This happens
|
|
|
|
# after the publication interval of the parent side.
|
|
|
|
"nextev": iretKSK - time_passed,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 4.
|
|
|
|
"zone": "step4.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
# The old DS is HIDDEN, we can remove the old algorithm records.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:unretentive ds:hidden offset:{offval}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step4']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the old DNSKEY becomes HIDDEN.
|
|
|
|
# This happens after the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": keyttlprop,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 5.
|
|
|
|
"zone": "step5.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
# The DNSKEY becomes HIDDEN.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden zrrsig:unretentive ds:hidden offset:{offval}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step5']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the RSASHA signatures become HIDDEN.
|
|
|
|
# This happens after the max-zone-ttl plus zone propagation delay
|
|
|
|
# minus the time already passed since the UNRETENTIVE state has
|
|
|
|
# been reached. Prevent intermittent false positives on slow
|
|
|
|
# platforms by subtracting the number of seconds which passed
|
|
|
|
# between key creation and invoking 'rndc reconfig'.
|
|
|
|
"nextev": iret - iretKSK - keyttlprop - time_passed,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
# Step 6.
|
|
|
|
"zone": "step6.csk-algorithm-roll.kasp",
|
|
|
|
"cdss": cdss,
|
|
|
|
"config": algoroll_config,
|
|
|
|
"policy": "csk-algoroll",
|
|
|
|
"keyprops": [
|
|
|
|
# The zone signatures are now HIDDEN.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offval}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step6']}",
|
|
|
|
],
|
|
|
|
# Next key event is never since we established the policy and the
|
|
|
|
# keys have an unlimited lifetime. Fallback to the default
|
|
|
|
# loadkeys interval.
|
|
|
|
"nextev": timedelta(hours=1),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
steps = steps + algo_steps
|
|
|
|
|
2025-03-19 11:35:18 +01:00
|
|
|
for step in steps:
|
2025-06-02 17:20:06 +02:00
|
|
|
isctest.kasp.check_rollover_step(server, step["config"], step["policy"], step)
|