2025-06-06 16:49:14 +02: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.
|
|
|
|
|
|
|
|
# pylint: disable=redefined-outer-name,unused-import
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
import isctest
|
|
|
|
from isctest.kasp import KeyTimingMetadata
|
2025-07-23 09:27:04 +02:00
|
|
|
from isctest.util import param
|
2025-08-04 16:30:41 +02:00
|
|
|
from rollover.common import (
|
2025-06-06 16:49:14 +02:00
|
|
|
pytestmark,
|
|
|
|
alg,
|
|
|
|
size,
|
|
|
|
CDSS,
|
|
|
|
ALGOROLL_CONFIG,
|
|
|
|
ALGOROLL_IPUB,
|
|
|
|
ALGOROLL_IPUBC,
|
|
|
|
ALGOROLL_IRET,
|
|
|
|
ALGOROLL_IRETKSK,
|
|
|
|
ALGOROLL_KEYTTLPROP,
|
|
|
|
ALGOROLL_OFFSETS,
|
|
|
|
ALGOROLL_OFFVAL,
|
2025-07-23 09:27:04 +02:00
|
|
|
DURATION,
|
2025-06-06 16:49:14 +02:00
|
|
|
TIMEDELTA,
|
|
|
|
)
|
|
|
|
|
|
|
|
CONFIG = ALGOROLL_CONFIG
|
|
|
|
POLICY = "csk-algoroll"
|
|
|
|
TIME_PASSED = 0 # set in reconfigure() fixture
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
2025-07-08 15:26:09 +02:00
|
|
|
def reconfigure(ns6, templates):
|
2025-06-06 16:49:14 +02:00
|
|
|
global TIME_PASSED # pylint: disable=global-statement
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, "step1.csk-algorithm-roll.kasp")
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
templates.render("ns6/named.conf", {"csk_roll": True})
|
2025-07-08 13:46:01 +02:00
|
|
|
start_time = KeyTimingMetadata.now()
|
2025-07-08 15:26:09 +02:00
|
|
|
ns6.reconfigure()
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
# Calculate time passed to correctly check for next key events.
|
|
|
|
TIME_PASSED = KeyTimingMetadata.now().value - start_time.value
|
|
|
|
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tld",
|
|
|
|
[
|
|
|
|
param("kasp"),
|
|
|
|
param("manual"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_algoroll_csk_reconfig_step1(tld, ns6, alg, size):
|
|
|
|
zone = f"step1.csk-algorithm-roll.{tld}"
|
|
|
|
policy = f"{POLICY}-{tld}"
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
if tld == "manual":
|
|
|
|
# Same as initial.
|
|
|
|
step = {
|
|
|
|
"zone": zone,
|
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk 0 8 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{-DURATION['P7D']}",
|
|
|
|
],
|
|
|
|
"manual-mode": True,
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
keys = isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
|
|
|
|
|
|
|
# Check logs.
|
|
|
|
tag = keys[0].key.tag
|
|
|
|
msg1 = f"keymgr-manual-mode: block retire DNSKEY {zone}/RSASHA256/{tag} (CSK)"
|
|
|
|
msg2 = f"keymgr-manual-mode: block new key generation for zone {zone} (policy {policy})"
|
|
|
|
ns6.log.expect(msg1)
|
|
|
|
ns6.log.expect(msg2)
|
|
|
|
|
|
|
|
# Force step.
|
|
|
|
with ns6.watch_log_from_here() as watcher:
|
|
|
|
ns6.rndc(f"dnssec -step {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
|
|
|
# Check state after step.
|
2025-06-06 16:49:14 +02:00
|
|
|
step = {
|
2025-07-08 13:46:01 +02:00
|
|
|
"zone": zone,
|
2025-06-06 16:49:14 +02:00
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
# The RSASHA keys are outroducing.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_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": ALGOROLL_IPUB,
|
|
|
|
}
|
2025-07-23 09:27:04 +02:00
|
|
|
isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tld",
|
|
|
|
[
|
|
|
|
param("kasp"),
|
|
|
|
param("manual"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_algoroll_csk_reconfig_step2(tld, ns6, alg, size):
|
|
|
|
zone = f"step2.csk-algorithm-roll.{tld}"
|
|
|
|
policy = f"{POLICY}-{tld}"
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
# manual-mode: Nothing changing in the zone, no 'dnssec -step' required.
|
|
|
|
|
2025-06-06 16:49:14 +02:00
|
|
|
step = {
|
2025-07-08 13:46:01 +02:00
|
|
|
"zone": zone,
|
2025-06-06 16:49:14 +02:00
|
|
|
"cdss": CDSS,
|
|
|
|
"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:{ALGOROLL_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:{ALGOROLL_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": ALGOROLL_IPUBC - ALGOROLL_IPUB - TIME_PASSED,
|
|
|
|
}
|
2025-07-23 09:27:04 +02:00
|
|
|
isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tld",
|
|
|
|
[
|
|
|
|
param("kasp"),
|
|
|
|
param("manual"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_algoroll_csk_reconfig_step3(tld, ns6, alg, size):
|
|
|
|
zone = f"step3.csk-algorithm-roll.{tld}"
|
|
|
|
policy = f"{POLICY}-{tld}"
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
if tld == "manual":
|
|
|
|
# Same as step 2, but the zone signatures have become OMNIPRESENT.
|
|
|
|
step = {
|
|
|
|
"zone": zone,
|
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step3']}",
|
|
|
|
],
|
|
|
|
"manual-mode": True,
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
keys = isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
|
|
|
|
|
|
|
# Check logs.
|
|
|
|
tag = keys[1].key.tag
|
|
|
|
msg = f"keymgr-manual-mode: block transition CSK {zone}/ECDSAP256SHA256/{tag} type DS state HIDDEN to state RUMOURED"
|
|
|
|
ns6.log.expect(msg)
|
|
|
|
|
|
|
|
# Force step.
|
|
|
|
with ns6.watch_log_from_here() as watcher:
|
|
|
|
ns6.rndc(f"dnssec -step {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
|
|
|
# Check logs.
|
|
|
|
tag = keys[0].key.tag
|
|
|
|
msg = f"keymgr-manual-mode: block transition CSK {zone}/RSASHA256/{tag} type DS state OMNIPRESENT to state UNRETENTIVE"
|
|
|
|
if msg in ns6.log:
|
|
|
|
# Force step.
|
|
|
|
isctest.log.debug(
|
|
|
|
f"keymgr-manual-mode blocking transition CSK {zone}/RSASHA256/{tag} type DS state OMNIPRESENT to state UNRETENTIVE, step again"
|
|
|
|
)
|
|
|
|
with ns6.watch_log_from_here() as watcher:
|
|
|
|
ns6.rndc(f"dnssec -step {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
2025-06-06 16:49:14 +02:00
|
|
|
step = {
|
2025-07-08 13:46:01 +02:00
|
|
|
"zone": zone,
|
2025-06-06 16:49:14 +02:00
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
# The DS can be swapped.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:unretentive offset:{ALGOROLL_OFFVAL}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{ALGOROLL_OFFSETS['step3']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the DS becomes OMNIPRESENT. This happens
|
|
|
|
# after the publication interval of the parent side.
|
|
|
|
"nextev": ALGOROLL_IRETKSK - TIME_PASSED,
|
|
|
|
}
|
2025-07-23 09:27:04 +02:00
|
|
|
isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tld",
|
|
|
|
[
|
|
|
|
param("kasp"),
|
|
|
|
param("manual"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_algoroll_csk_reconfig_step4(tld, ns6, alg, size):
|
|
|
|
zone = f"step4.csk-algorithm-roll.{tld}"
|
|
|
|
policy = f"{POLICY}-{tld}"
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
if tld == "manual":
|
|
|
|
# Same as step 3, but the DS has become HIDDEN/OMNIPRESENT.
|
|
|
|
step = {
|
|
|
|
"zone": zone,
|
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFVAL}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}",
|
|
|
|
],
|
|
|
|
"manual-mode": True,
|
|
|
|
"nextev": None,
|
|
|
|
}
|
|
|
|
keys = isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
|
|
|
|
|
|
|
# Check logs.
|
|
|
|
tag = keys[0].key.tag
|
|
|
|
msg = f"keymgr-manual-mode: block transition CSK {zone}/RSASHA256/{tag} type DNSKEY state OMNIPRESENT to state UNRETENTIVE"
|
|
|
|
ns6.log.expect(msg)
|
|
|
|
|
|
|
|
# Force step.
|
|
|
|
with ns6.watch_log_from_here() as watcher:
|
|
|
|
ns6.rndc(f"dnssec -step {zone}")
|
|
|
|
watcher.wait_for_line(f"keymgr: {zone} done")
|
|
|
|
|
2025-06-06 16:49:14 +02:00
|
|
|
step = {
|
2025-07-08 13:46:01 +02:00
|
|
|
"zone": zone,
|
2025-06-06 16:49:14 +02:00
|
|
|
"cdss": CDSS,
|
|
|
|
"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:{ALGOROLL_OFFVAL}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}",
|
|
|
|
],
|
|
|
|
# Next key event is when the old DNSKEY becomes HIDDEN.
|
|
|
|
# This happens after the DNSKEY TTL plus zone propagation delay.
|
|
|
|
"nextev": ALGOROLL_KEYTTLPROP,
|
|
|
|
}
|
2025-07-23 09:27:04 +02:00
|
|
|
isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tld",
|
|
|
|
[
|
|
|
|
param("kasp"),
|
|
|
|
param("manual"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_algoroll_csk_reconfig_step5(tld, ns6, alg, size):
|
|
|
|
zone = f"step5.csk-algorithm-roll.{tld}"
|
|
|
|
policy = f"{POLICY}-{tld}"
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
# manual-mode: Nothing changing in the zone, no 'dnssec -step' required.
|
|
|
|
|
2025-06-06 16:49:14 +02:00
|
|
|
step = {
|
2025-07-08 13:46:01 +02:00
|
|
|
"zone": zone,
|
2025-06-06 16:49:14 +02:00
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
# The DNSKEY becomes HIDDEN.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden zrrsig:unretentive ds:hidden offset:{ALGOROLL_OFFVAL}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_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": ALGOROLL_IRET - ALGOROLL_IRETKSK - ALGOROLL_KEYTTLPROP - TIME_PASSED,
|
|
|
|
}
|
2025-07-23 09:27:04 +02:00
|
|
|
isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|
2025-06-06 16:49:14 +02:00
|
|
|
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tld",
|
|
|
|
[
|
|
|
|
param("kasp"),
|
|
|
|
param("manual"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_algoroll_csk_reconfig_step6(tld, ns6, alg, size):
|
|
|
|
zone = f"step6.csk-algorithm-roll.{tld}"
|
|
|
|
policy = f"{POLICY}-{tld}"
|
2025-07-08 13:46:01 +02:00
|
|
|
|
|
|
|
isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
|
|
|
|
|
2025-07-23 09:27:04 +02:00
|
|
|
# manual-mode: Nothing changing in the zone, no 'dnssec -step' required.
|
|
|
|
|
2025-06-06 16:49:14 +02:00
|
|
|
step = {
|
2025-07-08 13:46:01 +02:00
|
|
|
"zone": zone,
|
2025-06-06 16:49:14 +02:00
|
|
|
"cdss": CDSS,
|
|
|
|
"keyprops": [
|
|
|
|
# The zone signatures are now HIDDEN.
|
|
|
|
f"csk 0 8 2048 goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{ALGOROLL_OFFVAL}",
|
|
|
|
f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_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["PT1H"],
|
|
|
|
}
|
2025-07-23 09:27:04 +02:00
|
|
|
isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
|