Commit 24cf5ffbef for bind

commit 24cf5ffbef82b604fc99682afa47efa154d931a6
Author: Nicki Křížek <nicki@isc.org>
Date:   Fri Oct 2 10:03:42 2026 +0000

    Replace HSM keys that dnssec-keyfromlabel refuses

    dnssec-keyfromlabel refuses to import a key from the HSM if a key file
    with the same key tag already exists for the zone, whatever its role.
    Unlike dnssec-keygen, pkcs11-tool does not avoid that, so the KSK of a
    zone may share the key tag of its ZSK (or in enginepkcs11, of any other
    key of the zone), and the import fails. Generate another key in that
    case, as dnssec-keygen would.

    Assisted-by: Claude:claude-opus-5-5

diff --git a/bin/tests/system/enginepkcs11/setup.sh b/bin/tests/system/enginepkcs11/setup.sh
index 459a6ea623..f10df0c118 100644
--- a/bin/tests/system/enginepkcs11/setup.sh
+++ b/bin/tests/system/enginepkcs11/setup.sh
@@ -44,6 +44,27 @@ keyfromlabel() {
   cat keyfromlabel.out.$zone.$id
 }

+# Unlike dnssec-keygen, pkcs11-tool does not avoid key tag collisions, so
+# dnssec-keyfromlabel may refuse a key. Replace it then.
+hsmkey() {
+  # keygen() and keyfromlabel() overwrite their (global) variables, so
+  # keep ours apart.
+  hsm_type="$1"
+  hsm_bits="$2"
+  hsm_alg="$3"
+  hsm_zone="$4"
+  hsm_id="$5"
+  hsm_dir="$6"
+  shift 6
+
+  for hsm_try in 0 1 2; do
+    keygen $hsm_type $hsm_bits $hsm_zone $hsm_id-$hsm_try || return 1
+    keyfromlabel $hsm_alg $hsm_zone $hsm_id-$hsm_try $hsm_dir "$@" && return 0
+    grep "already exists" keyfromlabel.err.$hsm_zone.$hsm_id-$hsm_try >/dev/null || return 1
+  done
+  return 1
+}
+
 mkdir ns1/keys

 dir="ns1"
@@ -64,16 +85,13 @@ for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \
     ret=0

     echo_i "Generate keys $alg $type:$bits for zone $zone"
-    keygen $type $bits $zone enginepkcs11-zsk || ret=1
-    keygen $type $bits $zone enginepkcs11-ksk || ret=1
-    test "$ret" -eq 0 || exit 1

     echo_i "Get ZSK $alg $zone $type:$bits"
-    zsk1=$(keyfromlabel $alg $zone enginepkcs11-zsk $dir)
+    zsk1=$(hsmkey $type $bits $alg $zone enginepkcs11-zsk $dir)
     test -z "$zsk1" && exit 1

     echo_i "Get KSK $alg $zone $type:$bits"
-    ksk1=$(keyfromlabel $alg $zone enginepkcs11-ksk $dir -f KSK)
+    ksk1=$(hsmkey $type $bits $alg $zone enginepkcs11-ksk $dir -f KSK)
     test -z "$ksk1" && exit 1

     (
@@ -90,16 +108,13 @@ for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \
     test "$ret" -eq 0 || exit 1

     echo_i "Generate successor keys $alg $type:$bits for zone $zone"
-    keygen $type $bits $zone enginepkcs11-zsk2 || ret=1
-    keygen $type $bits $zone enginepkcs11-ksk2 || ret=1
-    test "$ret" -eq 0 || exit 1

-    echo_i "Get ZSK $alg $id-$zone $type:$bits"
-    zsk2=$(keyfromlabel $alg $zone enginepkcs11-zsk2 $dir)
+    echo_i "Get successor ZSK $alg $zone $type:$bits"
+    zsk2=$(hsmkey $type $bits $alg $zone enginepkcs11-zsk2 $dir)
     test -z "$zsk2" && exit 1

-    echo_i "Get KSK $alg $id-$zone $type:$bits"
-    ksk2=$(keyfromlabel $alg $zone enginepkcs11-ksk2 $dir -f KSK)
+    echo_i "Get successor KSK $alg $zone $type:$bits"
+    ksk2=$(hsmkey $type $bits $alg $zone enginepkcs11-ksk2 $dir -f KSK)
     test -z "$ksk2" && exit 1

     (
@@ -195,16 +210,13 @@ if [ "${supported}" = 1 ]; then
   ret=0

   echo_i "Generate keys $alg $type:$bits for zone $zone"
-  keygen $type $bits $zone enginepkcs11-zsk || ret=1
-  keygen $type $bits $zone enginepkcs11-ksk || ret=1
-  test "$ret" -eq 0 || exit 1

   echo_i "Get ZSK $alg $zone $type:$bits"
-  zsk1=$(keyfromlabel $alg $zone enginepkcs11-zsk $dir)
+  zsk1=$(hsmkey $type $bits $alg $zone enginepkcs11-zsk $dir)
   test -z "$zsk1" && exit 1

   echo_i "Get KSK $alg $zone $type:$bits"
-  ksk1=$(keyfromlabel $alg $zone enginepkcs11-ksk $dir -f KSK)
+  ksk1=$(hsmkey $type $bits $alg $zone enginepkcs11-ksk $dir -f KSK)
   test -z "$ksk1" && exit 1

   (
@@ -225,16 +237,13 @@ if [ "${supported}" = 1 ]; then
   test "$ret" -eq 0 || exit 1

   echo_i "Generate successor keys $alg $type:$bits for zone $zone"
-  keygen $type $bits $zone enginepkcs11-zsk2 || ret=1
-  keygen $type $bits $zone enginepkcs11-ksk2 || ret=1
-  test "$ret" -eq 0 || exit 1

-  echo_i "Get ZSK $alg $id-$zone $type:$bits"
-  zsk2=$(keyfromlabel $alg $zone enginepkcs11-zsk2 $dir)
+  echo_i "Get successor ZSK $alg $zone $type:$bits"
+  zsk2=$(hsmkey $type $bits $alg $zone enginepkcs11-zsk2 $dir)
   test -z "$zsk2" && exit 1

-  echo_i "Get KSK $alg $id-$zone $type:$bits"
-  ksk2=$(keyfromlabel $alg $zone enginepkcs11-ksk2 $dir -f KSK)
+  echo_i "Get successor KSK $alg $zone $type:$bits"
+  ksk2=$(hsmkey $type $bits $alg $zone enginepkcs11-ksk2 $dir -f KSK)
   test -z "$ksk2" && exit 1

   (
diff --git a/bin/tests/system/keyfromlabel/tests_keyfromlabel.py b/bin/tests/system/keyfromlabel/tests_keyfromlabel.py
index efd152d3c5..3e2ee37dfd 100644
--- a/bin/tests/system/keyfromlabel/tests_keyfromlabel.py
+++ b/bin/tests/system/keyfromlabel/tests_keyfromlabel.py
@@ -117,8 +117,7 @@ def token_init_and_cleanup():
 )
 def test_keyfromlabel(alg_name, alg_type, alg_bits):

-    def keygen(alg_type, alg_bits, zone, key_id):
-        label = f"{key_id}-{zone}"
+    def keygen(alg_type, alg_bits, label):
         p11_id = hashlib.sha1(label.encode("utf-8")).hexdigest()

         pkcs11_command = [
@@ -143,7 +142,7 @@ def test_keyfromlabel(alg_name, alg_type, alg_bits):

         assert "Key pair generated" in cmd.out

-    def keyfromlabel(alg_name, zone, key_id, key_flag):
+    def keyfromlabel(alg_name, zone, label, key_flag):
         key_flag = key_flag.split() if key_flag else []

         keyfrlab_command = [
@@ -152,32 +151,45 @@ def test_keyfromlabel(alg_name, alg_type, alg_bits):
             alg_name,
             "-y",
             "-l",
-            f"pkcs11:token=softhsm2-keyfromlabel;object={key_id}-{zone};pin-source=pin",
+            f"pkcs11:token=softhsm2-keyfromlabel;object={label};pin-source=pin",
             *key_flag,
             zone,
         ]

-        cmd = isctest.run.cmd(keyfrlab_command)
+        cmd = isctest.run.cmd(keyfrlab_command, raise_on_exception=False)
+        if cmd.rc != 0 and "already exists" in cmd.err:
+            return None
+        assert cmd.rc == 0
         keyfile = cmd.out.rstrip() + ".key"

         assert os.path.exists(keyfile)

         return keyfile

+    def hsm_key(alg_type, alg_bits, alg_name, zone, key_id, key_flag):
+        # Unlike dnssec-keygen, pkcs11-tool does not avoid key tag collisions,
+        # so dnssec-keyfromlabel may refuse a key. Replace it then.
+        keyfile = None
+        for attempt in range(3):
+            label = f"{key_id}-{attempt}-{zone}"
+            keygen(alg_type, alg_bits, label)
+            keyfile = keyfromlabel(alg_name, zone, label, key_flag)
+            if keyfile is not None:
+                break
+        assert keyfile is not None, f"{key_id}: key tags keep colliding"
+        return keyfile
+
     if f"{alg_name.upper()}_SUPPORTED" not in os.environ:
         pytest.skip(f"{alg_name} is not supported")

     # Generate keys for the $zone zone
     zone = f"{alg_name}.example"

-    keygen(alg_type, alg_bits, zone, "keyfromlabel-zsk")
-    keygen(alg_type, alg_bits, zone, "keyfromlabel-ksk")
-
     # Get ZSK
-    zsk_file = keyfromlabel(alg_name, zone, "keyfromlabel-zsk", "")
+    zsk_file = hsm_key(alg_type, alg_bits, alg_name, zone, "keyfromlabel-zsk", "")

     # Get KSK
-    ksk_file = keyfromlabel(alg_name, zone, "keyfromlabel-ksk", "-f KSK")
+    ksk_file = hsm_key(alg_type, alg_bits, alg_name, zone, "keyfromlabel-ksk", "-f KSK")

     # Sign zone with KSK and ZSK
     zone_file = f"zone.{alg_name}.example.db"