Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2228240/?format=api
{ "id": 2228240, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2228240/?format=api", "web_url": "http://patchwork.ozlabs.org/project/uboot/patch/20260423154536.768603-3-sergio.prado@e-labworks.com/", "project": { "id": 18, "url": "http://patchwork.ozlabs.org/api/1.1/projects/18/?format=api", "name": "U-Boot", "link_name": "uboot", "list_id": "u-boot.lists.denx.de", "list_email": "u-boot@lists.denx.de", "web_url": null, "scm_url": null, "webscm_url": null }, "msgid": "<20260423154536.768603-3-sergio.prado@e-labworks.com>", "date": "2026-04-23T15:45:36", "name": "[v3,2/2] binman: x509_cert: add PKCS#11/HSM signing support", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "9d66a1bcb8936932d6c3a0a30279cfd68666d56a", "submitter": { "id": 67358, "url": "http://patchwork.ozlabs.org/api/1.1/people/67358/?format=api", "name": "Sergio Prado", "email": "sergio.prado@e-labworks.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/uboot/patch/20260423154536.768603-3-sergio.prado@e-labworks.com/mbox/", "series": [ { "id": 501468, "url": "http://patchwork.ozlabs.org/api/1.1/series/501468/?format=api", "web_url": "http://patchwork.ozlabs.org/project/uboot/list/?series=501468", "date": "2026-04-23T15:45:36", "name": "binman: add PKCS#11/HSM signing support for X509 certificates", "version": 3, "mbox": "http://patchwork.ozlabs.org/series/501468/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2228240/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2228240/checks/", "tags": {}, "headers": { "Return-Path": "<u-boot-bounces@lists.denx.de>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=e-labworks-com.20251104.gappssmtp.com\n header.i=@e-labworks-com.20251104.gappssmtp.com header.a=rsa-sha256\n header.s=20251104 header.b=p0YdyKCz;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de\n (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de;\n envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org)", "phobos.denx.de;\n dmarc=fail (p=none dis=none) header.from=e-labworks.com", "phobos.denx.de;\n spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de", "phobos.denx.de;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=e-labworks-com.20251104.gappssmtp.com\n header.i=@e-labworks-com.20251104.gappssmtp.com header.b=\"p0YdyKCz\";\n\tdkim-atps=neutral", "phobos.denx.de; dmarc=fail (p=none dis=none)\n header.from=e-labworks.com", "phobos.denx.de;\n spf=none smtp.mailfrom=sergio.prado@e-labworks.com" ], "Received": [ "from phobos.denx.de (phobos.denx.de\n [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g2xSw1kQdz1y2d\n\tfor <incoming@patchwork.ozlabs.org>; Sun, 26 Apr 2026 03:21:36 +1000 (AEST)", "from h2850616.stratoserver.net (localhost [IPv6:::1])\n\tby phobos.denx.de (Postfix) with ESMTP id 22983843FB;\n\tSat, 25 Apr 2026 19:21:34 +0200 (CEST)", "by phobos.denx.de (Postfix, from userid 109)\n id 853AC84413; Sat, 25 Apr 2026 19:21:32 +0200 (CEST)", "from mail-dy1-x1342.google.com (mail-dy1-x1342.google.com\n [IPv6:2607:f8b0:4864:20::1342])\n (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits))\n (No client certificate requested)\n by phobos.denx.de (Postfix) with ESMTPS id 27F24841D7\n for <u-boot@lists.denx.de>; Sat, 25 Apr 2026 19:21:29 +0200 (CEST)", "by mail-dy1-x1342.google.com with SMTP id\n 5a478bee46e88-2d8ffdc31d0so346352eec.0\n for <u-boot@lists.denx.de>; Sat, 25 Apr 2026 10:21:29 -0700 (PDT)", "from desktop.. ([2804:7f0:6401:dc31:4200:1760:44a0:9daa])\n by smtp.gmail.com with ESMTPSA id\n 5a478bee46e88-2e539fa5c86sm33732055eec.1.2026.04.23.08.46.29\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 23 Apr 2026 08:46:32 -0700 (PDT)" ], "X-Spam-Checker-Version": "SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de", "X-Spam-Level": "", "X-Spam-Status": "No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED,\n DKIM_VALID,RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_NONE autolearn=ham\n autolearn_force=no version=3.4.2", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=e-labworks-com.20251104.gappssmtp.com; s=20251104; t=1777137687;\n x=1777742487;\n darn=lists.denx.de;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=XPWWihH6+qR8yVOIL+ONIesalJTy1218yyCJiBCdL6w=;\n b=p0YdyKCziSCZExzXpw7xuEvHZlBR/Qbk+uW2uqb0XPUxklqq9ER/QUDRaobugQZVzs\n icX5zJ6YqwYAr5VwbpRvLi4VOGZB5qNk6DZtqYTwVqsoyTd6iuqWvWtSCirugY4B21tf\n Jrcxit/MzANyk05TCQlKkjafFc/s2olLjHCXjBSS/1KMKANFaqXzxX/QPlfsy1rmA/7A\n YvbGZvE0oeDZhAgZjasl2QHBXq0dnaGQ1DfSNGd5SSzlnHMZw04tGtBkOPjlIksUAJ06\n FATb1APubXnOqWjsGvt3gqiPuZIL0G1Myq6Of+v7YWi8Kp7jZbBRLQcxXkW8nCxuRDp8\n in4Q==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777137687; x=1777742487;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=XPWWihH6+qR8yVOIL+ONIesalJTy1218yyCJiBCdL6w=;\n b=knGyAT0V/uJVogitjrYbTMcAps2fJRQjFZOYWHpBWik3fEDJ2BvMpn1hb3PalvI834\n DgM4OPxbAH61IiL/n7vhmiHmH8G3yHX3rjdVWE9Je95n5yVUTD3ZN2pNCZw9H0QDi/ea\n E1OjkuoaSNtOcFMraEhn40MuOVxKH6jWN6KIh/vkYlL0JYpTD+HwZWaupr5vdnNGiCtT\n c6cYqOEbYuz/d3nWgkET+rNWn/024PYa3sVyBLfinKxmHuNcErqDMQlw3ryTe03yJeyG\n neU3BXSEHFT0cflqZK8vnz3dt9lUIccqKxagMICV29vKgGHMzDIr1GTq7l8rNEn91NRV\n 1IFA==", "X-Gm-Message-State": "AOJu0YxE8KUqhMfNkX+VVxU3DxP3NjszlboXqZJxYTWhXVVLcPp+PYod\n xuDbKH0tl1WT/Bp4EcLFGb5g02jC3FUetPrZa8WDA8DG0k3sFIa+9VT7muXVl7gIhZEI/VgkInT\n CB9XeR1+o8yxk", "X-Gm-Gg": "AeBDieun1G7w98ou29OiqF8Cxaul+8k2W67BLYX7RdtzyS8HtA1UeJNYFtHKIF5H39e\n boqdnIIqKQjYmBw7O6Jr07dI+9a8hTBJK9uSbu17EWXXDqLiwM4JSrBgyC02yyk2raTxkXv5Imr\n uQ629JhAX3s7bUeaS1OtzEJJ8JKURhhEVd891KiU3J/8uTT7oS1IATGqn+FiEeLDQT3KjZHPnm+\n hqeffhM0VwVw0IZJCY3xulFXYQfX+yYp3tj+J4MBurstVLnzt0NUFYd8qgjamPrNAOubIKLoqYk\n YLnjcPoHCxUO+AWhHI5CQjKmOtevenOSfPkZY3ktd82FYnLkV7ndXl4mlpDTZsm7Xkll3fqkV9g\n 0+Z78ElmJvIR5mtihmDWuPUYgw78SB8b0o1tgT4pNvJD06ttaPOrtQ0Gt41++4XOV3BcpdSI7ik\n +/O6QO/LHBZCFC3mV2Tvs94KxhQKBtTyeCgIYjc42ZQA==", "X-Received": "by 2002:a05:7301:4186:b0:2de:cc07:e99 with SMTP id\n 5a478bee46e88-2e464dafc4cmr14124143eec.7.1776959193243;\n Thu, 23 Apr 2026 08:46:33 -0700 (PDT)", "From": "Sergio Prado <sergio.prado@e-labworks.com>", "To": "u-boot@lists.denx.de", "Cc": "trini@konsulko.com, sjg@chromium.org, alpernebiyasak@gmail.com,\n ilias.apalodimas@linaro.org, marek.vasut+renesas@mailbox.org,\n sughosh.ganu@arm.com, wolfgang.wallner@at.abb.com, bb@ti.com,\n y.moog@phytec.de, xypron.glpk@gmx.de, sergio.prado@e-labworks.com,\n jerome.forissier@arm.com, afd@ti.com, quentin.schulz@cherry.de,\n Wojciech.Dubowik@mt.com", "Subject": "[PATCH v3 2/2] binman: x509_cert: add PKCS#11/HSM signing support", "Date": "Thu, 23 Apr 2026 12:45:36 -0300", "Message-Id": "<20260423154536.768603-3-sergio.prado@e-labworks.com>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20260423154536.768603-1-sergio.prado@e-labworks.com>", "References": "<20260423154536.768603-1-sergio.prado@e-labworks.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "u-boot@lists.denx.de", "X-Mailman-Version": "2.1.39", "Precedence": "list", "List-Id": "U-Boot discussion <u-boot.lists.denx.de>", "List-Unsubscribe": "<https://lists.denx.de/options/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=unsubscribe>", "List-Archive": "<https://lists.denx.de/pipermail/u-boot/>", "List-Post": "<mailto:u-boot@lists.denx.de>", "List-Help": "<mailto:u-boot-request@lists.denx.de?subject=help>", "List-Subscribe": "<https://lists.denx.de/listinfo/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=subscribe>", "Errors-To": "u-boot-bounces@lists.denx.de", "Sender": "\"U-Boot\" <u-boot-bounces@lists.denx.de>", "X-Virus-Scanned": "clamav-milter 0.103.8 at phobos.denx.de", "X-Virus-Status": "Clean" }, "content": "Allow X509 certificates used for TI K3 secure boot to be signed via an\nHSM using the PKCS#11 standard, so that the private key never leaves\nthe hardware token.\n\nTwo new make variables are introduced:\n\n BINMAN_PKCS11_URI PKCS#11 URI identifying the signing key, e.g.\n pkcs11:token=mytoken;object=mykey;type=private\n BINMAN_PKCS11_MODULE Path to the PKCS#11 shared library (.so), e.g.\n /usr/lib/softhsm/libsofthsm2.so\n\nWhen BINMAN_PKCS11_URI is set, it is forwarded to binman as the\npkcs11-uri entry argument, overriding the keyfile property at signing\ntime. BINMAN_PKCS11_MODULE is optional and only needed when the PKCS#11\nlibrary is not already configured via openssl.cnf or the\nPKCS11_MODULE_PATH environment variable.\n\nThe openssl bintool gains three helper methods:\n\n _pkcs11_use_provider() auto-detects whether the OpenSSL pkcs11\n provider (OpenSSL >= 3.1, pkcs11-provider) or the legacy pkcs11\n engine (libp11) is available, by inspecting 'openssl list\n -providers'.\n\n _build_key_args() builds the -key/-provider/-engine arguments for the\n openssl command line. When PKCS11_PIN is set in the environment,\n its value is appended as ?pin-value=<pin> to the URI so that\n non-interactive signing works with both the provider and engine\n paths.\n\n _run_cmd_pkcs11() sets PKCS11_MODULE_PATH and PKCS11_PROVIDER_MODULE\n before invoking openssl when a module path is given, and serialises\n concurrent signing calls with a module-level lock. The lock is\n required because binman's ThreadPoolExecutor may invoke multiple\n signing operations simultaneously, and HSMs (including SoftHSM2) do\n not support concurrent logins from different threads.\n\nExisting behavior is unchanged when neither BINMAN_PKCS11_URI nor\nBINMAN_PKCS11_MODULE is set.\n\nTested with SoftHSM2 and a YubiKey using the verdin-am62_a53_defconfig\nconfiguration and both engine and provider OpenSSL APIs.\n\nSigned-off-by: Sergio Prado <sergio.prado@e-labworks.com>\n---\n Makefile | 2 +\n tools/binman/binman.rst | 18 +++++\n tools/binman/btool/openssl.py | 117 +++++++++++++++++++++++++++-----\n tools/binman/etype/x509_cert.py | 47 +++++++++++--\n tools/binman/ftest.py | 110 ++++++++++++++++++++++++++++++\n 5 files changed, 274 insertions(+), 20 deletions(-)", "diff": "diff --git a/Makefile b/Makefile\nindex dfc95d314ddd..8d6c584b2731 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1701,6 +1701,8 @@ cmd_binman = $(srctree)/tools/binman/binman $(if $(BINMAN_DEBUG),-D) \\\n \t\t-a vpl-dtb=$(CONFIG_VPL_OF_REAL) \\\n \t\t-a pre-load-key-path=${PRE_LOAD_KEY_PATH} \\\n \t\t-a of-spl-remove-props=$(CONFIG_OF_SPL_REMOVE_PROPS) \\\n+\t\t$(if $(BINMAN_PKCS11_URI),-a pkcs11-uri=\"$(BINMAN_PKCS11_URI)\") \\\n+\t\t$(if $(BINMAN_PKCS11_MODULE),-a pkcs11-module=\"$(BINMAN_PKCS11_MODULE)\") \\\n \t\t$(BINMAN_$(@F))\n \n OBJCOPYFLAGS_u-boot.ldr.hex := -I binary -O ihex\ndiff --git a/tools/binman/binman.rst b/tools/binman/binman.rst\nindex 366491089ad9..e60eb7f572eb 100644\n--- a/tools/binman/binman.rst\n+++ b/tools/binman/binman.rst\n@@ -2161,6 +2161,24 @@ BINMAN_INDIRS\n Sets the search path for input files used by binman by adding one or more\n `-I` arguments. See :ref:`External blobs`.\n \n+BINMAN_PKCS11_URI\n+ PKCS#11 URI used to sign boot artifacts via an HSM instead of a PEM key\n+ file on disk. When set, it is passed as ``-a pkcs11-uri=<uri>`` to binman,\n+ which overrides the ``keyfile`` entry argument for all signing operations.\n+ Example::\n+\n+ make BINMAN_PKCS11_URI=\"pkcs11:token=mytoken;object=mykey;type=private\"\n+\n+BINMAN_PKCS11_MODULE\n+ Path to the PKCS#11 shared library (.so) for HSM signing. When set, it is\n+ passed as ``-a pkcs11-module=<path>`` to binman. Only needed when the\n+ module is not already configured via ``openssl.cnf`` or the\n+ ``PKCS11_MODULE_PATH`` environment variable. Typically used together with\n+ ``BINMAN_PKCS11_URI``::\n+\n+ make BINMAN_PKCS11_URI=\"pkcs11:token=mytoken;object=mykey;type=private\" \\\n+ BINMAN_PKCS11_MODULE=\"/usr/lib/pkcs11/libsofthsm2.so\"\n+\n BINMAN_TOOLPATHS\n Sets the search path for external tool used by binman by adding one or more\n `--toolpath` arguments. See :ref:`External tools`.\ndiff --git a/tools/binman/btool/openssl.py b/tools/binman/btool/openssl.py\nindex b26f087c4470..ec477a46a3d7 100644\n--- a/tools/binman/btool/openssl.py\n+++ b/tools/binman/btool/openssl.py\n@@ -11,10 +11,16 @@ Source code is at https://www.openssl.org/\n \"\"\"\n \n import hashlib\n+import os\n+import threading\n \n from binman import bintool\n from u_boot_pylib import tools\n \n+# Serializes concurrent PKCS#11 signing operations. SoftHSM2 and many real\n+# HSMs do not support simultaneous logins from multiple threads, so binman's\n+# ThreadPoolExecutor would otherwise cause intermittent login failures.\n+_pkcs11_lock = threading.Lock()\n \n VALID_SHAS = [256, 384, 512, 224]\n SHA_OIDS = {256:'2.16.840.1.101.3.4.2.1',\n@@ -35,18 +41,89 @@ class Bintoolopenssl(bintool.Bintool):\n super().__init__(\n name, 'openssl cryptography toolkit',\n version_regex=r'OpenSSL (.*) \\(', version_args='version')\n+ self._use_provider = None\n+\n+ def _pkcs11_use_provider(self):\n+ \"\"\"Return True if the provider API should be used for PKCS#11 signing.\n+\n+ Checks whether the pkcs11 provider is available by running\n+ 'openssl list -providers'. The result is cached after the first call\n+ to avoid spawning a subprocess on every signing operation.\n+\n+ Returns:\n+ bool: True if provider should be used, False if engine should be used\n+ \"\"\"\n+ if self._use_provider is None:\n+ out = self.run_cmd('list', '-providers') or ''\n+ self._use_provider = 'pkcs11' in out\n+ return self._use_provider\n+\n+ def _build_key_args(self, key_fname):\n+ \"\"\"Build openssl CLI arguments for the signing key.\n+\n+ Always add '-key key_fname', and for a PKCS#11 URI (pkcs11:...),\n+ prepends either '-provider default -provider pkcs11' (if provider API\n+ is available) or '-engine pkcs11 -keyform engine' (legacy fallback).\n+ If the PKCS11_PIN environment variable is set, appends pin-value=<pin>\n+ to the URI.\n+\n+ Args:\n+ key_fname (str): Filename of .pem file or PKCS#11 URI\n+\n+ Returns:\n+ list: openssl arguments for the key selection\n+ \"\"\"\n+ if key_fname.startswith('pkcs11:'):\n+ pin = os.environ.get('PKCS11_PIN')\n+ if pin:\n+ sep = '&' if '?' in key_fname else '?'\n+ key_fname = f'{key_fname}{sep}pin-value={pin}'\n+ if self._pkcs11_use_provider():\n+ args = ['-provider', 'default', '-provider', 'pkcs11', '-key', key_fname]\n+ else:\n+ args = ['-engine', 'pkcs11', '-keyform', 'engine', '-key', key_fname]\n+ else:\n+ args = ['-key', key_fname]\n+ return args\n+\n+ def _run_cmd_pkcs11(self, pkcs11_module, *args):\n+ \"\"\"Run an openssl command, optionally passing PKCS#11 module env vars.\n+\n+ When pkcs11_module is set, PKCS11_MODULE_PATH (engine) and\n+ PKCS11_PROVIDER_MODULE (provider) are passed as extra environment\n+ variables.\n+ If already configured via openssl.cnf or the calling environment,\n+ pkcs11_module can be None.\n+\n+ Args:\n+ pkcs11_module (str or None): Path to the PKCS#11 shared library\n+ *args: Arguments forwarded to run_cmd\n+\n+ Returns:\n+ str: openssl output\n+ \"\"\"\n+ if pkcs11_module:\n+ extra_env = {\n+ 'PKCS11_MODULE_PATH': pkcs11_module,\n+ 'PKCS11_PROVIDER_MODULE': pkcs11_module,\n+ }\n+ with _pkcs11_lock:\n+ return self.run_cmd(*args, extra_env=extra_env)\n+ return self.run_cmd(*args)\n \n def x509_cert(self, cert_fname, input_fname, key_fname, cn, revision,\n- config_fname):\n+ config_fname, pkcs11_module=None):\n \"\"\"Create a certificate\n \n Args:\n cert_fname (str): Filename of certificate to create\n input_fname (str): Filename containing data to sign\n- key_fname (str): Filename of .pem file\n+ key_fname (str): Filename of .pem file or PKCS#11 URI\n cn (str): Common name\n revision (int): Revision number\n config_fname (str): Filename to write fconfig into\n+ pkcs11_module (str or None): Path to PKCS#11 shared library, or\n+ None if already configured via openssl.cnf or the environment\n \n Returns:\n str: Tool output\n@@ -76,19 +153,20 @@ shaType = OID:2.16.840.1.101.3.4.2.3\n shaValue = FORMAT:HEX,OCT:{hashval}\n imageSize = INTEGER:{len(indata)}\n ''', file=outf)\n- args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',\n+ args = ['req', '-new', '-x509', *self._build_key_args(key_fname), '-nodes',\n '-outform', 'DER', '-out', cert_fname, '-config', config_fname,\n '-sha512']\n- return self.run_cmd(*args)\n+ return self._run_cmd_pkcs11(pkcs11_module, *args)\n \n def x509_cert_sysfw(self, cert_fname, input_fname, key_fname, sw_rev,\n- config_fname, req_dist_name_dict, firewall_cert_data):\n+ config_fname, req_dist_name_dict, firewall_cert_data,\n+ pkcs11_module=None):\n \"\"\"Create a certificate to be booted by system firmware\n \n Args:\n cert_fname (str): Filename of certificate to create\n input_fname (str): Filename containing data to sign\n- key_fname (str): Filename of .pem file\n+ key_fname (str): Filename of .pem file or PKCS#11 URI\n sw_rev (int): Software revision\n config_fname (str): Filename to write fconfig into\n req_dist_name_dict (dict): Dictionary containing key-value pairs of\n@@ -101,6 +179,8 @@ imageSize = INTEGER:{len(indata)}\n extended certificate\n - certificate (str): Extended firewall certificate with\n the information for the firewall configurations.\n+ pkcs11_module (str or None): Path to PKCS#11 shared library, or\n+ None if already configured via openssl.cnf or the environment\n \n Returns:\n str: Tool output\n@@ -146,20 +226,20 @@ authInPlace = INTEGER:{hex(firewall_cert_data['auth_in_place'])}\n numFirewallRegions = INTEGER:{firewall_cert_data['num_firewalls']}\n {firewall_cert_data['certificate']}\n ''', file=outf)\n- args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',\n+ args = ['req', '-new', '-x509', *self._build_key_args(key_fname), '-nodes',\n '-outform', 'DER', '-out', cert_fname, '-config', config_fname,\n '-sha512']\n- return self.run_cmd(*args)\n+ return self._run_cmd_pkcs11(pkcs11_module, *args)\n \n def x509_cert_rom(self, cert_fname, input_fname, key_fname, sw_rev,\n config_fname, req_dist_name_dict, cert_type, bootcore,\n- bootcore_opts, load_addr, sha, debug):\n+ bootcore_opts, load_addr, sha, debug, pkcs11_module=None):\n \"\"\"Create a certificate\n \n Args:\n cert_fname (str): Filename of certificate to create\n input_fname (str): Filename containing data to sign\n- key_fname (str): Filename of .pem file\n+ key_fname (str): Filename of .pem file or PKCS#11 URI\n sw_rev (int): Software revision\n config_fname (str): Filename to write fconfig into\n req_dist_name_dict (dict): Dictionary containing key-value pairs of\n@@ -170,6 +250,8 @@ numFirewallRegions = INTEGER:{firewall_cert_data['num_firewalls']}\n bootcore_opts(int): Booting core option, lockstep (0) or split (2) mode\n load_addr (int): Load address of image\n sha (int): Hash function\n+ pkcs11_module (str or None): Path to PKCS#11 shared library, or\n+ None if already configured via openssl.cnf or the environment\n \n Returns:\n str: Tool output\n@@ -231,10 +313,10 @@ emailAddress = {req_dist_name_dict['emailAddress']}\n coreDbgEn = INTEGER:0\n coreDbgSecEn = INTEGER:0\n ''', file=outf)\n- args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',\n+ args = ['req', '-new', '-x509', *self._build_key_args(key_fname), '-nodes',\n '-outform', 'DER', '-out', cert_fname, '-config', config_fname,\n '-sha512']\n- return self.run_cmd(*args)\n+ return self._run_cmd_pkcs11(pkcs11_module, *args)\n \n def x509_cert_rom_combined(self, cert_fname, input_fname, key_fname, sw_rev,\n config_fname, req_dist_name_dict, load_addr, sha, total_size, num_comps,\n@@ -242,13 +324,14 @@ emailAddress = {req_dist_name_dict['emailAddress']}\n imagesize_sbl, hashval_sbl, load_addr_sysfw, imagesize_sysfw,\n hashval_sysfw, load_addr_sysfw_data, imagesize_sysfw_data,\n hashval_sysfw_data, sysfw_inner_cert_ext_boot_block,\n- dm_data_ext_boot_block, bootcore_opts, debug):\n+ dm_data_ext_boot_block, bootcore_opts, debug,\n+ pkcs11_module=None):\n \"\"\"Create a certificate\n \n Args:\n cert_fname (str): Filename of certificate to create\n input_fname (str): Filename containing data to sign\n- key_fname (str): Filename of .pem file\n+ key_fname (str): Filename of .pem file or PKCS#11 URI\n sw_rev (int): Software revision\n config_fname (str): Filename to write fconfig into\n req_dist_name_dict (dict): Dictionary containing key-value pairs of\n@@ -259,6 +342,8 @@ emailAddress = {req_dist_name_dict['emailAddress']}\n load_addr (int): Load address of image\n sha (int): Hash function\n bootcore_opts (int): Booting core option, lockstep (0) or split (2) mode\n+ pkcs11_module (str or None): Path to PKCS#11 shared library, or\n+ None if already configured via openssl.cnf or the environment\n \n Returns:\n str: Tool output\n@@ -342,10 +427,10 @@ coreDbgSecEn = INTEGER:0\n \n {dm_data_ext_boot_block}\n ''', file=outf)\n- args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',\n+ args = ['req', '-new', '-x509', *self._build_key_args(key_fname), '-nodes',\n '-outform', 'DER', '-out', cert_fname, '-config', config_fname,\n '-sha512']\n- return self.run_cmd(*args)\n+ return self._run_cmd_pkcs11(pkcs11_module, *args)\n \n def fetch(self, method):\n \"\"\"Fetch handler for openssl\ndiff --git a/tools/binman/etype/x509_cert.py b/tools/binman/etype/x509_cert.py\nindex b6e8b0b4fb09..cdeff1a227a1 100644\n--- a/tools/binman/etype/x509_cert.py\n+++ b/tools/binman/etype/x509_cert.py\n@@ -19,6 +19,18 @@ class Entry_x509_cert(Entry_collection):\n \n Properties / Entry arguments:\n - content: List of phandles to entries to sign\n+ - keyfile: Filename of the PEM key file used to sign the binary\n+ - pkcs11-uri: PKCS#11 URI for signing via an HSM, e.g.\n+ ``pkcs11:token=mytoken;object=mykey;type=private``. When provided\n+ via ``-a pkcs11-uri=...`` on the binman command line (or via the\n+ ``BINMAN_PKCS11_URI`` make variable), it overrides ``keyfile`` for\n+ the signing operation.\n+ - pkcs11-module: Path to the PKCS#11 shared library (.so), e.g.\n+ ``/usr/lib/pkcs11/libsofthsm2.so``. Only needed when signing via\n+ HSM and the module is not already configured via ``openssl.cnf`` or\n+ the ``PKCS11_MODULE_PATH`` environment variable. Provided via\n+ ``-a pkcs11-module=...`` or the ``BINMAN_PKCS11_MODULE`` make\n+ variable.\n \n Output files:\n - input.<unique_name> - input file passed to openssl\n@@ -27,6 +39,16 @@ class Entry_x509_cert(Entry_collection):\n \n openssl signs the provided data, writing the signature in this entry. This\n allows verification that the data is genuine\n+\n+ To sign with an HSM, pass the PKCS#11 URI and optionally the module path\n+ at build time::\n+\n+ make BINMAN_PKCS11_URI=\"pkcs11:token=mytoken;object=mykey;type=private\" \\\\\n+ BINMAN_PKCS11_MODULE=\"/usr/lib/pkcs11/libsofthsm2.so\" \\\\\n+ PKCS11_PIN=\"1234\"\n+\n+ The ``PKCS11_PIN`` environment variable is used by the openssl bintool to\n+ append ``?pin-value=<pin>`` to the URI when required.\n \"\"\"\n def __init__(self, section, etype, node):\n super().__init__(section, etype, node)\n@@ -53,6 +75,8 @@ class Entry_x509_cert(Entry_collection):\n self.dm_data_ext_boot_block = None\n self.firewall_cert_data = None\n self.debug = False\n+ self.pkcs11_uri = None\n+ self.pkcs11_module = None\n \n def ReadNode(self):\n super().ReadNode()\n@@ -61,6 +85,10 @@ class Entry_x509_cert(Entry_collection):\n self.key_fname = self.GetEntryArgsOrProps([\n EntryArg('keyfile', str)], required=True)[0]\n self.sw_rev = fdt_util.GetInt(self._node, 'sw-rev', 1)\n+ self.pkcs11_uri = self.GetEntryArgsOrProps([\n+ EntryArg('pkcs11-uri', str)], required=False)[0]\n+ self.pkcs11_module = self.GetEntryArgsOrProps([\n+ EntryArg('pkcs11-module', str)], required=False)[0]\n \n def GetCertificate(self, required, type='generic'):\n \"\"\"Get the contents of this entry\n@@ -80,6 +108,13 @@ class Entry_x509_cert(Entry_collection):\n if input_data is None:\n return None\n \n+ # Override keyfile with the PKCS#11 URI if provided. This must be\n+ # done here rather than in ReadNode(), because subclasses (e.g.\n+ # ti_secure_rom, ti_secure) call super().ReadNode() and then\n+ # overwrite self.key_fname with the DTS 'keyfile' property.\n+ if self.pkcs11_uri:\n+ self.key_fname = self.pkcs11_uri\n+\n uniq = self.GetUniqueName()\n output_fname = tools.get_output_filename('cert.%s' % uniq)\n input_fname = tools.get_output_filename('input.%s' % uniq)\n@@ -93,7 +128,8 @@ class Entry_x509_cert(Entry_collection):\n key_fname=self.key_fname,\n cn=self._cert_ca,\n revision=self._cert_rev,\n- config_fname=config_fname)\n+ config_fname=config_fname,\n+ pkcs11_module=self.pkcs11_module)\n elif type == 'sysfw':\n stdout = self.openssl.x509_cert_sysfw(\n cert_fname=output_fname,\n@@ -102,7 +138,8 @@ class Entry_x509_cert(Entry_collection):\n config_fname=config_fname,\n sw_rev=self.sw_rev,\n req_dist_name_dict=self.req_dist_name,\n- firewall_cert_data=self.firewall_cert_data)\n+ firewall_cert_data=self.firewall_cert_data,\n+ pkcs11_module=self.pkcs11_module)\n elif type == 'rom':\n stdout = self.openssl.x509_cert_rom(\n cert_fname=output_fname,\n@@ -116,7 +153,8 @@ class Entry_x509_cert(Entry_collection):\n bootcore_opts=self.bootcore_opts,\n load_addr=self.load_addr,\n sha=self.sha,\n- debug=self.debug\n+ debug=self.debug,\n+ pkcs11_module=self.pkcs11_module\n )\n elif type == 'rom-combined':\n stdout = self.openssl.x509_cert_rom_combined(\n@@ -143,7 +181,8 @@ class Entry_x509_cert(Entry_collection):\n sysfw_inner_cert_ext_boot_block=self.sysfw_inner_cert_ext_boot_block,\n dm_data_ext_boot_block=self.dm_data_ext_boot_block,\n bootcore_opts=self.bootcore_opts,\n- debug=self.debug\n+ debug=self.debug,\n+ pkcs11_module=self.pkcs11_module\n )\n if stdout is not None:\n data = tools.read_file(output_fname)\ndiff --git a/tools/binman/ftest.py b/tools/binman/ftest.py\nindex ca5149ee654a..a23719c767c8 100644\n--- a/tools/binman/ftest.py\n+++ b/tools/binman/ftest.py\n@@ -6884,6 +6884,116 @@ fdt fdtmap Extract the devicetree blob from the fdtmap\n err = stderr.getvalue()\n self.assertRegex(err, \"Image 'image'.*missing bintools.*: openssl\")\n \n+ def testOpenSslBuildKeyArgsPem(self):\n+ \"\"\"Test _build_key_args with a regular PEM key file\"\"\"\n+ openssl = bintool.Bintool.create('openssl')\n+ args = openssl._build_key_args('/path/to/key.pem')\n+ self.assertEqual(['-key', '/path/to/key.pem'], args)\n+\n+ def testOpenSslBuildKeyArgsPkcs11Provider(self):\n+ \"\"\"Test _build_key_args with a PKCS#11 URI when provider API is available\"\"\"\n+ uri = 'pkcs11:token=mytoken;object=mykey'\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.object(openssl, 'run_cmd',\n+ return_value='pkcs11 provider'):\n+ args = openssl._build_key_args(uri)\n+ self.assertEqual(['-provider', 'default', '-provider', 'pkcs11',\n+ '-key', uri], args)\n+\n+ def testOpenSslBuildKeyArgsPkcs11Engine(self):\n+ \"\"\"Test _build_key_args with a PKCS#11 URI when only engine API is available\"\"\"\n+ uri = 'pkcs11:token=mytoken;object=mykey'\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.object(openssl, 'run_cmd', return_value=''):\n+ args = openssl._build_key_args(uri)\n+ self.assertEqual(['-engine', 'pkcs11', '-keyform', 'engine',\n+ '-key', uri], args)\n+\n+ def testOpenSslBuildKeyArgsPkcs11Pin(self):\n+ \"\"\"Test _build_key_args appends pin-value to URI when PKCS11_PIN is set\"\"\"\n+ uri = 'pkcs11:token=mytoken;object=mykey'\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.dict('os.environ', {'PKCS11_PIN': '1234'}):\n+ with unittest.mock.patch.object(openssl, 'run_cmd',\n+ return_value='pkcs11'):\n+ args = openssl._build_key_args(uri)\n+ self.assertIn(f'{uri}?pin-value=1234', args)\n+\n+ def testOpenSslBuildKeyArgsPkcs11PinExistingQuery(self):\n+ \"\"\"Test _build_key_args uses '&' separator when URI already has query params\"\"\"\n+ uri = 'pkcs11:token=mytoken;object=mykey?module-path=/path/to/lib'\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.dict('os.environ', {'PKCS11_PIN': '1234'}):\n+ with unittest.mock.patch.object(openssl, 'run_cmd',\n+ return_value='pkcs11'):\n+ args = openssl._build_key_args(uri)\n+ self.assertIn(f'{uri}&pin-value=1234', args)\n+\n+ def testOpenSslPkcs11UseProviderCached(self):\n+ \"\"\"Test that _pkcs11_use_provider caches its result after the first call\"\"\"\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.object(openssl, 'run_cmd',\n+ return_value='pkcs11') as mock_cmd:\n+ result1 = openssl._pkcs11_use_provider()\n+ result2 = openssl._pkcs11_use_provider()\n+ self.assertTrue(result1)\n+ self.assertTrue(result2)\n+ mock_cmd.assert_called_once_with('list', '-providers')\n+\n+ def testOpenSslRunCmdPkcs11NoModule(self):\n+ \"\"\"Test _run_cmd_pkcs11 without a module just forwards to run_cmd\"\"\"\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.object(openssl, 'run_cmd',\n+ return_value='output') as mock_cmd:\n+ result = openssl._run_cmd_pkcs11(None, 'version')\n+ mock_cmd.assert_called_once_with('version')\n+ self.assertEqual('output', result)\n+\n+ def testOpenSslRunCmdPkcs11WithModule(self):\n+ \"\"\"Test _run_cmd_pkcs11 passes module paths to run_cmd via extra_env\"\"\"\n+ module = '/usr/lib/pkcs11/libsofthsm2.so'\n+ openssl = bintool.Bintool.create('openssl')\n+ with unittest.mock.patch.object(openssl, 'run_cmd',\n+ return_value='output') as mock_cmd:\n+ openssl._run_cmd_pkcs11(module, 'version')\n+ mock_cmd.assert_called_once_with('version', extra_env={\n+ 'PKCS11_MODULE_PATH': module,\n+ 'PKCS11_PROVIDER_MODULE': module,\n+ })\n+\n+ def testX509CertPkcs11(self):\n+ \"\"\"Test X509 certificate creation using a PKCS#11 URI instead of a key file\"\"\"\n+ PKCS11_URI = 'pkcs11:token=test;object=mykey;type=private'\n+ PKCS11_MODULE = '/usr/lib/pkcs11/libsofthsm2.so'\n+ original = bintool.Bintool.run_cmd\n+ signing_args = []\n+\n+ def fake_openssl(self_tool, *args, binary=False, extra_env=None):\n+ if self_tool.name != 'openssl':\n+ return original(self_tool, *args, binary=binary)\n+ arg_list = list(args)\n+ if arg_list == ['list', '-providers']:\n+ return 'pkcs11 provider'\n+ if '-out' in arg_list:\n+ signing_args.extend(arg_list)\n+ tools.write_file(arg_list[arg_list.index('-out') + 1],\n+ b'\\x00' * 32)\n+ return ''\n+\n+ entry_args = {\n+ 'keyfile': self.TestFile('security/key.key'),\n+ 'pkcs11-uri': PKCS11_URI,\n+ 'pkcs11-module': PKCS11_MODULE,\n+ }\n+ with unittest.mock.patch.object(bintool.Bintool, 'run_cmd',\n+ new=fake_openssl):\n+ data = self._DoReadFileDtb('security/x509_cert.dts',\n+ entry_args=entry_args)[0]\n+ self.assertGreater(len(data), len(U_BOOT_DATA))\n+ self.assertEqual(U_BOOT_DATA, data[-4:])\n+ self.assertIn('-provider pkcs11', ' '.join(signing_args))\n+ self.assertIn(PKCS11_URI, signing_args)\n+\n def testPackRockchipTpl(self):\n \"\"\"Test that an image with a Rockchip TPL binary can be created\"\"\"\n data = self._DoReadFile('vendor/rockchip_tpl.dts')\n", "prefixes": [ "v3", "2/2" ] }