]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Merge kasp.Key functionality into zone.FileZoneKey
authorNicki Křížek <nicki@isc.org>
Tue, 16 Jun 2026 12:34:48 +0000 (12:34 +0000)
committerNicki Křížek <nicki@isc.org>
Thu, 2 Jul 2026 12:51:41 +0000 (14:51 +0200)
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
bin/tests/system/isctest/zone.py

index b919753dd7ec67bcc5525c1d569134634f44694b..e86b9d6a813105569db9e985b385e08f9f0d03fb 100644 (file)
@@ -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:
index df6593254143e4594fd466d1306c501b27fe623a..02b93c8b8d995091967005a1de3b7efbb422b758 100644 (file)
@@ -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):