Commit 021ea1508f for bind
commit 021ea1508f46b482c7186a4be70300c2596735b3
Author: Nicki Křížek <nicki@isc.org>
Date: Fri Oct 2 14:02:24 2026 +0000
Test an offline KSK and a ZSK with the same key tag
The keys are pre-generated, as finding a colliding pair takes a
brute-force search.
Assisted-by: Claude:claude-opus-5-5
diff --git a/REUSE.toml b/REUSE.toml
index 2602a08198..d5a03a653b 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -14,11 +14,14 @@ path = [
"**/**.good",
"**/**.key",
"**/**.key.in",
+ "**/**.key.j2",
"**/**.keytab",
"**/**.pem",
"**/**.private",
+ "**/**.private.j2",
"**/**.raw",
"**/**.saved",
+ "**/**.state.j2",
"**/**.zonelist",
"**/**dig.out**",
"**/root.hint*",
diff --git a/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.key.j2 b/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.key.j2
new file mode 100644
index 0000000000..cf9fe80773
--- /dev/null
+++ b/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.key.j2
@@ -0,0 +1,5 @@
+; This is a zone-signing key, keyid 59801, for collision.test.
+; Created: 20261002135138 (Fri Oct 2 13:51:38 2026)
+; Publish: 20261002114638 (Fri Oct 2 11:46:38 2026)
+; Activate: 20261002135138 (Fri Oct 2 13:51:38 2026)
+collision.test. 3600 IN DNSKEY 256 3 13 Y3DnyQIs45OFmo/RsU26hphYm/6o0kJi 5vpXl4+Lkw4pEE4BhwXbPlg6b3cGNB4T GDv/1c8Xr1L65OKwcvwGPQ==
diff --git a/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.private.j2 b/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.private.j2
new file mode 100644
index 0000000000..ef93c85abe
--- /dev/null
+++ b/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.private.j2
@@ -0,0 +1,6 @@
+Private-key-format: v1.3
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: NxYzgdqUTRx1DnuItRAvOGQZqAKmUfpowxI/O4xkL3I=
+Created: 20261002135138
+Publish: 20261002114638
+Activate: 20261002135138
diff --git a/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.state.j2 b/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.state.j2
new file mode 100644
index 0000000000..f34ff650f5
--- /dev/null
+++ b/bin/tests/system/ksr/ns1/Kcollision.test.+013+59801.state.j2
@@ -0,0 +1,9 @@
+; This is the state of key 59801, for collision.test.
+Algorithm: 13
+Length: 256
+Lifetime: 0
+KSK: no
+ZSK: yes
+Generated: 20261002135138 (Fri Oct 2 13:51:38 2026)
+Published: 20261002114638 (Fri Oct 2 11:46:38 2026)
+Active: 20261002135138 (Fri Oct 2 13:51:38 2026)
diff --git a/bin/tests/system/ksr/ns1/named.conf.j2 b/bin/tests/system/ksr/ns1/named.conf.j2
index f0b586c515..1dd240f972 100644
--- a/bin/tests/system/ksr/ns1/named.conf.j2
+++ b/bin/tests/system/ksr/ns1/named.conf.j2
@@ -87,6 +87,14 @@ dnssec-policy "fast" {
parent-propagation-delay 5;
};
+dnssec-policy "collision" {
+ offline-ksk yes;
+ keys {
+ ksk lifetime unlimited algorithm ecdsap256sha256;
+ zsk lifetime unlimited algorithm ecdsap256sha256;
+ };
+};
+
dnssec-policy "invalid-skr" {
offline-ksk yes;
keys {
diff --git a/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.key.j2 b/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.key.j2
new file mode 100644
index 0000000000..d8c2e2cefe
--- /dev/null
+++ b/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.key.j2
@@ -0,0 +1,6 @@
+; This is a key-signing key, keyid 59801, for collision.test.
+; Created: 20261002135138 (Fri Oct 2 13:51:38 2026)
+; Publish: 20261002114638 (Fri Oct 2 11:46:38 2026)
+; Activate: 20261002135138 (Fri Oct 2 13:51:38 2026)
+; SyncPublish: 20261003115138 (Sat Oct 3 11:51:38 2026)
+collision.test. 3600 IN DNSKEY 257 3 13 JOazOBxTLZ5WaN4G9XiHpF89zEyKXfF7 Gaf6IV0gMazEGet++f2s45R+h/OmYAfR 6V9t3B4QMJ7hS+8NT8Lg0A==
diff --git a/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.private.j2 b/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.private.j2
new file mode 100644
index 0000000000..d83ac28626
--- /dev/null
+++ b/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.private.j2
@@ -0,0 +1,7 @@
+Private-key-format: v1.3
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: +ZKH/ZSHNZbYn6dst6ZMUJePTYd2KgGCPYqqBwPJkaA=
+Created: 20261002135138
+Publish: 20261002114638
+Activate: 20261002135138
+SyncPublish: 20261003115138
diff --git a/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.state.j2 b/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.state.j2
new file mode 100644
index 0000000000..04be5a59eb
--- /dev/null
+++ b/bin/tests/system/ksr/ns1/offline/Kcollision.test.+013+59801.state.j2
@@ -0,0 +1,10 @@
+; This is the state of key 59801, for collision.test.
+Algorithm: 13
+Length: 256
+Lifetime: 0
+KSK: yes
+ZSK: no
+Generated: 20261002135138 (Fri Oct 2 13:51:38 2026)
+Published: 20261002114638 (Fri Oct 2 11:46:38 2026)
+Active: 20261002135138 (Fri Oct 2 13:51:38 2026)
+PublishCDS: 20261003115138 (Sat Oct 3 11:51:38 2026)
diff --git a/bin/tests/system/ksr/ns1/setup.sh b/bin/tests/system/ksr/ns1/setup.sh
index 482bafa9e8..3c69533cbc 100644
--- a/bin/tests/system/ksr/ns1/setup.sh
+++ b/bin/tests/system/ksr/ns1/setup.sh
@@ -16,7 +16,6 @@
# Key directories
mkdir keydir
-mkdir offline
# Zone files
cp template.db.in common.test.db
@@ -29,3 +28,4 @@ cp template.db.in two-tone.test.db
cp template.db.in ksk-roll.test.db
cp template.db.in invalid-skr.test.db
cp template.db.in fast.test.db
+cp template.db.in collision.test.db
diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py
index c9752b71b8..f8db9a81cf 100644
--- a/bin/tests/system/ksr/tests_ksr.py
+++ b/bin/tests/system/ksr/tests_ksr.py
@@ -10,6 +10,7 @@
# information regarding copyright ownership.
from datetime import timedelta
+from re import compile as Re
import os
import re
@@ -18,7 +19,7 @@ import time
import pytest
-from isctest.algorithms import Algorithm
+from isctest.algorithms import ECDSAP256SHA256, Algorithm
from isctest.kasp import KeyTimingMetadata
from rollover.common import TIMEDELTA
@@ -27,6 +28,7 @@ import isctest
pytestmark = pytest.mark.extra_artifacts(
[
"K*",
+ "collision.test.*",
"common.test.*",
"fast.test.*",
"future.test.*",
@@ -40,6 +42,7 @@ pytestmark = pytest.mark.extra_artifacts(
"ns1/K*",
"ns1/_default.nzd",
"ns1/_default.nzf",
+ "ns1/collision.test.*",
"ns1/common.test.db",
"ns1/common.test.db.jbk",
"ns1/common.test.db.signed",
@@ -1478,6 +1481,74 @@ def test_ksr_fast(ns1):
isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
+def test_ksr_keytag_collision(ns1):
+ # The offline KSK and the ZSK are generated by different parties, so
+ # their key tags may collide. Making them collide takes a brute-force
+ # search, so the keys are pre-generated.
+ zone = "collision.test"
+ policy = "collision"
+ alg = ECDSAP256SHA256
+ n = 1
+
+ ksks = isctest.kasp.keydir_to_keylist(zone, "ns1/offline")
+ zsks = isctest.kasp.keydir_to_keylist(zone, "ns1")
+ assert len(ksks) == 1
+ assert len(zsks) == 1
+ assert ksks[0].tag == zsks[0].tag
+
+ check_keys(ksks, None, CONFIG, alg.dst, alg.bits)
+ check_keys(zsks, None, CONFIG, alg.dst, alg.bits)
+
+ now = KeyTimingMetadata.now()
+ until = now + timedelta(days=365)
+ ksr_fname = f"{zone}.ksr.{n}"
+ ksr(
+ zone,
+ policy,
+ "request",
+ options=f"-K ns1 -i {now} -e +1y",
+ to_file=ksr_fname,
+ )
+ check_keysigningrequest(ksr_fname, zsks, now, until)
+
+ refresh = -432000 # 5 days
+ skr_fname = f"{zone}.skr.{n}"
+ ksr(
+ zone,
+ policy,
+ "sign",
+ options=f"-K ns1/offline -f {ksr_fname} -i {now} -e +1y",
+ to_file=skr_fname,
+ )
+ check_signedkeyresponse(skr_fname, CONFIG, zone, ksks, zsks, now, until, refresh)
+
+ # Without an SKR, the rekey that follows loading the zone fails.
+ with ns1.watch_log_from_here() as watcher:
+ ns1.rndc(
+ f"addzone {zone} "
+ + "{ type primary; file "
+ + f'"{zone}.db"; dnssec-policy {policy}; '
+ + "};",
+ )
+ watcher.wait_for_line(
+ f"zone {zone}/IN (signed): zone_rekey failure: no SKR file"
+ )
+
+ shutil.copyfile(skr_fname, f"ns1/{skr_fname}")
+ with ns1.watch_log_from_here() as watcher:
+ ns1.rndc(f"skr -import {skr_fname} {zone}")
+ rekey = watcher.wait_for_line(
+ Re(rf"zone {zone}/IN \(signed\): zone_rekey (done|failure.*)")
+ )
+ assert rekey.group(1) == "done", f"zone_rekey {rekey.group(1)}"
+
+ isctest.kasp.check_dnssecstatus(ns1, zone, zsks, policy=policy, verbose=True)
+ isctest.kasp.check_dnssec_verify(ns1, zone)
+ check_keys(zsks, None, CONFIG, alg.dst, alg.bits, with_state=True)
+ isctest.kasp.check_apex(ns1, zone, ksks, zsks, offline_ksk=True)
+ isctest.kasp.check_subdomain(ns1, zone, ksks, zsks, offline_ksk=True)
+
+
def test_ksr_oversize(ns1):
zone = "invalid-skr.test"
n = 1