From 9dc039f735788d81f695dccac6ffad65e8547476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 15:57:48 +0200 Subject: [PATCH 1/7] Read DNSKEY TTL from kasp.Key.dnskey Remove the redundant ttl() method. The DNSKEY RR already provides the TTL. --- bin/tests/system/isctest/kasp.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 5987de2c77..b919753dd7 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -506,14 +506,6 @@ class Key: return isctest.vars.algorithms.RSASHA512OID.number return alg - def ttl(self) -> int: - with open(self.keyfile, "r", encoding="utf-8") as file: - for line in file: - if line.startswith(";"): - continue - return int(line.split()[1]) - return 0 - @property def dnskey(self) -> dns.rrset.RRset: with open(self.keyfile, "r", encoding="utf-8") as file: @@ -588,7 +580,7 @@ class Key: dsfromkey_command = [ os.environ.get("DSFROMKEY"), "-T", - str(self.ttl()), + str(self.dnskey.ttl), "-a", alg, "-C", From 35fc5ce8b4ddc71d032b634cd7a827b606742495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 12:34:48 +0000 Subject: [PATCH 2/7] Merge kasp.Key functionality into zone.FileZoneKey Make zone.FileZoneKey the single representation of a file-backed key (typically generated by dnssec-keygen). Move the common key-related functionality into zone.FileZoneKey, and extend that functionality in kasp.Key to also add state and timing related operations on top. Remove duplicate into_ta() function. Note that is_ksk() is implemented differently for kasp.Key: with the metadata file available, the KSK status is loaded from that file, as it indicates the authoritative policy decision which makes the key a KSK. In zone.FileZoneKey which doesn't work with the metadata file, the KSK status if inferred from the DNSKEY SEP flag - the best information available for that class. Assisted-by: Claude:claude-opus-4-8 --- bin/tests/system/isctest/kasp.py | 40 +++++++------------------------- bin/tests/system/isctest/zone.py | 29 ++++++++++++++++------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index b919753dd7..e86b9d6a81 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -20,22 +20,19 @@ import os import re import time -import dns.dnssec import dns.exception import dns.message import dns.name import dns.rcode import dns.rdataclass import dns.rdatatype -import dns.rrset import dns.tsig import dns.zone -import dns.zonefile from isctest.instance import NamedInstance from isctest.run import EnvCmd -from isctest.template import TrustAnchor from isctest.vars.algorithms import ALL_ALGORITHMS_BY_NUM, Algorithm +from isctest.zone import FileZoneKey import isctest.log import isctest.query @@ -380,26 +377,18 @@ class SettimeOptions: @total_ordering -class Key: +class Key(FileZoneKey): """ - Represent a key from a keyfile. + A FileZoneKey specialized with KASP timing and state-file operations. - This object keeps track of its origin (keydir + name), can be used to - retrieve metadata from the underlying files and supports convenience - operations for KASP tests. + Inherits the key-material accessors (dnskey, into_ta, ...) from FileZoneKey + and adds the metadata reads, signing-state derivation, and timing + convenience operations used by the KASP/rollover tests. """ def __init__(self, name: str, keydir: str | Path | None = None): - self.name = name - if keydir is None: - self.keydir = Path() - else: - self.keydir = Path(keydir) - self.path = str(self.keydir / name) - self.privatefile = f"{self.path}.private" - self.keyfile = f"{self.path}.key" + super().__init__(name, keydir) self.statefile = f"{self.path}.state" - self.tag = int(self.name[-5:]) self.external = False def get_timing( @@ -522,20 +511,9 @@ class Key: ), f"DNSKEY not found in {self.keyfile}" return dnskey_rr - def into_ta(self, ta_type: str, dsdigest=dns.dnssec.DSDigest.SHA256) -> TrustAnchor: - dnskey = self.dnskey - if ta_type in ["static-ds", "initial-ds"]: - ds = dns.dnssec.make_ds(dnskey.name, dnskey[0], dsdigest) - parts = str(ds).split() - contents = " ".join(parts[:3]) + f' "{parts[3]}"' - elif ta_type in ["static-key", "initial-key"]: - parts = str(dnskey).split() - contents = " ".join(parts[4:7]) + f' "{"".join(parts[7:])}"' - else: - raise ValueError(f"invalid trust anchor type: {ta_type}") - return TrustAnchor(str(dnskey.name), ta_type, contents) - def is_ksk(self) -> bool: + # KASP role follows the .state KSK metadata, not the DNSKEY SEP flag: + # a CSK may be configured without SEP (see the csk-nosep test). return self.get_metadata("KSK") == "yes" def is_zsk(self) -> bool: diff --git a/bin/tests/system/isctest/zone.py b/bin/tests/system/isctest/zone.py index df65932541..02b93c8b8d 100644 --- a/bin/tests/system/isctest/zone.py +++ b/bin/tests/system/isctest/zone.py @@ -27,8 +27,8 @@ import dns.name import dns.rdataclass import dns.rdatatype import dns.rrset +import dns.zonefile -from .kasp import Key from .log import debug from .run import EnvCmd from .template import NS1, Nameserver, TemplateEngine, TrustAnchor @@ -50,7 +50,7 @@ class ZoneKey(ABC): Abstract base for a DNSSEC key attached to a Zone. Two concrete implementations exist: - FileZoneKey — wraps a dnssec-keygen-managed key file pair (kasp.Key). + FileZoneKey — reads a dnssec-keygen-managed key file pair. PythonZoneKey — holds a Python-native (private_key, dnskey_rdata) pair required for dnspython-based operations and signing. @@ -107,18 +107,28 @@ class FileZoneKey(ZoneKey): """ A ZoneKey backed by dnssec-keygen-managed key files. - Constructed by FileZoneKey.generate(); callers normally do not - instantiate this directly. The underlying kasp.Key is accessible via - .key for working with timing metadata, state files, etc. + Reads key material directly from the .key/.private file pair. Normally + constructed via FileZoneKey.generate() (or Zone.add_keys()); + isctest.kasp.Key subclasses it to add KASP timing/state operations. """ - def __init__(self, key: Key, zone: Zone) -> None: - self.key = key + def __init__( + self, + name: str, + keydir: str | Path | None = None, + zone: Zone | None = None, + ) -> None: + self.name = name + self.keydir = Path() if keydir is None else Path(keydir) + self.path = str(self.keydir / name) + self.privatefile = f"{self.path}.private" + self.keyfile = f"{self.path}.key" + self.tag = int(self.name[-5:]) self.zone = zone @property def dnskey(self) -> dns.rrset.RRset: - return self.key.dnskey + raise NotImplementedError # TODO will be re-implemented in the followup commit def write_dsset( self, @@ -134,6 +144,7 @@ class FileZoneKey(ZoneKey): PythonZoneKey KSKs (PythonZoneKey appends to the same file); Zone.copy_dssets enforces this. """ + assert self.zone is not None, "write_dsset requires a zone-attached key" src = Path(self.zone.ns.name) / f"dsset-{self.zone.name}." shutil.copy(src, Path(target_dir)) debug(f"{self.zone.name}: dsset copied to {target_dir}") @@ -160,7 +171,7 @@ class FileZoneKey(ZoneKey): "KEYGEN", f"-q -a {alg.number} -b {alg.bits} -K {KEYDIR} -L {DNSKEY_TTL}" ) key_name = keygen(f"{params} {zone.name}", cwd=zone.ns.name).out.strip() - return FileZoneKey(Key(key_name, keydir=keydir), zone=zone) + return FileZoneKey(key_name, keydir=keydir, zone=zone) class PythonZoneKey(ZoneKey): From 55cd4a1e11af1bd83f3dcb24e38d24182bc1adf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 12:32:28 +0000 Subject: [PATCH 3/7] Move dnskey method from kasp.Key to zone.FileZoneKey Code move with one change - switch dnskey TTL from 300s (DEFAULT_TTL) to 3600s (DNSKEY_TTL). Assisted-by: Claude:claude-opus-4-8 --- bin/tests/system/isctest/kasp.py | 16 ---------------- bin/tests/system/isctest/zone.py | 14 +++++++++++++- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index e86b9d6a81..2bbcfce95a 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -495,22 +495,6 @@ class Key(FileZoneKey): return isctest.vars.algorithms.RSASHA512OID.number return alg - @property - def dnskey(self) -> dns.rrset.RRset: - with open(self.keyfile, "r", encoding="utf-8") as file: - rrsets = dns.zonefile.read_rrsets( - file.read(), - rdclass=None, # read rdclass from the file - default_ttl=DEFAULT_TTL, # use this TTL if not present - ) - assert len(rrsets) == 1, f"{self.keyfile} has multiple RRsets" - dnskey_rr = rrsets[0] - assert len(dnskey_rr) == 1, f"{self.keyfile} has multiple RRs" - assert ( - dnskey_rr.rdtype == dns.rdatatype.DNSKEY - ), f"DNSKEY not found in {self.keyfile}" - return dnskey_rr - def is_ksk(self) -> bool: # KASP role follows the .state KSK metadata, not the DNSKEY SEP flag: # a CSK may be configured without SEP (see the csk-nosep test). diff --git a/bin/tests/system/isctest/zone.py b/bin/tests/system/isctest/zone.py index 02b93c8b8d..a7ba22db90 100644 --- a/bin/tests/system/isctest/zone.py +++ b/bin/tests/system/isctest/zone.py @@ -128,7 +128,19 @@ class FileZoneKey(ZoneKey): @property def dnskey(self) -> dns.rrset.RRset: - raise NotImplementedError # TODO will be re-implemented in the followup commit + with open(self.keyfile, "r", encoding="utf-8") as file: + rrsets = dns.zonefile.read_rrsets( + file.read(), + rdclass=None, # read rdclass from the file + default_ttl=DNSKEY_TTL, # use this TTL if not present + ) + assert len(rrsets) == 1, f"{self.keyfile} has multiple RRsets" + dnskey_rr = rrsets[0] + assert len(dnskey_rr) == 1, f"{self.keyfile} has multiple RRs" + assert ( + dnskey_rr.rdtype == dns.rdatatype.DNSKEY + ), f"DNSKEY not found in {self.keyfile}" + return dnskey_rr def write_dsset( self, From 596e41553c95a8cb0d256521098959fe03a15a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 17:54:56 +0200 Subject: [PATCH 4/7] Add NSEC3RSASHA1 to list of algorithms This algorithm is deprecated and not currently used in our system tests, but it should be in the list of all algorithms. --- bin/tests/system/isctest/vars/algorithms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/tests/system/isctest/vars/algorithms.py b/bin/tests/system/isctest/vars/algorithms.py index 1ce1682481..0ff211da95 100644 --- a/bin/tests/system/isctest/vars/algorithms.py +++ b/bin/tests/system/isctest/vars/algorithms.py @@ -87,6 +87,7 @@ class AlgorithmSet(NamedTuple): RSASHA1 = Algorithm("RSASHA1", 5, 5, 2048) +NSEC3RSASHA1 = Algorithm("NSEC3RSASHA1", 7, 7, 2048) RSASHA256 = Algorithm("RSASHA256", 8, 8, 2048) RSASHA512 = Algorithm("RSASHA512", 10, 10, 2048) ECDSAP256SHA256 = Algorithm("ECDSAP256SHA256", 13, 13, 256) @@ -98,6 +99,7 @@ RSASHA512OID = Algorithm("RSASHA512OID", 254, 257, 2048) ALL_ALGORITHMS = [ RSASHA1, + NSEC3RSASHA1, RSASHA256, RSASHA512, ECDSAP256SHA256, From 7f69a57021d437d713a1f6eca82bf4ecb5cf475e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 17:56:00 +0200 Subject: [PATCH 5/7] Replace get_dnsalg() with kasp.Key.algorithm The get_dnsalg() was just a compatibility layer for 9.18 which lacked the Algorithm support - remove it in favor of using the .algorithm property. In order to properly support private OID algorithms, use the DST value which is unique across all algorithms. Also fix private_type_record(): the choice between the 5- and 7-byte signing record depends on the DST value (256/257 for the private-OID algorithms), not the on-wire number, which never reaches 256. Assisted-by: Claude:claude-opus-4-8 --- bin/tests/system/isctest/kasp.py | 35 ++++++++++----------- bin/tests/system/isctest/vars/algorithms.py | 4 +++ bin/tests/system/ksr/tests_ksr.py | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 2bbcfce95a..2c82cce37b 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -31,7 +31,7 @@ import dns.zone from isctest.instance import NamedInstance from isctest.run import EnvCmd -from isctest.vars.algorithms import ALL_ALGORITHMS_BY_NUM, Algorithm +from isctest.vars.algorithms import ALL_ALGORITHMS_BY_DST, ECDSAP256SHA256, Algorithm from isctest.zone import FileZoneKey import isctest.log @@ -215,7 +215,7 @@ class KeyProperties: @staticmethod def default(with_state=True) -> "KeyProperties": metadata = { - "Algorithm": isctest.vars.algorithms.ECDSAP256SHA256.number, + "Algorithm": ECDSAP256SHA256.number, "Length": 256, "Lifetime": 0, "KSK": "yes", @@ -487,14 +487,6 @@ class Key(FileZoneKey): return ksigning, zsigning - def get_dnsalg(self) -> int: - alg = int(self.get_metadata("Algorithm")) - if alg == isctest.vars.algorithms.RSASHA256OID.dst: - return isctest.vars.algorithms.RSASHA256OID.number - if alg == isctest.vars.algorithms.RSASHA512OID.dst: - return isctest.vars.algorithms.RSASHA512OID.number - return alg - def is_ksk(self) -> bool: # KASP role follows the .state KSK metadata, not the DNSKEY SEP flag: # a CSK may be configured without SEP (see the csk-nosep test). @@ -512,8 +504,15 @@ class Key(FileZoneKey): @property def algorithm(self) -> Algorithm: - num = int(self.get_metadata("Algorithm")) - return ALL_ALGORITHMS_BY_NUM[num] + """ + Obtain the algorithm from key metadata. + + The .state Algorithm field holds the DST identifier, which (unlike the + on-wire DNSKEY algorithm number) distinguishes the private-OID variants + RSASHA256OID and RSASHA512OID, both of which appear as 254 on the wire. + """ + dst = int(self.get_metadata("Algorithm")) + return ALL_ALGORITHMS_BY_DST[dst] def dnskey_equals(self, value, cdnskey=False): dnskey = value.split() @@ -951,7 +950,7 @@ def _check_signatures( offline_ksk=offline_ksk, zsk_missing=zsk_missing, smooth=smooth ) - alg = key.get_dnsalg() + alg = key.algorithm.number rtype = dns.rdatatype.to_text(covers) expect = rf"IN RRSIG {rtype} {alg} (\d) (\d+) (\d+) (\d+) {key.tag} {signer}" @@ -1716,10 +1715,10 @@ def private_type_record(zone: str, key: Key, rrtype: int = 65534) -> str: indicating that the signing process for this key is completed. """ keyid = key.tag - algorithm = key.get_dnsalg() - secalg = int(key.get_metadata("Algorithm")) + wire_alg = key.algorithm.number + dst_alg = key.algorithm.dst - if algorithm < 256: - return f"{zone}. 0 IN TYPE{rrtype} \\# 5 {secalg:02x}{keyid:04x}0000" + if dst_alg < 256: + return f"{zone}. 0 IN TYPE{rrtype} \\# 5 {wire_alg:02x}{keyid:04x}0000" - return f"{zone}. 0 IN TYPE{rrtype} \\# 7 {secalg:02x}{keyid:04x}0000{algorithm:04x}" + return f"{zone}. 0 IN TYPE{rrtype} \\# 7 {wire_alg:02x}{keyid:04x}0000{dst_alg:04x}" diff --git a/bin/tests/system/isctest/vars/algorithms.py b/bin/tests/system/isctest/vars/algorithms.py index 0ff211da95..a0081e0c47 100644 --- a/bin/tests/system/isctest/vars/algorithms.py +++ b/bin/tests/system/isctest/vars/algorithms.py @@ -111,6 +111,10 @@ ALL_ALGORITHMS = [ ] ALL_ALGORITHMS_BY_NUM = {alg.number: alg for alg in ALL_ALGORITHMS} +# Keyed by the DST identifier rather than the on-wire number: unlike `number` +# (where both private-OID variants collide at 254), `dst` is unique, so this +# map distinguishes RSASHA256OID (256) from RSASHA512OID (257). +ALL_ALGORITHMS_BY_DST = {alg.dst: alg for alg in ALL_ALGORITHMS} ALGORITHM_SETS = { "stable": AlgorithmSet( diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py index 32060acd3c..b1e1be907e 100644 --- a/bin/tests/system/ksr/tests_ksr.py +++ b/bin/tests/system/ksr/tests_ksr.py @@ -318,7 +318,7 @@ def check_rrsig_bundle(bundle_keys, bundle_lines, zone, rrtype, sigend, sigstart count = 0 for key in bundle_keys: found = False - alg = key.get_dnsalg() + alg = key.algorithm.number expect = f"{zone}. {ttl} IN RRSIG {rrtype} {alg} 2 {ttl} {sigend} {sigstart} {key.tag} {zone}." # there must be a signature of this ksk for line in bundle_lines: From ee73ba3bbaef6352dfeca8ebee53c78657e793e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 16:38:53 +0200 Subject: [PATCH 6/7] Add private_key support to all ZoneKey implementations Extend the ZoneKeyFile to read the file-backed private key and return it in a format suitable for use with dnspython. Add ZoneKey.private_key property to unify the interface. --- bin/tests/system/isctest/zone.py | 74 ++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/bin/tests/system/isctest/zone.py b/bin/tests/system/isctest/zone.py index a7ba22db90..8f5a886627 100644 --- a/bin/tests/system/isctest/zone.py +++ b/bin/tests/system/isctest/zone.py @@ -16,6 +16,7 @@ from collections.abc import Callable from pathlib import Path from typing import TypeAlias +import base64 import shutil from cryptography.hazmat.primitives import serialization @@ -32,7 +33,14 @@ import dns.zonefile from .log import debug from .run import EnvCmd from .template import NS1, Nameserver, TemplateEngine, TrustAnchor -from .vars.algorithms import Algorithm +from .vars.algorithms import ( + ALL_ALGORITHMS_BY_NUM, + ECDSAP256SHA256, + ECDSAP384SHA384, + ED448, + ED25519, + Algorithm, +) KEYDIR = "keys" DNSKEY_TTL = 3600 @@ -61,7 +69,16 @@ class ZoneKey(ABC): @property @abstractmethod def dnskey(self) -> dns.rrset.RRset: - """The DNSKEY RRset for this key (single-record, with TTL).""" + """ + The DNSKEY RRset for this key (single-record, with TTL). + """ + + @property + @abstractmethod + def private_key(self) -> PrivateKey: + """ + The dnspython private-key object, for use with dns.dnssec signing. + """ def is_ksk(self) -> bool: return bool(self.dnskey[0].flags & int(Flag.SEP)) @@ -142,6 +159,53 @@ class FileZoneKey(ZoneKey): ), f"DNSKEY not found in {self.keyfile}" return dnskey_rr + @property + def private_key(self) -> PrivateKey: + """ + Read the .private file and build the corresponding cryptography + private-key object, dispatching on the DNSKEY algorithm. + + Supports the RSA, ECDSA and EdDSA families; other algorithms raise + NotImplementedError. + """ + alg = ALL_ALGORITHMS_BY_NUM[self.dnskey[0].algorithm] + + fields = {} + with open(self.privatefile, "r", encoding="utf-8") as file: + for line in file: + name, sep, value = line.partition(":") + if sep: + fields[name.strip()] = value.strip() + + def field(name: str) -> bytes: + return base64.b64decode(fields[name]) + + if "Modulus" in fields: # RSA family, including the private-OID variants + public = rsa.RSAPublicNumbers( + int.from_bytes(field("PublicExponent"), "big"), + int.from_bytes(field("Modulus"), "big"), + ) + return rsa.RSAPrivateNumbers( + p=int.from_bytes(field("Prime1"), "big"), + q=int.from_bytes(field("Prime2"), "big"), + d=int.from_bytes(field("PrivateExponent"), "big"), + dmp1=int.from_bytes(field("Exponent1"), "big"), + dmq1=int.from_bytes(field("Exponent2"), "big"), + iqmp=int.from_bytes(field("Coefficient"), "big"), + public_numbers=public, + ).private_key() + + secret = field("PrivateKey") + if alg == ECDSAP256SHA256: + return ec.derive_private_key(int.from_bytes(secret, "big"), ec.SECP256R1()) + if alg == ECDSAP384SHA384: + return ec.derive_private_key(int.from_bytes(secret, "big"), ec.SECP384R1()) + if alg == ED25519: + return ed25519.Ed25519PrivateKey.from_private_bytes(secret) + if alg == ED448: + return ed448.Ed448PrivateKey.from_private_bytes(secret) + raise NotImplementedError(f"dnskey algorithm {alg.name} not implemented") + def write_dsset( self, target_dir: Path | str, @@ -215,10 +279,14 @@ class PythonZoneKey(ZoneKey): ttl: int = DNSKEY_TTL, ) -> None: self.zone = zone - self.private_key = private_key + self._private_key = private_key self._dnskey_rdata = dnskey_rdata self.ttl = ttl + @property + def private_key(self) -> PrivateKey: + return self._private_key + @property def dnskey(self) -> dns.rrset.RRset: rrset = dns.rrset.RRset( From 4d76b7bd9eb6c5b96f43b6f14919b1c762e15e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Jun 2026 16:23:23 +0000 Subject: [PATCH 7/7] Move algorithm definitions into a top-level isctest.algorithms module The Algorithm type, the per-algorithm constants, and the ALL_ALGORITHMS* lookup tables are general DNSSEC key definitions used across isctest package and the tests. Move them into a dedicated module to separate these from the environment-specific setup that remains in isctest.vars.algorithms. Assisted-by: Claude:claude-opus-4-8 --- bin/tests/system/conftest.py | 2 +- bin/tests/system/dnssec_py/tests_mixed_ds.py | 2 +- bin/tests/system/isctest/__init__.py | 2 + bin/tests/system/isctest/algorithms.py | 61 +++++++++++++++++++ bin/tests/system/isctest/kasp.py | 2 +- bin/tests/system/isctest/vars/algorithms.py | 56 +++-------------- bin/tests/system/isctest/zone.py | 8 +-- bin/tests/system/kasp/tests_kasp.py | 2 +- bin/tests/system/ksr/tests_ksr.py | 2 +- .../system/migrate2kasp/tests_migrate2kasp.py | 2 +- bin/tests/system/nsec3/tests_nsec3_change.py | 2 +- bin/tests/system/nsec3/tests_nsec3_initial.py | 2 +- .../system/nsec3/tests_nsec3_reconfig.py | 2 +- bin/tests/system/nsec3/tests_nsec3_restart.py | 2 +- .../system/nsec3/tests_nsec3_retransfer.py | 2 +- bin/tests/system/rollover/setup.py | 2 +- .../system/rollover/tests_rollover_manual.py | 2 +- 17 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 bin/tests/system/isctest/algorithms.py diff --git a/bin/tests/system/conftest.py b/bin/tests/system/conftest.py index b9a4943be9..e0c2a08d66 100644 --- a/bin/tests/system/conftest.py +++ b/bin/tests/system/conftest.py @@ -273,7 +273,7 @@ def control_port(): @pytest.fixture(scope="module") def default_algorithm(): - return isctest.vars.algorithms.Algorithm.default() + return isctest.algorithms.Algorithm.default() @pytest.fixture(scope="module") diff --git a/bin/tests/system/dnssec_py/tests_mixed_ds.py b/bin/tests/system/dnssec_py/tests_mixed_ds.py index a3c9d43845..57ca84457f 100644 --- a/bin/tests/system/dnssec_py/tests_mixed_ds.py +++ b/bin/tests/system/dnssec_py/tests_mixed_ds.py @@ -12,8 +12,8 @@ from re import compile as Re from dnssec_py.common import DNSSEC_PY_MARK +from isctest.algorithms import Algorithm from isctest.template import NS2, NS3, zones -from isctest.vars.algorithms import Algorithm from isctest.zone import Zone, configure_root import isctest diff --git a/bin/tests/system/isctest/__init__.py b/bin/tests/system/isctest/__init__.py index 30256087b3..326b77f0d4 100644 --- a/bin/tests/system/isctest/__init__.py +++ b/bin/tests/system/isctest/__init__.py @@ -10,6 +10,7 @@ # information regarding copyright ownership. from . import ( # pylint: disable=redefined-builtin + algorithms, check, hypothesis, instance, @@ -29,6 +30,7 @@ from . import ( # pylint: disable=redefined-builtin # instead. __all__ = [ + "algorithms", "check", "hypothesis", "instance", diff --git a/bin/tests/system/isctest/algorithms.py b/bin/tests/system/isctest/algorithms.py new file mode 100644 index 0000000000..160282f39a --- /dev/null +++ b/bin/tests/system/isctest/algorithms.py @@ -0,0 +1,61 @@ +# 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. + +from typing import NamedTuple + +import os + + +class Algorithm(NamedTuple): + name: str + number: int + dst: int + bits: int + + @classmethod + def default(cls): + return cls( + os.environ["DEFAULT_ALGORITHM"], + int(os.environ["DEFAULT_ALGORITHM_NUMBER"]), + int(os.environ["DEFAULT_ALGORITHM_DST_NUMBER"]), + int(os.environ["DEFAULT_BITS"]), + ) + + +RSASHA1 = Algorithm("RSASHA1", 5, 5, 2048) +NSEC3RSASHA1 = Algorithm("NSEC3RSASHA1", 7, 7, 2048) +RSASHA256 = Algorithm("RSASHA256", 8, 8, 2048) +RSASHA512 = Algorithm("RSASHA512", 10, 10, 2048) +ECDSAP256SHA256 = Algorithm("ECDSAP256SHA256", 13, 13, 256) +ECDSAP384SHA384 = Algorithm("ECDSAP384SHA384", 14, 14, 384) +ED25519 = Algorithm("ED25519", 15, 15, 256) +ED448 = Algorithm("ED448", 16, 16, 456) +RSASHA256OID = Algorithm("RSASHA256OID", 254, 256, 2048) +RSASHA512OID = Algorithm("RSASHA512OID", 254, 257, 2048) + +ALL_ALGORITHMS = [ + RSASHA1, + NSEC3RSASHA1, + RSASHA256, + RSASHA512, + ECDSAP256SHA256, + ECDSAP384SHA384, + ED25519, + ED448, + RSASHA256OID, + RSASHA512OID, +] + +ALL_ALGORITHMS_BY_NUM = {alg.number: alg for alg in ALL_ALGORITHMS} +# Keyed by the DST identifier rather than the on-wire number: unlike `number` +# (where both private-OID variants collide at 254), `dst` is unique, so this +# map distinguishes RSASHA256OID (256) from RSASHA512OID (257). +ALL_ALGORITHMS_BY_DST = {alg.dst: alg for alg in ALL_ALGORITHMS} diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 2c82cce37b..0bc7bd9b4b 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -29,9 +29,9 @@ import dns.rdatatype import dns.tsig import dns.zone +from isctest.algorithms import ALL_ALGORITHMS_BY_DST, ECDSAP256SHA256, Algorithm from isctest.instance import NamedInstance from isctest.run import EnvCmd -from isctest.vars.algorithms import ALL_ALGORITHMS_BY_DST, ECDSAP256SHA256, Algorithm from isctest.zone import FileZoneKey import isctest.log diff --git a/bin/tests/system/isctest/vars/algorithms.py b/bin/tests/system/isctest/vars/algorithms.py index a0081e0c47..61c8097f71 100644 --- a/bin/tests/system/isctest/vars/algorithms.py +++ b/bin/tests/system/isctest/vars/algorithms.py @@ -19,6 +19,16 @@ import tempfile import time from .. import log +from ..algorithms import ( + ALL_ALGORITHMS, + ECDSAP256SHA256, + ECDSAP384SHA384, + ED448, + ED25519, + RSASHA256, + RSASHA512, + Algorithm, +) from .basic import BASIC_VARS # Algorithms are selected randomly at runtime from a list of supported @@ -55,22 +65,6 @@ STABLE_PERIOD = 3600 * 3 """number of secs during which algorithm selection remains stable""" -class Algorithm(NamedTuple): - name: str - number: int - dst: int - bits: int - - @classmethod - def default(cls): - return cls( - os.environ["DEFAULT_ALGORITHM"], - int(os.environ["DEFAULT_ALGORITHM_NUMBER"]), - int(os.environ["DEFAULT_ALGORITHM_DST_NUMBER"]), - int(os.environ["DEFAULT_BITS"]), - ) - - class AlgorithmSet(NamedTuple): """Collection of DEFAULT, ALTERNATIVE and DISABLED algorithms""" @@ -86,36 +80,6 @@ class AlgorithmSet(NamedTuple): "disable-algorithms" configuration option.""" -RSASHA1 = Algorithm("RSASHA1", 5, 5, 2048) -NSEC3RSASHA1 = Algorithm("NSEC3RSASHA1", 7, 7, 2048) -RSASHA256 = Algorithm("RSASHA256", 8, 8, 2048) -RSASHA512 = Algorithm("RSASHA512", 10, 10, 2048) -ECDSAP256SHA256 = Algorithm("ECDSAP256SHA256", 13, 13, 256) -ECDSAP384SHA384 = Algorithm("ECDSAP384SHA384", 14, 14, 384) -ED25519 = Algorithm("ED25519", 15, 15, 256) -ED448 = Algorithm("ED448", 16, 16, 456) -RSASHA256OID = Algorithm("RSASHA256OID", 254, 256, 2048) -RSASHA512OID = Algorithm("RSASHA512OID", 254, 257, 2048) - -ALL_ALGORITHMS = [ - RSASHA1, - NSEC3RSASHA1, - RSASHA256, - RSASHA512, - ECDSAP256SHA256, - ECDSAP384SHA384, - ED25519, - ED448, - RSASHA256OID, - RSASHA512OID, -] - -ALL_ALGORITHMS_BY_NUM = {alg.number: alg for alg in ALL_ALGORITHMS} -# Keyed by the DST identifier rather than the on-wire number: unlike `number` -# (where both private-OID variants collide at 254), `dst` is unique, so this -# map distinguishes RSASHA256OID (256) from RSASHA512OID (257). -ALL_ALGORITHMS_BY_DST = {alg.dst: alg for alg in ALL_ALGORITHMS} - ALGORITHM_SETS = { "stable": AlgorithmSet( default=ECDSAP256SHA256, alternative=RSASHA256, disabled=ECDSAP384SHA384 diff --git a/bin/tests/system/isctest/zone.py b/bin/tests/system/isctest/zone.py index 8f5a886627..359d0f2707 100644 --- a/bin/tests/system/isctest/zone.py +++ b/bin/tests/system/isctest/zone.py @@ -30,10 +30,7 @@ import dns.rdatatype import dns.rrset import dns.zonefile -from .log import debug -from .run import EnvCmd -from .template import NS1, Nameserver, TemplateEngine, TrustAnchor -from .vars.algorithms import ( +from .algorithms import ( ALL_ALGORITHMS_BY_NUM, ECDSAP256SHA256, ECDSAP384SHA384, @@ -41,6 +38,9 @@ from .vars.algorithms import ( ED25519, Algorithm, ) +from .log import debug +from .run import EnvCmd +from .template import NS1, Nameserver, TemplateEngine, TrustAnchor KEYDIR = "keys" DNSKEY_TTL = 3600 diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 5f970a66be..c57807e940 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -25,9 +25,9 @@ import dns.tsig import dns.update import pytest +from isctest.algorithms import ECDSAP256SHA256, ECDSAP384SHA384, Algorithm from isctest.kasp import KeyProperties, KeyTimingMetadata, SettimeOptions from isctest.util import param -from isctest.vars.algorithms import ECDSAP256SHA256, ECDSAP384SHA384, Algorithm import isctest import isctest.mark diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py index b1e1be907e..5f54a3361f 100644 --- a/bin/tests/system/ksr/tests_ksr.py +++ b/bin/tests/system/ksr/tests_ksr.py @@ -18,8 +18,8 @@ import time import pytest +from isctest.algorithms import Algorithm from isctest.kasp import KeyTimingMetadata -from isctest.vars.algorithms import Algorithm from rollover.common import TIMEDELTA import isctest diff --git a/bin/tests/system/migrate2kasp/tests_migrate2kasp.py b/bin/tests/system/migrate2kasp/tests_migrate2kasp.py index 3e4150ee7f..750eee509b 100644 --- a/bin/tests/system/migrate2kasp/tests_migrate2kasp.py +++ b/bin/tests/system/migrate2kasp/tests_migrate2kasp.py @@ -15,7 +15,7 @@ import os import pytest -from isctest.vars.algorithms import Algorithm +from isctest.algorithms import Algorithm import isctest diff --git a/bin/tests/system/nsec3/tests_nsec3_change.py b/bin/tests/system/nsec3/tests_nsec3_change.py index b8a8b066e3..564a670d63 100644 --- a/bin/tests/system/nsec3/tests_nsec3_change.py +++ b/bin/tests/system/nsec3/tests_nsec3_change.py @@ -17,7 +17,7 @@ import dns.rdataclass import dns.rdatatype import pytest -from isctest.vars.algorithms import Algorithm +from isctest.algorithms import Algorithm from nsec3.common import NSEC3_MARK, check_nsec3_case import isctest diff --git a/bin/tests/system/nsec3/tests_nsec3_initial.py b/bin/tests/system/nsec3/tests_nsec3_initial.py index d855032bd1..8fb23ee2a2 100644 --- a/bin/tests/system/nsec3/tests_nsec3_initial.py +++ b/bin/tests/system/nsec3/tests_nsec3_initial.py @@ -17,7 +17,7 @@ import dns.rcode import dns.update import pytest -from isctest.vars.algorithms import RSASHA1, Algorithm +from isctest.algorithms import RSASHA1, Algorithm from nsec3.common import NSEC3_MARK, check_nsec3_case import isctest diff --git a/bin/tests/system/nsec3/tests_nsec3_reconfig.py b/bin/tests/system/nsec3/tests_nsec3_reconfig.py index 1a4ba812f0..ea3ee8e843 100644 --- a/bin/tests/system/nsec3/tests_nsec3_reconfig.py +++ b/bin/tests/system/nsec3/tests_nsec3_reconfig.py @@ -18,7 +18,7 @@ import dns.rdataclass import dns.rdatatype import pytest -from isctest.vars.algorithms import RSASHA1, Algorithm +from isctest.algorithms import RSASHA1, Algorithm from nsec3.common import NSEC3_MARK, check_nsec3_case import isctest diff --git a/bin/tests/system/nsec3/tests_nsec3_restart.py b/bin/tests/system/nsec3/tests_nsec3_restart.py index ca6ebef811..aed7c41781 100644 --- a/bin/tests/system/nsec3/tests_nsec3_restart.py +++ b/bin/tests/system/nsec3/tests_nsec3_restart.py @@ -14,7 +14,7 @@ import os import dns.rdatatype import pytest -from isctest.vars.algorithms import Algorithm +from isctest.algorithms import Algorithm from nsec3.common import NSEC3_MARK, check_nsec3_case, check_nsec3param import isctest diff --git a/bin/tests/system/nsec3/tests_nsec3_retransfer.py b/bin/tests/system/nsec3/tests_nsec3_retransfer.py index 4df768cc00..bb019d395f 100644 --- a/bin/tests/system/nsec3/tests_nsec3_retransfer.py +++ b/bin/tests/system/nsec3/tests_nsec3_retransfer.py @@ -16,7 +16,7 @@ import os import dns.rcode import dns.rdatatype -from isctest.vars.algorithms import RSASHA256 +from isctest.algorithms import RSASHA256 from nsec3.common import NSEC3_MARK, check_auth_nsec3, check_nsec3param import isctest diff --git a/bin/tests/system/rollover/setup.py b/bin/tests/system/rollover/setup.py index dc83402069..97bed7e1e8 100644 --- a/bin/tests/system/rollover/setup.py +++ b/bin/tests/system/rollover/setup.py @@ -13,10 +13,10 @@ from pathlib import Path import shutil +from isctest.algorithms import Algorithm from isctest.kasp import SettimeOptions, private_type_record from isctest.run import EnvCmd from isctest.template import NS2, NS3, TrustAnchor, Zone -from isctest.vars.algorithms import Algorithm import isctest diff --git a/bin/tests/system/rollover/tests_rollover_manual.py b/bin/tests/system/rollover/tests_rollover_manual.py index 71907a0be5..d7e326db9c 100644 --- a/bin/tests/system/rollover/tests_rollover_manual.py +++ b/bin/tests/system/rollover/tests_rollover_manual.py @@ -11,6 +11,7 @@ from datetime import timedelta +from isctest.algorithms import Algorithm from isctest.kasp import ( Ipub, Iret, @@ -20,7 +21,6 @@ from isctest.kasp import ( ) from isctest.run import EnvCmd from isctest.template import NS3, Zone -from isctest.vars.algorithms import Algorithm from rollover.setup import configure_root, configure_tld, setkeytimes import isctest