[PATCH v5 0/2] binman: add PKCS#11/HSM signing support for X509 certificates

Sergio Prado sergio.prado at e-labworks.com
Mon May 25 15:28:26 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
------

The 'keyfile' entry argument of x509_cert (and its TI K3 subclasses)
now accepts either a filesystem path to a PEM key, as before, or a
PKCS#11 URI referring to a key stored in an HSM. The URI is forwarded
as-is to 'openssl -key', which resolves it via the pkcs11 provider (or
engine) configured externally through OPENSSL_CONF. This keeps binman
itself free of any PKCS#11-specific knowledge.

A new make variable, BINMAN_X509_KEY_URI, overrides the 'keyfile' entry
argument for all x509 certificate signing operations:

    make BINMAN_X509_KEY_URI="pkcs11:token=mytk;object=mykey;type=private" \
         OPENSSL_CONF=/path/to/openssl.cnf

The recommended way to deliver the PIN for non-interactive signing is
to configure pkcs11-module-token-pin in openssl.cnf under the pkcs11
provider section. As a convenience fallback, the PKCS11_PIN environment
variable can be set; its value is appended to the URI as a percent-
encoded ?pin-value=<pin> (per RFC 7512) so the PIN does not have to be
embedded in BINMAN_X509_KEY_URI.

Two URI forms are supported on OpenSSL 3.x:

  - Provider path (recommended), via the pkcs11-provider package
  - Engine path, prefixed with org.openssl.engine:<engine_name>: so
    OpenSSL 3.x's STORE API routes it to the engine

Testing
-------

Tested on a Toradex Verdin AM62 (verdin-am62_a53_defconfig) board with
both the engine path and the pkcs11 provider path, using SoftHSM2 and
YubiKey 5 NFC.

A SoftHSM2-based integration test (testX509CertPkcs11) exercises the
full signing path end-to-end against an in-tree test key; it skips
cleanly when the OpenSSL pkcs11 provider is not installed. A unit test
(testX509CertBuildPkcs11Key) covers the URI/PIN combiner including
percent-encoding of reserved characters.

Changes in v5:
- Split the Entry_x509_cert docstring expansion into a separate
  preparatory commit (Simon)
- Unify URI support under the existing 'keyfile' entry argument
  rather than introducing a new 'x509-key-uri' arg; drop the
  Entry_x509_cert state and override logic that v4 added (Quentin)
- Percent-encode the PIN before appending to the URI using
  urllib.parse.quote(), per RFC 7512, and add a unit test
  covering PINs with reserved characters (Simon)
- Detect the OpenSSL pkcs11 provider via
  'openssl list -providers -provider pkcs11' rather than discovering
  the provider .so path manually; drops the 'openssl version -m'
  MODULESDIR lookup (Quentin)
- Drop the 'p11-kit print-config' / softhsm2 .so discovery from the
  test; the simpler openssl.cnf relies on softhsm2 being registered
  with p11-kit globally (Quentin). As a side effect this also fixes
  the test on Ubuntu 22.04, where 'p11-kit print-config' is not a
  valid subcommand
- Move the test openssl.cnf in-tree as
  tools/binman/test/fit/openssl_provider.conf and trim it to the
  minimum needed (no 'module = ', no 'pkcs11-module-path = ') (Quentin)
- Replace 'pkcs11-tool --keypairgen' with 'softhsm2-util --import' of
  tools/binman/test/fit/rsa2048.key so the test runs faster and drops
  the pkcs11-tool dependency (Quentin)
- Recommend pkcs11-module-token-pin in openssl.cnf as the primary
  way to deliver the PIN; PKCS11_PIN env var is now documented as
  the convenience fallback (Quentin)
- Drop the incorrect claim that PKCS11_PIN keeps the PIN out of
  shell history (Quentin)
- Rephrase the URI intro in binman.rst and clarify that
  'pkcs11-provider' is a Debian package name, not a path (Quentin)
- Drop the inheritance notes added to Entry_ti_secure and
  Entry_ti_secure_rom in v4; with the v5 keyfile unification the
  original motivation (an inherited x509-key-uri property) no
  longer applies

Changes in v4:
- Drop the v3 bintool extra_env commit entirely; binman no longer
  sets any PKCS#11-related environment variables (Quentin)
- Drop BINMAN_PKCS11_MODULE / pkcs11-module entry argument; the
  PKCS#11 module path must be configured externally via openssl.cnf
  (Quentin)
- Drop provider/engine auto-detection (_pkcs11_use_provider,
  _build_key_args, _run_cmd_pkcs11) along with the threading.Lock;
  the user selects provider or engine via OPENSSL_CONF and the URI
  form (Quentin)
- Rename the v3 BINMAN_PKCS11_URI / pkcs11-uri to BINMAN_X509_KEY_URI
  / x509-key-uri to scope the names to x509 certificate entries
  without locking them to a specific URI scheme (Quentin)
- Document that the engine path is supported on OpenSSL 3.x by
  prefixing the URI with org.openssl.engine:<engine_name>: (Quentin)
- Replace the mocked openssl test with a real SoftHSM2-based
  integration test using the provider path and OPENSSL_CONF (Quentin)
- Use 'p11-kit print-config' to locate the softhsm2 library at test
  time instead of hardcoding a distro-specific path (Quentin)
- Use 'openssl version -m' (MODULESDIR) to locate the OpenSSL pkcs11
  provider .so file, so multiarch paths like
  /usr/lib/x86_64-linux-gnu/ossl-modules on Debian/Ubuntu are handled
  correctly
- Generate the test RSA keypair with pkcs11-tool; softhsm2-util has
  no key-generation action (only --import) and silently exits 0 on an
  unknown --generate-keypair option, which would leave the token
  empty and make the openssl step fail with 'Could not read private
  key'
- Add self._CheckBintool() to all PKCS#11 test paths so tests skip
  cleanly when bintools are missing (Quentin)
- Extract the URI/PIN combiner into Entry_x509_cert._build_pkcs11_key()
  and add a unit test for it
- Document that PKCS11_PIN keeps the PIN out of the make command line
  but is still visible in 'ps' output via the openssl invocation; for
  improved isolation, configure the PIN in openssl.cnf
- Document all Entry_x509_cert properties (content, keyfile,
  x509-key-uri, cert-ca, cert-revision-int, sw-rev) in its docstring
  (Quentin)
- Add inheritance notes to Entry_ti_secure and Entry_ti_secure_rom
  docstrings, pointing out that they extend Entry_x509_cert via
  super() and therefore accept its properties (notably x509-key-uri)
  (Quentin)

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: x509_cert: document Entry_x509_cert properties
  binman: x509_cert: support PKCS#11 URI in keyfile for HSM signing

 Makefile                                    |  1 +
 tools/binman/binman.rst                     | 55 ++++++++++++
 tools/binman/etype/x509_cert.py             | 45 ++++++++--
 tools/binman/ftest.py                       | 92 +++++++++++++++++++++
 tools/binman/test/fit/openssl_provider.conf | 14 ++++
 5 files changed, 202 insertions(+), 5 deletions(-)
 create mode 100644 tools/binman/test/fit/openssl_provider.conf

-- 
2.34.1



More information about the U-Boot mailing list