Test pkcs11 URI support for UEFI capsule generation. Both
public certificate and private key are used over pkcs11
protocol.
Pkcs11-tool has been introduced as softhsm tool doesn't have
functionality to import certificates in commonly distributed
version (only in the latest).

Signed-off-by: Wojciech Dubowik <[email protected]>
Reviewed-by: Simon Glass <[email protected]>
---
 tools/binman/btool/p11_kit.py                 | 21 ++++++
 tools/binman/btool/pkcs11_tool.py             | 21 ++++++
 tools/binman/ftest.py                         | 73 +++++++++++++++++++
 .../binman/test/351_capsule_signed_pkcs11.dts | 22 ++++++
 4 files changed, 137 insertions(+)
 create mode 100644 tools/binman/btool/p11_kit.py
 create mode 100644 tools/binman/btool/pkcs11_tool.py
 create mode 100644 tools/binman/test/351_capsule_signed_pkcs11.dts

diff --git a/tools/binman/btool/p11_kit.py b/tools/binman/btool/p11_kit.py
new file mode 100644
index 000000000000..9d8d5d848b44
--- /dev/null
+++ b/tools/binman/btool/p11_kit.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright 2026 Mettler Toledo Technologies GmbH
+#
+"""Bintool implementation for p11-kit"""
+
+from binman import bintool
+
+
+class Bintoolp11_kit(bintool.Bintool):
+    """p11-kit -- support tool for pkcs#11 libraries"""
+    def __init__(self, name):
+        super().__init__('p11-kit',
+                         'Pkcs11 library modules tool',
+                         version_args='list modules')
+
+    def fetch(self, method):
+        """Install p11-kit via APT """
+        if method != bintool.FETCH_BIN:
+            return None
+
+        return self.apt_install('p11-kit')
diff --git a/tools/binman/btool/pkcs11_tool.py 
b/tools/binman/btool/pkcs11_tool.py
new file mode 100644
index 000000000000..673c0ea0ac35
--- /dev/null
+++ b/tools/binman/btool/pkcs11_tool.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright 2026 Mettler Toledo Technologies GmbH
+#
+"""Bintool implementation for pkcs11-tool"""
+
+from binman import bintool
+
+
+class Bintoolpkcs11_tool(bintool.Bintool):
+    """pkcs11-tool -- support tool for managing pkcs#11 tokens"""
+    def __init__(self, name):
+        super().__init__('pkcs11-tool',
+                         'PKCS #11 tokens managing tool',
+                         version_args='--show-info')
+
+    def fetch(self, method):
+        """Install opensc via APT """
+        if method != bintool.FETCH_BIN:
+            return None
+
+        return self.apt_install('opensc')
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index a53e37f31b3e..d47364162188 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -7,6 +7,7 @@
 #    python -m unittest func_test.TestFunctional.testHelp
 
 import collections
+import configparser
 import glob
 import gzip
 import hashlib
@@ -7532,6 +7533,78 @@ fdt         fdtmap                Extract the devicetree 
blob from the fdtmap
 
         self._CheckCapsule(data, signed_capsule=True)
 
+    def testPkcs11SignedCapsuleGen(self):
+        """Test generation of EFI capsule (with PKCS11)"""
+        data = tools.read_file(self.TestFile("key.key"))
+        private_key = self._MakeInputFile("key.key", data)
+        data = tools.read_file(self.TestFile("key.pem"))
+        cert_file = self._MakeInputFile("key.crt", data)
+
+        softhsm2_util = bintool.Bintool.create('softhsm2_util')
+        self._CheckBintool(softhsm2_util)
+
+        pkcs11_tool = bintool.Bintool.create('pkcs11-tool')
+        self._CheckBintool(pkcs11_tool)
+
+        prefix = "testPkcs11SignedCapsuleGen."
+        # Configure SoftHSMv2
+        data = tools.read_file(self.TestFile('340_softhsm2.conf'))
+        softhsm2_conf = self._MakeInputFile(f'{prefix}softhsm2.conf', data)
+        softhsm2_tokens_dir = self._MakeInputDir(f'{prefix}softhsm2.tokens')
+
+        with open(softhsm2_conf, 'a') as f:
+            f.write(f'directories.tokendir = {softhsm2_tokens_dir}\n')
+
+        # Find the path to softhsm2 library
+        p11_kit = bintool.Bintool.create('p11-kit')
+        self._CheckBintool(p11_kit)
+
+        p11_kit_config = configparser.ConfigParser()
+        out = tools.run('p11-kit', 'print-config')
+        p11_kit_config.read_string(out)
+        softhsm2_lib = p11_kit_config.get('softhsm2', 'module',
+                                           fallback=None)
+        self.assertIsNotNone(softhsm2_lib)
+
+        with unittest.mock.patch.dict('os.environ',
+                                      {'SOFTHSM2_CONF': softhsm2_conf,
+                                       'PKCS11_MODULE_PATH': softhsm2_lib}):
+                tools.run('softhsm2-util', '--init-token', '--free', '--label',
+                          'U-Boot token', '--pin', '1111', '--so-pin',
+                          '222222')
+                tools.run('pkcs11-tool', '--module', softhsm2_lib,
+                          '--write-object', cert_file, '--pin', '1111',
+                          '--type', 'cert', '--id', '999999', '--label',
+                          'test_cert', '--login')
+                tools.run('softhsm2-util', '--import', private_key, '--token',
+                          'U-Boot token', '--label', 'test_key', '--id', 
'999999',
+                          '--pin', '1111')
+                data = self._DoReadFile('351_capsule_signed_pkcs11.dts')
+
+        self._CheckCapsule(data, signed_capsule=True)
+
+        hdr = self._GetCapsuleHeaders(data)
+        monotonic_count = hdr['EFI_FIRMWARE_IMAGE_AUTH.MONOTONIC_COUNT']
+
+        # UEFI standard requires that signature is checked over payload 
followed
+        # by a monotonic count as little endian 64-bit integer.
+        sig_input = self._MakeInputFile("sig_input", EFI_CAPSULE_DATA)
+        with open(sig_input, 'ab') as f:
+            f.write(struct.pack('<Q', int(monotonic_count, 16)))
+
+        # Verify dumped capsule signature dumped by meficapsule during
+        # generation
+        openssl = bintool.Bintool.create('openssl')
+        self._CheckBintool(openssl)
+        openssl_args = ['smime', '-verify', '-inform', 'DER',
+                        '-in', 
tools.get_output_filename('capsule.efi-capsule.p7'),
+                        '-content', sig_input, '-CAfile', cert_file,
+                        '-no_check_time',
+                        '-out', 
tools.get_output_filename('decoded-capsule.bin')]
+        result = openssl.run_cmd_result(*openssl_args)
+        self.assertIsNotNone(result.stdout)
+        self.assertIn('Verification successful', result.stderr)
+
     def testCapsuleGenVersionSupport(self):
         """Test generation of EFI capsule with version support"""
         data = self._DoReadFile('313_capsule_version.dts')
diff --git a/tools/binman/test/351_capsule_signed_pkcs11.dts 
b/tools/binman/test/351_capsule_signed_pkcs11.dts
new file mode 100644
index 000000000000..bb87e18a15fe
--- /dev/null
+++ b/tools/binman/test/351_capsule_signed_pkcs11.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+       binman {
+               efi-capsule {
+                       image-index = <0x1>;
+                       /* Image GUID for testing capsule update */
+                       image-guid = "binman-test";
+                       hardware-instance = <0x0>;
+                       monotonic-count = <0x1>;
+                       dump-signature;
+                       private-key = 
"pkcs11:token=U-Boot%20token;object=test_key;type=private;pin-value=1111";
+                       public-key-cert = 
"pkcs11:token=U-Boot%20token;object=test_cert;type=cert;pin-value=1111";
+
+                       blob {
+                               filename = "capsule_input.bin";
+                       };
+               };
+       };
+};
-- 
2.47.3

Reply via email to