Extend the pre-load entry type to support signing via hardware
security modules (HSM) using PKCS#11. The implementation integrates
with the pkcs11_tool btool to perform signing operations.

Signed-off-by: Yan WANG <[email protected]>
---
 tools/binman/etype/pre_load.py | 285 +++++++++++++++++++++++++--------
 1 file changed, 214 insertions(+), 71 deletions(-)

diff --git a/tools/binman/etype/pre_load.py b/tools/binman/etype/pre_load.py
index 0d953cb258e..26e2c5eb3be 100644
--- a/tools/binman/etype/pre_load.py
+++ b/tools/binman/etype/pre_load.py
@@ -2,7 +2,7 @@
 # Copyright (c) 2022 Softathome
 # Written by Philippe Reynes <[email protected]>
 #
-# Entry-type for the global header
+# Entry-type for the global header with file-based and HSM signing support
 #
 
 import os
@@ -22,9 +22,9 @@ from Cryptodome.Signature import pss
 PRE_LOAD_MAGIC = b'UBSH'
 
 RSAS = {
-    'rsa1024': 1024 / 8,
-    'rsa2048': 2048 / 8,
-    'rsa4096': 4096 / 8
+    'rsa1024': 1024 // 8,
+    'rsa2048': 2048 // 8,
+    'rsa4096': 4096 // 8
 }
 
 SHAS = {
@@ -33,8 +33,9 @@ SHAS = {
     'sha512': SHA512
 }
 
+
 class Entry_pre_load(Entry_collection):
-    """Pre load image header
+    """Pre load image header with file-based or HSM signing support
 
     Properties / Entry arguments:
         - pre-load-key-path: Path of the directory that store key (provided by
@@ -46,29 +47,57 @@ class Entry_pre_load(Entry_collection):
         - header-size: Total size of the header
         - version: Version of the header
 
-    This entry creates a pre-load header that contains a global
-    image signature.
+    HSM Signing Properties (optional):
+        - hsm-support: Enable HSM signing (boolean)
+        - hsm-library: Path to PKCS#11 library (.so file)
+        - hsm-token-label: HSM token label
+        - hsm-token-serial: HSM token serial number (alternative to 
token-label)
+        - hsm-key-label: HSM private key label
+        - hsm-key-id: HSM private key ID (alternative to key-label)
+        - hsm-pin: HSM user PIN
+
+    This entry creates a pre-load header that contains a global image 
signature.
+    The signature can be created using either a traditional RSA key file or a
+    Hardware Security Module (HSM) via PKCS#11.
 
-    For example, this creates an image with a pre-load header and a binary::
+    Example with file-based signing::
 
         binman {
-            image2 {
-                filename = "sandbox.bin";
-
-                pre-load {
-                    content = <&image>;
-                    algo-name = "sha256,rsa2048";
-                    padding-name = "pss";
-                    key-name = "private.pem";
-                    header-size = <4096>;
-                    version = <1>;
-                };
-
-                image: blob-ext {
-                    filename = "sandbox.itb";
-                };
+            pre-load {
+                content = <&image>;
+                algo-name = "sha256,rsa2048";
+                padding-name = "pss";
+                key-name = "private.pem";
+                header-size = <4096>;
+                version = <1>;
+            };
+
+            image: blob-ext {
+                filename = "sandbox.itb";
+            };
+        };
+
+    Example with HSM signing (SoftHSM)::
+
+        binman {
+            pre-load {
+                content = <&image>;
+                algo-name = "sha256,rsa2048";
+                padding-name = "pss";
+                header-size = <4096>;
+                version = <1>;
+                hsm-support;
+                hsm-library = "/usr/lib/softhsm/libsofthsm2.so";
+                hsm-token-label = "uboot-hsm";
+                hsm-key-label = "uboot-key";
+                hsm-pin = "1234";
+            };
+
+            image: blob-ext {
+                filename = "sandbox.itb";
             };
         };
+
     """
 
     def __init__(self, section, etype, node):
@@ -79,6 +108,19 @@ class Entry_pre_load(Entry_collection):
         self.header_size = fdt_util.GetInt(self._node, 'header-size')
         self.version = fdt_util.GetInt(self._node, 'version')
 
+        # HSM properties (optional)
+        self.hsm_support = fdt_util.GetBool(self._node, 'hsm-support', False)
+        self.hsm_library = fdt_util.GetString(self._node, 'hsm-library')
+        self.hsm_token_label = fdt_util.GetString(self._node, 
'hsm-token-label')
+        self.hsm_token_serial = fdt_util.GetString(self._node, 
'hsm-token-serial')
+        self.hsm_slot_index = fdt_util.GetString(self._node, 'hsm-slot-index')
+        self.hsm_key_label = fdt_util.GetString(self._node, 'hsm-key-label')
+        self.hsm_key_id = fdt_util.GetString(self._node, 'hsm-key-id')
+        self.hsm_pin = fdt_util.GetString(self._node, 'hsm-pin')
+
+        # Will be set in AddBintools if HSM is used
+        self.pkcs11_tool = None
+
     def ReadNode(self):
         super().ReadNode()
         self.key_path, = self.GetEntryArgsOrProps(
@@ -86,68 +128,169 @@ class Entry_pre_load(Entry_collection):
         if self.key_path is None:
             self.key_path = ''
 
-    def _CreateHeader(self):
-        """Create a pre load header"""
+    def AddBintools(self, btools):
+        """Add required btools
+
+        Register pkcs11-tool btool if HSM signing is enabled.
+        """
+        super().AddBintools(btools)
+        if self.hsm_support:
+            self.pkcs11_tool = self.AddBintool(btools, 'pkcs11_tool')
+
+    def _SignData(self, data):
+        """Sign data using either file-based key or HSM
+
+        This method provides a unified interface for signing data regardless
+        of whether a traditional RSA key file or an HSM is used. The signing
+        backend is selected based on the hsm-support property.
+
+        Args:
+            data (bytes): Data to sign
+
+        Returns:
+            bytes: Signature
+
+        Raises:
+            ValueError: If configuration is invalid
+            RuntimeError: If signing operation fails
+        """
         hash_name, sign_name = self.algo_name.split(',')
-        padding_name = self.padding_name
-        key_name = os.path.join(self.key_path, self.key_name)
-
-        # Check hash and signature name/type
-        if hash_name not in SHAS:
-            self.Raise(hash_name + " is not supported")
-        if sign_name not in RSAS:
-            self.Raise(sign_name + " is not supported")
-
-        # Read the key
-        key = RSA.import_key(tools.read_file(key_name))
-
-        # Check if the key has the expected size
-        if key.size_in_bytes() != RSAS[sign_name]:
-            self.Raise("The key " + self.key_name + " don't have the expected 
size")
-
-        # Compute the hash
-        hash_image = SHAS[hash_name].new()
-        hash_image.update(self.image)
-
-        # Compute the signature
-        if padding_name is None:
-            padding_name = "pkcs-1.5"
-        padding = None
-        padding_args = None
-        if padding_name == "pss":
-            salt_len = key.size_in_bytes() - hash_image.digest_size - 2
-            padding = pss
-            padding_args = {'salt_bytes': salt_len}
-        elif padding_name == "pkcs-1.5":
-            padding = pkcs1_15
-            padding_args = {}
+        padding_name = self.padding_name or 'pkcs-1.5'
+        padding_btool = None
+
+        if self.hsm_support:
+            # HSM-based signing via pkcs11-tool btool
+            if not self.pkcs11_tool.is_present():
+                self.Raise("pkcs11-tool btool not available for HSM signing")
+
+            if not self.hsm_library:
+                self.Raise("hsm-library is required for HSM signing")
+
+            # Normalize padding name for btool
+            if padding_name == 'pss':
+                padding_btool = 'pss'
+            elif padding_name == 'pkcs-1.5':
+                padding_btool = 'pkcs1v15'
+            else:
+                self.Raise(f"Unsupported padding scheme: {padding_name}")
+
+            # Use slot index if provided (simpler than serial lookup)
+            try:
+                signature = self.pkcs11_tool.sign(
+                    data=data,
+                    module=self.hsm_library,
+                    token_label=self.hsm_token_label,
+                    token_serial=self.hsm_token_serial,
+                    slot_index=self.hsm_slot_index,
+                    key_label=self.hsm_key_label,
+                    key_id=self.hsm_key_id,
+                    pin=self.hsm_pin,
+                    hash_algo=hash_name,
+                    sign_algo=sign_name,
+                    padding=padding_btool
+                )
+            except (ValueError, RuntimeError) as e:
+                self.Raise(f"HSM signing failed: {e}")
+
         else:
-            self.Raise(padding_name + " is not supported")
+            # File-based signing (traditional method)
+            if not self.key_name:
+                self.Raise("key-name is required for file-based signing")
+
+            key_name = os.path.join(self.key_path, self.key_name)
+
+            # Check hash and signature name/type
+            if hash_name not in SHAS:
+                self.Raise(f"{hash_name} is not supported")
+            if sign_name not in RSAS:
+                self.Raise(f"{sign_name} is not supported")
+
+            # Read the key
+            key = RSA.import_key(tools.read_file(key_name))
+
+            # Check if the key has the expected size
+            if key.size_in_bytes() != RSAS[sign_name]:
+                self.Raise(
+                    f"The key {self.key_name} doesn't have the expected size "
+                    f"({key.size_in_bytes()} bytes vs expected "
+                    f"{RSAS[sign_name]} bytes)"
+                )
 
-        sig = padding.new(key, **padding_args).sign(hash_image)
+            # Compute the hash
+            hash_obj = SHAS[hash_name].new()
+            hash_obj.update(data)
 
-        hash_sig = SHA256.new()
-        hash_sig.update(sig)
+            # Setup padding
+            padding_module = None
+            padding_args = {}
+            if padding_name == "pss":
+                salt_len = key.size_in_bytes() - hash_obj.digest_size - 2
+                padding_module = pss
+                padding_args = {'salt_bytes': salt_len}
+            elif padding_name == "pkcs-1.5":
+                padding_module = pkcs1_15
+                padding_args = {}
+            else:
+                self.Raise(f"{padding_name} is not supported")
+
+            # Sign
+            signature = padding_module.new(key, **padding_args).sign(hash_obj)
+
+        return signature
+
+    def _CreateHeader(self):
+        """Create a pre-load header with signature
 
+        This method creates the pre-load header regardless of whether signing
+        is done via file-based keys or HSM. The signing backend is abstracted
+        in _SignData(), eliminating code duplication.
+
+        The header format is:
+        - First header (64 bytes): magic, version, sizes, hash of image 
signature
+        - Header signature (variable): signature of first header
+        - Image signature (variable): signature of image data
+        - Padding: zeros to fill header_size
+
+        Returns:
+            bytes: Complete pre-load header with padding
+        """
+        # Sign the image data
+        img_sig = self._SignData(self.image)
+
+        # Compute hash of image signature (always SHA256 for header)
+        hash_sig = SHA256.new(img_sig)
+
+        # Build header fields
         version = self.version
         header_size = self.header_size
         image_size = len(self.image)
-        ofs_img_sig = 64 + len(sig)
+        ofs_img_sig = 64 + len(img_sig)
         flags = 0
         reserved0 = 0
         reserved1 = 0
 
-        first_header = struct.pack('>4sIIIIIII32s', PRE_LOAD_MAGIC,
-                                   version, header_size, image_size,
-                                   ofs_img_sig, flags, reserved0,
-                                   reserved1, hash_sig.digest())
+        # Pack first header (64 bytes, big-endian)
+        first_header = struct.pack(
+            '>4sIIIIIII32s',
+            PRE_LOAD_MAGIC,      # 4 bytes: magic number 'UBSH'
+            version,             # 4 bytes: header version
+            header_size,         # 4 bytes: total header size
+            image_size,          # 4 bytes: image size
+            ofs_img_sig,         # 4 bytes: offset to image signature
+            flags,               # 4 bytes: flags (reserved)
+            reserved0,           # 4 bytes: reserved
+            reserved1,           # 4 bytes: reserved
+            hash_sig.digest()    # 32 bytes: SHA256 hash of image signature
+        )
+
+        # Sign the header
+        hdr_sig = self._SignData(first_header)
 
-        hash_first_header = SHAS[hash_name].new()
-        hash_first_header.update(first_header)
-        sig_first_header = padding.new(key, 
**padding_args).sign(hash_first_header)
+        # Assemble complete header: first_header + header_sig + image_sig
+        data = first_header + hdr_sig + img_sig
 
-        data = first_header + sig_first_header + sig
-        pad  = bytearray(self.header_size - len(data))
+        # Add padding to reach header_size
+        pad = bytearray(self.header_size - len(data))
 
         return data + pad
 
-- 
2.43.0

Reply via email to