Skip to content

Building CRA-ready STM32H5 controllers

Secure boot and signed firmware updates, end to end, on an STM32H5. OEMiRoT, ST's immutable first stage built from MCUboot, verifies the secure and the non-secure application image, and every signing key stays inside the HSM.

The Cyber Resilience Act's Annex I requires that a product protects the integrity of its software and that security updates can be installed securely. On an STM32H5 both come down to the same discipline: each application image is signed, the bootloader that verifies it cannot be changed, and the keys it verifies against are provisioned once into the chip.

How to read this

This is an example configuration, not a design guide. It shows which platform operations sign the images of ST's own reference setup (STM32CubeH5, the OEMiRoT projects for the STM32H573I-DK) and what the device verifies, so that a manufacturer with a design of its own can see which calls fulfil which part.

Secure boot across the STM32 range, and why the H5

ST's families differ in what they offer as a first stage, and that decides what gets signed and how. The older families have software-only bootloaders; the current ones carry a root of trust in silicon or in locked flash. The same two operations serve the other families where the image is an MCUboot image.

Families First stage Images signed with
F0 to F7, G0/G4, L0 to L4, WB None in silicon. A software bootloader in flash (ST's legacy SBSFU, or MCUboot), protected only by readout protection SignMcuBoot for MCUboot
L5, U5, WBA MCUboot (TF-M's BL2), locked by a boot lock SignMcuBoot
H5 (OEMiRoT also on H7RS, U0, U3, C5) OEMiRoT: MCUboot in user flash, made immutable by the option bytes, keys in the chip. On the parts with the cryptographic accelerator, such as the H573, also STiRoT, ST's own immutable code SignMcuBoot (OEMiRoT); DigestSigning (STiRoT)
N6 Boot ROM with its own first-stage format, MCUboot above it DigestSigning, then SignMcuBoot
MP1, MP2 (Linux) Boot ROM with a key hash in OTP, then TF-A, OP-TEE, U-Boot Not covered by this guide

This guide is the highlighted row. The H5 is its example because it is where ST's current designs go: every OEM-owned stack ST ships for it is MCUboot-based with ECDSA P-256, the keys live in the chip rather than in flash, and the image format is one the platform already signs for other vendors.

Secure boot on the STM32H5: ST's two first stages

The STM32H5 has no public-key secure boot in its ROM. ST ships two ways to anchor the chain instead, and a product picks one of them at design time. They differ in who owns the first stage and in which image format the first stage verifies; that decides which LAAVAT operation signs.

Path What boots what When a product picks it LAAVAT operation
OEMiRoT alone OEMiRoT (MCUboot, OEM keys, made immutable by the option bytes) → secure application → non-secure application The OEM owns the bootloader and the keys; exists on every H5 part; images are plain MCUboot. The bootloader itself is not updatable in the field SignMcuBoot
STiRoT STiRoT (ST's own immutable code in system flash, on the parts with the cryptographic accelerator) → up to two application images in STiRoT's format, signed and encrypted. ST also allows an updatable OEM bootloader in between (OEMuRoT, which is the OEMiRoT build in a second configuration), for OEMs that need their own boot logic and must be able to update it No OEM boot code, certified by ST; the path behind Secure Manager on the H573. Equally normal; the OEM owns only the keys DigestSigning for the STiRoT-format image; SignMcuBoot for anything an OEMuRoT verifies

In both, exactly one MCUboot is involved: OEMiRoT, or its OEMuRoT configuration under STiRoT.

This guide shows OEMiRoT, for three reasons: it exists on every H5 part, its images are in MCUboot's plain image format, which SignMcuBoot signs directly, and the OEM owns the bootloader as well as the keys. The STiRoT path is just as normal on the H573; its images need the two-step with DigestSigning — see digest signing. On the older STM32 families without TrustZone (F4, L4 and similar) the application image is the same and is signed with the same operation, but the verifying key sits in flash behind readout protection, which is not a root of trust.

The chain

One bootloader and two application images. Trust starts in OEMiRoT, which the option bytes make immutable; it verifies both images, then starts the secure one, which starts the non-secure one. The two images are the two halves of a TrustZone application, not two stages: ST's reference build splits the application into a secure side and a non-secure side, each with its own key. One ECDSA P-256 key per image, both on the platform.

graph TD
    O["<b>OEMiRoT</b><br/>stage 1 · locked flash<br/>MCUboot, keys in OBKeys"]
    S["<b>Secure application</b><br/>stage 2a<br/>secure world"]
    N["<b>Non-secure application</b><br/>stage 2b<br/>non-secure world"]
    O -->|"verifies with the S key"| S
    O -->|"verifies with the NS key"| N
    S -.->|starts| N

OEMiRoT verifies both images before anything runs; the secure side then hands control to the non-secure side. The two public keys go into the chip as OBKeys at the factory; the debug-authentication secret stays with the OEM — see What goes on the device. The images in this example are signed, not encrypted.

The two images can also be one

ST's "full secure" configuration of the same project builds the application as a single image verified with a single key, with or without TrustZone. The product then has one operation instead of two; everything else on this page is unchanged.

Link The device verifies with LAAVAT signs with Where the public half goes
OEMiRoT → secure application ECDSA P-256 against the secure authentication key in OBKeys; SHA-256 of the image; security counter SignMcuBoot, secure application operation OBKeys, at the factory
OEMiRoT → non-secure application The same, against the non-secure authentication key SignMcuBoot, non-secure application operation OBKeys, at the factory
Field update The new image in the download slot, verified the same way before it replaces the old one; a lower security counter is refused The same two operations Already on the device

Core property — HSM-resident keys

The two ECDSA P-256 private keys that anchor this chain live in the HSM and never leave it. What the device holds is the matching pair of public keys, written into the OBKeys once, at the factory. OEMiRoT compares the hash of the key named in each image with what it holds, then verifies the signature. LAAVAT is not in the loop at boot or at update time. See key custody.

The LAAVAT product

One product, two SignMcuBoot operations, one ECDSA P-256 key each. The platform runs imgtool itself with the HSM-held key; the build host sends the unsigned image and the parameters.

Example product definition

{
  "name": "STM32H573 controller",
  "description": "STM32H573 controller: OEMiRoT verifies the secure and non-secure application images",
  "productType": "Production",
  "enabled": true,
  "caInfo": [], "rndKeys": [],
  "productOperations": [
    { "name": "STM32H5 secure application signing",
      "description": "Sign the OEMiRoT secure application image",
      "operationType": "SignMcuBoot",
      "token": { "name": "STM32H5 S app signing key", "description": "ECDSA P-256, secure image",
                 "keyType": "ECDSAP256" },
      "approvalRule": { "name": "Release", "description": "Unattended release signing",
                        "allowedGroups": ["<release-group-uuid>"],
                        "approvalGroups": [], "blanketGroups": ["<release-group-uuid>"] } },
    { "name": "STM32H5 non-secure application signing",
      "description": "Sign the OEMiRoT non-secure application image",
      "operationType": "SignMcuBoot",
      "token": { "name": "STM32H5 NS app signing key", "description": "ECDSA P-256, non-secure image",
                 "keyType": "ECDSAP256" },
      "approvalRule": { "name": "Release", "description": "Unattended release signing",
                        "allowedGroups": ["<release-group-uuid>"],
                        "approvalGroups": [], "blanketGroups": ["<release-group-uuid>"] } }
  ]
}
signing-tool -n test.ini product add -T stm32h573-controller.json
# approve it in the console, then fetch each operation's public key (one call per operation); the factory provisions them
signing-tool -n test.ini product getpubkey -P <product-uuid> --operid <s-app-operation-uuid>  -O OEMiRoT_Authentication_S.pem
signing-tool -n test.ini product getpubkey -P <product-uuid> --operid <ns-app-operation-uuid> -O OEMiRoT_Authentication_NS.pem

Both operations are blanket-approved for the release group in this example, so the release pipeline runs unattended; an approval group on one or both is a one-line change in the template. The config file test.ini is set up once, as described in providing the token.

Signing the release artifacts

Secure and non-secure application images — SignMcuBoot

ST's project builds two binaries: rot_tz_s_app.bin (the secure side) and rot_tz_ns_app.bin (the non-secure side). Each is signed with its own operation, so a release is two signing requests. The steps below show the secure image; the non-secure image goes through the same three steps with slot_size 0xA0000 and the non-secure operation.

The imgtool parameters in the request are ST's own: they are the values in ROT_Provisioning/OEMiROT/Images/OEMiROT_S_Code_Image.xml, the file ST's signing tool (Trusted Package Creator, part of STM32CubeProgrammer) reads when it signs the same image with a local key. Every field is described in MCUboot signing.

1. Package what imgtool needs. A .tgz with the unsigned image and a request.json carrying the imgtool parameters:

{ "images": [ {
    "filename": "rot_tz_s_app.bin", "output": "rot_tz_s_app_sign.bin",
    "align": "16", "version": "1.0.0", "header_size": "0x400", "slot_size": "0x6000",
    "pad_header": true, "pad": true, "confirm": false, "overwrite_only": true,
    "max_sectors": "0", "max_align": "8",
    "security_counter": "auto", "public_key_format": "hash" } ] }
tar -czf s-app.tar.gz request.json rot_tz_s_app.bin

2. Submit to the MCUboot operation. The platform signs asynchronously: the request is created and becomes Ready a few seconds later with blanket approval.

signing-tool -n test.ini imagesigning add MCUBoot -P <product-uuid> --operid <s-app-operation-uuid> \
    -N "controller 1.0.0 secure app" -D "OEMiRoT secure image" -F s-app.tar.gz
Request sent. Request ID: <request-uuid>, state: Created

3. Download and extract the signed image. --wait waits until the request is Ready, then downloads it.

signing-tool -n test.ini imagesigning get -I <request-uuid> --wait --skipBase64 -O s-app-signed.tar.gz
tar -xzf s-app-signed.tar.gz                                                # rot_tz_s_app_sign.bin

Then the same for rot_tz_ns_app.bin with the non-secure operation. The two signed images are what the factory flashes first and what every update ships. Delivering an update to the device and writing it into the download slot is the manufacturer's update mechanism and is not covered here.

Before shipping, check the result as described under Verifying before shipping; the byte comparison against ST's own tool is the check that matters.

Two values to get right

Field Value Reason
public_key_format "hash" OEMiRoT compares the hash of the key in the image (KEYHASH) with the OBKeys. The platform's default is full, which embeds the whole public key, so the field has to be set
max_align "8" with align "16" ST's MCUboot fork compares the image trailer against the classic MCUboot magic, although the H5 flash writes in 16-byte words. Upstream imgtool, which the platform runs, switches to a newer trailer magic whenever align is 16 unless max_align is 8. The image verifies with imgtool either way; OEMiRoT only accepts the classic magic. ST's own tool does not need this field

Every other value is ST's. confirm: false is in the request because ST does not confirm images.

What goes on the device

At the factory, the two public keys are written into the chip's option-byte keys (OBKeys). OEMiRoT reads them from there at every boot; nothing else on the device holds key material for the signatures. The private keys stay in the HSM.

The OBKeys are written as a file that ST's Trusted Package Creator builds from OEMiRoT_Config.xml. The XML names the key files; for the two signing keys those are the public keys fetched with product getpubkey above. They reach the factory as PEM files; they are public, so what matters is only that the factory has the right ones, which the key hashes printed by product getpubkey let it confirm.

STM32TrustedPackageCreator_CLI -obk ROT_Provisioning/OEMiROT/Config/OEMiRoT_Config.xml
# -> Binary/OEMiRoT_Config.obk, written into the chip with STM32CubeProgrammer during provisioning
In the OBKeys Comes from Who keeps the secret
Secure and non-secure signing public keys The platform (product getpubkey) The HSM holds the private keys
OEM encryption key The OEM; ST's provisioning file always has this slot. Unused in this example, which does not encrypt images The OEM
Debug-authentication password or key The OEM, through Trusted Package Creator The OEM; without it, a Closed chip cannot be reopened for debugging

Only the first row comes from the platform.

After provisioning, the product state is set to Closed, which locks the option bytes and the OBKeys. Closed can be reverted only through debug authentication with the OEM's secret, and the reversal erases the chip; Locked, the final state, is permanent.

Verifying before shipping

With ST's imgtool and the upstream imgtool

Two tools, the same check. imgtool is the upstream MCUboot tool (pip install imgtool). stm32-mw-mcuboot is ST's fork of MCUboot, the code OEMiRoT is built from; it is on GitHub and inside STM32CubeH5 under Middlewares/Third_Party/mcuboot, and its scripts/imgtool.py is the imgtool Trusted Package Creator runs. Both verify the signed image against the operation's public key:

imgtool verify -k OEMiRoT_Authentication_S.pem rot_tz_s_app_sign.bin
python3 stm32-mw-mcuboot/scripts/imgtool.py verify -k OEMiRoT_Authentication_S.pem rot_tz_s_app_sign.bin
python3 stm32-mw-mcuboot/scripts/imgtool.py dumpinfo rot_tz_s_app_sign.bin   # header, TLVs, trailer

Against the image ST's own tool produces

Sign the same binary once with ST's imgtool and a local test key, using the parameters from ST's XML, and compare. The two files must be identical except for the key hash and the signature bytes: same header fields, same payload, same security counter, same TLV sequence (SHA256, KEYHASH, ECDSASIG), same digest, same padding and trailer. That comparison is what caught the max_align difference; a signature check alone would not have.

python3 stm32-mw-mcuboot/scripts/imgtool.py sign -k test_key.pem -e little --pad -S 0x6000 -H 0x400 \
    --pad-header --overwrite-only -v 1.0.0 -s auto --align 16 -M 0 rot_tz_s_app.bin reference.bin
cmp -l reference.bin rot_tz_s_app_sign.bin | wc -l            # differences only inside KEYHASH and ECDSASIG

Where off-hardware verification stops

Worth stating plainly, because a passing test suite is easy to over-read:

Claim Off-hardware What would close the gap
Both image signatures are cryptographically correct for OEMiRoT Checked on the build host: imgtool verify (upstream and ST's fork) accepts both images with the operations' public keys —
The images are in the format OEMiRoT expects Checked on the build host: byte-identical to ST's own tool output except key hash and signature —
A tampered image or a wrong key is rejected Checked on the build host: both imgtools reject a flipped payload byte, a flipped TLV byte and the other operation's key —
The OBKeys file carries the platform's public keys Checked on the build host: the file Trusted Package Creator produces contains both public keys —
The provisioning flow puts the keys in the chip and closes it Not tested Provisioning on an STM32H573I-DK
OEMiRoT boots the images, installs an update, refuses a rollback Not tested Boot on an STM32H573I-DK

Device-side requirements: STM32CubeH5 with its OEMiRoT projects, ST's MCUboot fork (stm32-mw-mcuboot) for its imgtool, and STM32CubeProgrammer, which contains Trusted Package Creator. The two application binaries used here were built from ST's STM32CubeIDE project on the command line with the GNU Arm toolchain; the IDE itself is not needed for signing.

Mapping to the CRA

Annex I requirement Covered by
Integrity of the software and its configuration is protected; unauthorised modification is detected OEMiRoT verifies both images with ECDSA P-256 against keys in OBKeys; the product state locks the option bytes
Security updates: the product can be updated MCUboot download slots with a security counter against rollback; each release signed with a recorded, group-restricted operation
Confidentiality of data and code where the risk assessment requires it Out of scope: this example signs plain images. ST's optional image encryption is not produced by this flow
Access control for privileged functions Debug authentication; signing restricted to groups; every signature on record

Building a device this way does not by itself make a product CRA compliant. The Annex I mapping sets out which requirements the platform supports and which remain yours.

References