[PATCH v3 0/2] binman: add PKCS#11/HSM signing support for X509 certificates
Sergio Prado
sergio.prado at e-labworks.com
Thu Apr 23 17:45:34 CEST 2026
Motivation
----------
TI K3 secure boot requires X509 certificates to be signed with a private
key at build time. Until now, binman expected this key to be present as a
plain PEM file on the build host. For production use, however, private keys
should never exist unprotected on a build machine — they belong inside a
Hardware Security Module (HSM) that enforces access control and keeps the
key material unexportable.
This series adds support for signing X509 certificates via any PKCS#11-
capable HSM (YubiKey, TPM, network HSM, SoftHSM2 for development, etc.).
Design
------
Two new make variables are the sole user-visible interface:
BINMAN_PKCS11_URI PKCS#11 URI of the signing key on the HSM, e.g.
pkcs11:token=mytoken;object=mykey;type=private
BINMAN_PKCS11_MODULE Path to the PKCS#11 shared library (.so), e.g.
/usr/lib/softhsm/libsofthsm2.so (optional when the
module is already configured via openssl.cnf or
PKCS11_MODULE_PATH)
When BINMAN_PKCS11_URI is set, binman replaces the on-disk keyfile with
the URI at signing time. The PKCS11_PIN environment variable can be used
to supply the token PIN non-interactively; it is appended as a
pin-value=<pin> query parameter to the URI.
The openssl bintool auto-detects which OpenSSL PKCS#11 interface to use:
- If the pkcs11 provider is loaded (OpenSSL >= 3.1 + pkcs11-provider),
it uses -provider default -provider pkcs11.
- Otherwise it falls back to the legacy libp11 engine
(-engine pkcs11 -keyform engine).
Detection is done once via 'openssl list -providers' and the result is
cached, so there is no per-signing subprocess overhead.
Testing
-------
Tested on a Toradex Verdin AM62 (verdin-am62_a53_defconfig) board with
both legacy engine path and pkcs11 provider path using SoftHSM2 and
YubiKey 5 NFC.
A subtle issue was discovered during testing: binman uses a
ThreadPoolExecutor internally (etype/section.py) and may call multiple
ObtainContents() methods concurrently even under make -j1. SoftHSM2 and
some real HSMs do not support simultaneous logins from different threads,
which caused intermittent "login failed" and "object not found" errors
during multi-certificate builds. To fix it, a module-level threading.Lock
was used to serialise all PKCS#11 signing calls.
Test coverage: btool/openssl.py and etype/x509_cert.py remain at 100%
(verified with 'binman test -T').
Changes in v3:
- Split into two patches: bintool infrastructure (1/2) and x509_cert
feature (2/2)
- Fix global environment mutation: _run_cmd_pkcs11() no longer writes to
os.environ directly; it now uses the new extra_env parameter so module
paths are scoped to the subprocess only, which is both cleaner and safe
under concurrent execution
- Add module-level threading.Lock to serialise concurrent PKCS#11 signing
calls and fix intermittent login failures caused by binman's
ThreadPoolExecutor
- Fix URI query string separator: use '&' when the URI already contains
'?' (e.g. module-path already present), '?' otherwise
- Test cases updated
Changes in v2:
- Add tests for _build_key_args() (PEM path, PKCS#11 provider, PKCS#11
engine, PIN appending), _pkcs11_use_provider() (caching),
_run_cmd_pkcs11() (with and without module path), and end-to-end
x509_cert signing with a PKCS#11 URI (testX509CertPkcs11), ensuring
btool/openssl.py and etype/x509_cert.py have 100% test coverage
Sergio Prado (2):
binman: bintool: add extra_env parameter to run_cmd
binman: x509_cert: add PKCS#11/HSM signing support
Makefile | 2 +
tools/binman/binman.rst | 18 +++++
tools/binman/bintool.py | 15 +++-
tools/binman/btool/openssl.py | 117 +++++++++++++++++++++++++++-----
tools/binman/etype/x509_cert.py | 47 +++++++++++--
tools/binman/ftest.py | 110 ++++++++++++++++++++++++++++++
6 files changed, 286 insertions(+), 23 deletions(-)
--
2.34.1
More information about the U-Boot
mailing list