LMS support planning#2707
Draft
d3zd3z wants to merge 16 commits intomcu-tools:mainfrom
Draft
Conversation
7b4d819 to
fd19a64
Compare
cryptography 47.0.0 (released April 2026) bumps its PyO3 minimum
supported PyPy version to 3.11. Pip-installing imgtool's deps on
PyPy 3.9 or 3.10 now fails during the cryptography wheel build
with
error: the configured PyPy interpreter version (3.10) is lower
than PyO3's minimum supported version (3.11)
so every PR run against this workflow fails on those two matrix
entries. The matrix that landed in 95a6e38 ("imgtool: python
version coverage") was green at the time because it ran against
cryptography 46.x; the next push exposed the issue.
Drop pypy3.9 and pypy3.10 from the matrix, keep pypy3.11. CPython
coverage is unchanged.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
Rename the Mbed TLS submodule's path from ext/mbedtls to ext/mbedtls-3.6.0 to make room for a second submodule pinned at Mbed TLS 4.1.0, which will be added in a follow-up. This clears the way to build and test MCUboot's PSA-based crypto paths against the Mbed TLS 4.x release series alongside the current 3.6 LTS. The dual-submodule arrangement lets the simulator's legacy-crypto feature paths (sig-rsa, sig-ecdsa-mbedtls, enc-rsa, enc-ec256-mbedtls, enc-x25519-mbedtls) continue using the 3.6 LTS series while new and PSA-based code migrates to 4.1 incrementally. Mbed TLS 4.x relocates the legacy crypto API to a private/ header tier and splits sources across a new tf-psa-crypto submodule, so a straight in-place bump would force all feature paths to be reworked at once. No content change for the submodule itself; it stays at v3.6.0 (2ca6c285a0). Updated: - .gitmodules path entry - .mbedignore - sim/mcuboot-sys/build.rs (all 148 source/include paths) - boot/espressif/CMakeLists.txt and crypto_config/rsa.cmake - docs/readme-espressif.md (instructions for submodule init) No changes required in boot/zephyr/, which uses Zephyr's own mbedtls module rather than this submodule. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
Add Mbed TLS v4.1.0 alongside the existing 3.6.0 submodule. Nothing
in the tree references this path yet; wiring up the simulator or
any OS port against 4.1 comes in follow-up commits as each feature
is migrated.
Mbed TLS 4.x restructures the repository into a top-level tree for
TLS/X.509/etc and a nested tf-psa-crypto submodule for crypto. A
shared framework/ submodule holds build infrastructure. After
cloning, run:
git submodule update --init --recursive ext/mbedtls-4.1.0
to fetch both nested submodules.
Pinned commit: 0fe989b6b5 (v4.1.0).
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
Drive the Mbed TLS 4.1 (TF-PSA-Crypto 1.1.0) build through its own
CMakeLists.txt instead of recompiling a hand-picked file list from
build.rs. This follows the same shift Zephyr made when it moved to
Mbed TLS 4.1: stop shadowing the upstream build and let upstream own
the (still-evolving) 4.x file layout, generator plumbing, and config-
adjustment logic. Our only inputs become the config header and a
handful of CMake -D knobs.
What changed:
- `sim/mcuboot-sys/Cargo.toml`: new `mbedtls-v4` feature and
`cmake = "0.1"` build-dep.
- `sim/Cargo.toml`: forwards the feature to `mcuboot-sys`.
- `sim/mcuboot-sys/csupport/config-ec-psa-v4.h`: minimal TF-PSA-Crypto
1.1.0 configuration (PSA_WANT_* macros); the 4.x build auto-derives
legacy MBEDTLS_*_C internals.
- `sim/mcuboot-sys/build.rs`: new `add_mbedtls_v4_psa_ecdsa()` branch
invokes the `cmake` crate on `ext/mbedtls-4.1.0/tf-psa-crypto` with
`TF_PSA_CRYPTO_CONFIG_FILE` + `ENABLE_{PROGRAMS,TESTING}=OFF`,
builds the `tfpsacrypto` target, and links the resulting static
library. The boot-code cc::Build gets the matching include paths
and `-DTF_PSA_CRYPTO_CONFIG_FILE` so public headers see the same
config.
- `sim/mcuboot-sys/csupport/psa_rng_stub_v4.c`: self-contained stub
for `mbedtls_psa_external_get_random` + the test-RNG enable/disable
toggles. The equivalent upstream test shim drags in the whole
test-framework header tree; verification-only tests only need a
symbol that resolves and returns bytes.
- `scripts/requirements.txt`: `jinja2`, `jsonschema` — the 4.x
CMake's GEN_FILES path invokes Python generators for
`psa_crypto_driver_wrappers*` and `tf_psa_crypto_config_check_*.h`.
Tested (arm64 macOS):
- `MCUBOOT_SKIP_SLOW_TESTS=1 cargo test --features \
sig-ecdsa-psa,mbedtls-v4 -- --test-threads=1` → 25/25.
- `cargo build --features sig-ecdsa-psa` (no mbedtls-v4) still
builds against 3.6.0 unchanged.
Developers need `cmake` in PATH and `jinja2`+`jsonschema` available
to the Python the build invokes.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
Add three new matrix rows exercising the `mbedtls-v4` Cargo feature across orthogonal feature combinations: 1. Mirror of the existing sig-ecdsa-psa row with `mbedtls-v4` appended (base, sig-p384, swap-move+bootstrap+max-align-16). The CMake-driven 4.x build is expected to match the 3.6 path here since the crypto config is the same shape. 2. Orthogonal features untested on the 3.6 sig-ecdsa-psa path (swap-offset, validate-primary-slot, overwrite-only, multiimage). These don't touch the crypto surface, so they ought to work on 4.1 too; good shakedown of the CMake build surface. 3. Reset-resilience / XIP / rollback combinations (ram-load, direct-xip, overwrite-only+downgrade-prevention, hw-rollback-protection+multiimage). Verified locally with `ptest -t 63..=73 run`: 11/11 pass. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
Three carry patches to make boot/bootutil/src/encrypted_psa.c compile
under Mbed TLS 4.1 with the simulator's -Werror -Wall -Wextra:
- Define MBEDTLS_OID_EC_ALG_UNRESTRICTED / _EC_GRP_SECP256R1 locally
when unavailable. In 4.x these moved from the public
`mbedtls/oid.h` to a private header
(`tf-psa-crypto/utilities/crypto_oid.h`); supply the raw OID bytes
rather than depend on a private include path.
- Cast the "MCUBoot_ECIES_v1" literal to `const uint8_t *` when
handed to psa_key_derivation_input_bytes, silencing
-Wpointer-sign.
- Mark `blk_off` as intentionally unused in bootutil_aes_ctr_{encrypt,
decrypt}; the PSA cipher API handles CTR block alignment
internally. Silences -Wunused-parameter.
Temporary local carries. File was originally written against Mbed TLS
3.x PSA mode; these should flow back upstream as a proper 4.x
compatibility fix, at which point this commit can be dropped.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
…s-v4
On the 3.6 path, `sig-ecdsa-psa enc-ec256` falls into the
`psa_crypto_init_stub.c` shim: PSA init is a no-op and the actual
ECIES work runs on TinyCrypt. On the mbedtls-v4 path we can do
better — the tfpsacrypto library is already being linked for the
signature side, so route image encryption through it too.
What changed:
- `sim/mcuboot-sys/csupport/config-ec-psa-v4.h`: new
`#if defined(MCUBOOT_ENCRYPT_EC256)` block enabling PSA_WANT_*
entries for ECDH, HKDF, CTR, HMAC, AES, HMAC keys, and
ECC_KEY_PAIR_{DERIVE,IMPORT}. ECC curve (SECP_R1_256) is already on
unconditionally for the signature path.
- `sim/mcuboot-sys/build.rs`:
- `add_mbedtls_v4_psa_ecdsa()` now takes `enc_ec256` and forwards
`-DMCUBOOT_ENCRYPT_EC256` to the CMake build so the library and
boot code evaluate `#if defined(...)` identically.
- New `enc_ec256 && mbedtls_v4` branch: pulls in both
`encrypted.c` (high-level `boot_enc_*` interface) and
`encrypted_psa.c` (PSA primitives under `MCUBOOT_USE_PSA_CRYPTO`),
does not define `MCUBOOT_USE_TINYCRYPT` (incompatible with
`MCUBOOT_USE_PSA_CRYPTO`), and defines `CONFIG_BOOT_ECDSA_PSA`
to gate out `encrypted.c`'s duplicated legacy ASN.1 + ECDH
block (which references `MBEDTLS_OID_*` macros no longer public
in 4.x).
- Skip `MBEDTLS_CONFIG_FILE=<config-asn1.h>` when `mbedtls-v4`
is set — 4.x uses `TF_PSA_CRYPTO_CONFIG_FILE` instead.
- `.github/workflows/sim.yaml`: new matrix row mirroring the 3.6
enc-ec256 entries, now exercising real PSA.
Tested: `MCUBOOT_SKIP_SLOW_TESTS=1 cargo test --features \
sig-ecdsa-psa,enc-ec256,mbedtls-v4 --test core -- \
--test-threads=1` → 25/25. Pre-existing 3.6 combinations (with and
without mbedtls-v4) unchanged.
Depends on boot/encrypted_psa local-carry patch (previous commit)
for the file to compile cleanly under 4.x + -Werror.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
enc-aes256-ec256 shares the ECIES-P256 machinery with enc-ec256 and differs only in BOOT_ENC_KEY_SIZE (32 bytes vs. 16), gated by MCUBOOT_AES_256. PSA_KEY_TYPE_AES covers all AES key sizes, so no additional PSA_WANT_* entries are required — this is purely a build.rs branch-extension plus a matrix row. Changes: - build.rs: the `enc_ec256 && mbedtls_v4` branch now also matches `enc_aes256_ec256 && mbedtls_v4`, defining MCUBOOT_AES_256 when the latter is selected. add_mbedtls_v4_psa_ecdsa() is told enc-ec256-equivalent encryption is active via `enc_ec256 || enc_aes256_ec256`, so it forwards `-DMCUBOOT_ENCRYPT_EC256` to CMake. - build.rs: guard the `config-ec.h` MBEDTLS_CONFIG_FILE selection with `!mbedtls_v4` so enc-aes256-ec256+mbedtls-v4 doesn't pick up the 3.6-style config header. - .github/workflows/sim.yaml: mirror of the 3.6 enc-aes256-ec256 entries, routed through the PSA path. Tested (arm64 macOS): - `cargo test --features sig-ecdsa-psa,enc-aes256-ec256,mbedtls-v4` → 25/25. - ptest -t 74..77 (enc-ec256 + enc-aes256-ec256 mbedtls-v4 entries) → 4/4. - Pre-existing 3.6 entries 19, 20, 50 (enc-aes256-ec256 on 3.6) unchanged. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
The ptest harness writes per-test success/failure logs into the ptest/ directory when run locally. Exclude them from version control so stray logs don't clutter working trees. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
Clang 19 (shipping in Xcode 16) introduced
-Wdefault-const-init-var-unsafe, which with MCUboot's -Werror
simulator build rejects the const VLA in tc_hmac_set_key():
const uint8_t dummy_key[key_size];
The buffer is a timing-equalisation device whose contents are fed
to tc_sha256_update() and then discarded. Since the enclosing branch
gates on key_size <= TC_SHA256_BLOCK_SIZE, replace the stack VLA
with a fixed-size zero-filled static const. tc_sha256_update() is
still passed key_size explicitly, so it reads the same number of
bytes as before:
static const uint8_t dummy_key[TC_SHA256_BLOCK_SIZE] = { 0 };
This moves the dummy from stack to .rodata, which differs subtly in
cache/timing characteristics from the original stack VLA (which,
despite the const qualifier, lived on the stack). The impact on the
timing-equalisation intent is likely small but non-zero; tinycrypt
is not a timing-resistant library in general so this is mostly
academic.
Temporary local workaround. tinycrypt is vendored source with no
upstream maintenance; revisit when tinycrypt is retired from MCUboot.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
Reserve TLV type 0x26 for the LMS (Leighton-Micali Signatures, RFC 8554) signature carried in the image trailer, add it to the unprotected-TLV allow list in image_validate.c, and mirror the definition into docs/design.md with a short subsection describing the wire formats. The public key (56 bytes) and signature (1452 bytes for the LMS_SHA256_M32_H10 + LMOTS_SHA256_N32_W8 parameter set that Mbed TLS 4.x supports) are defined by RFC 8554 rather than by MCUboot; their sizes are a property of the chosen parameter set, not a design choice. docs/design.md now spells out the u32 lms_type | u32 lmots_type | 16-byte I | 32-byte T[1] layout of the public key so a reader does not have to consult the RFC to explain where the 56 comes from. No new public-key TLV is needed: the existing IMAGE_TLV_PUBKEY / IMAGE_TLV_KEYHASH pair carries the LMS public key or its SHA-256, unchanged. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
Wrap pyhsslms (RFC 8554, BSD-3-Clause) so imgtool can generate LMS
keypairs, sign image digests, and verify the resulting signatures.
The bootloader-side verifier (Mbed TLS 4.x) currently supports only
LMS_SHA256_M32_H10 + LMOTS_SHA256_N32_W8, but the wrapper carries
the (lms_type, lmots_type) tuple through so a new entry in
LMS_PARAM_SETS is the only change needed to surface another variant
once the verifier supports it. Two are exposed initially:
lms-sha256-h10-w8 (matches the Mbed TLS set) and lms-sha256-h5-w8
(faster, useful for round-trip tests).
The key file is a custom PEM-style envelope ("BEGIN MCUBOOT LMS
PRIVATE KEY") holding the 60-byte serialized state
lms_type || lmots_type || SEED || I || q. keys/__init__.py:load()
sniffs the marker before falling through to the cryptography PEM
loader, so existing key types are unaffected.
LMS is built on a one-time signature scheme: the leaf index q must
never be reused. sign_digest() advances q in memory, then
atomically rewrites the key file (tmpfile + fsync + rename) before
returning the signature. If the disk write fails, no signature is
produced; if the caller crashes after the write but before
persisting the signed image, an LMS index is wasted but never
reused. The hazard is documented in the lms.py module docstring.
The public key is exported in the 56-byte RFC 8554 form
(u32 lms_type | u32 lmots_type | 16-byte I | 32-byte T[1]) and fed
unchanged into IMAGE_TLV_PUBKEY / IMAGE_TLV_KEYHASH as allocated in
the previous commit. image.py registers TLV_VALUES['LMS'] = 0x26
and ALLOWED_KEY_SHA[LMS] = ['256']; the LMS wrapper deliberately
omits a `verify` method so image.py's verify path picks up
`verify_digest` automatically.
Tests parameterize over keygens.keys(), so the new variants pass
test_keygen, test_getpub, test_getpubhash, and test_sign_verify
out of the box. test_keygen_type (uses `openssl pkey`) and
test_getpriv (PKCS8/openssl formats) skip for LMS since LMS keys
are not PEM/PKCS8.
Timing on CPython 3.13: h=10 keygen 3.9 s, load 3.9 s, sign and
verify ~2 ms each post-load; h=5 keygen and load 0.12 s each. Each
`imgtool sign` pays the load cost because pyhsslms only persists
the 60-byte state, not the 1024-leaf precomputed Merkle tree.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
Upcoming LMS signature support adds an unconditional `import pyhsslms` to imgtool's key-loading path. The FIH workflow bind-mounts the mcuboot tree into the mcuboot-fih-test container and runs the in-repo scripts/imgtool.py to derive the bl2 signing public key, so every imgtool invocation in that workflow will fail with ModuleNotFoundError: No module named 'pyhsslms' once the LMS commits land, unless the container has pyhsslms available first. Add pyhsslms to the pip install list in the Dockerfile and bump FIH_IMAGE_VERSION 0.0.3 -> 0.0.4 so the workflow pulls a freshly built image. The new tag has to be built and pushed to ghcr.io/mcu-tools/mcuboot-fih-test:0.0.4 out-of-band by someone with mcu-tools org publish access. This commit (and the corresponding image push) should land before the imgtool LMS support commits, so the FIH workflow stays green throughout. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
Print human-readable information about a key file. For most key
types this is just the signature type (e.g. "PKCS1_PSS_RSA2048_SHA256"
or "ED25519"). For LMS keys it adds the parameter set name and the
maximum signature count, and for an LMS private key the count of
signatures used so far — useful given the stateful-private-key
hazard.
Implementation: a default key_info() on KeyClass returns a single
("Key type", sig_type()) row; LMS overrides to surface its
parameter set, with the private-key class extending the public's
output to include the q-vs-2^h usage. The CLI command formats the
returned (label, value) pairs with right-aligned labels.
Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: David Brown <david.brown@linaro.org>
Add the lms-signature crate to bootsim's regular [dependencies] and introduce a Rust integration test that round-trips LMS keys and signatures with imgtool's pyhsslms wrapper. Two test directions, neither using committed key material: - python_signs_rust_verifies — Python (imgtool.keys.lms) generates a fresh keypair and signs; Rust parses the 56-byte public key and the RFC 8554 signature with VerifyingKey::try_from / Signature::try_from and verifies. - rust_signs_python_verifies — Rust generates a SigningKey<...>::new, signs, and hands the pubkey + signature + message to a pyhsslms subprocess that calls LmsPublicKey.deserialize(...).verify(...). The test uses LMS_SHA256_M32_H5 / LMOTS_SHA256_N32_W8 for keygen speed (~120 ms). Both pyhsslms and lms-signature support it. When bootloader-side verification (Mbed TLS 4.x) lands, the H10/W8 set the boot path actually verifies can be added in parallel. lms-signature lives in [dependencies] rather than [dev-dependencies] because TlvGen will sign LMS test images at sim runtime once the sig-lms path is wired up — sim does not call out to imgtool. The new dep set (lms-signature, getrandom, rand_core, hybrid-array, signature) will be reused there. Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Brown <david.brown@linaro.org>
Ongoing planning document tracking LMS support and the Mbed TLS 4.x PSA porting effort. Updated in place as work progresses; will be converted into proper documentation once the implementation lands. Signed-off-by: David Brown <david.brown@linaro.org>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds a plan file for tracking the implementation of LMS within MCUboot.