> Markdown version of https://docs.laavat.io/solutions/cra-ready-stm32h5/ from the LAAVAT PKI and Signing Platform documentation. All pages: https://docs.laavat.io/llms.txt

# 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.

> **Note: 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](https://docs.laavat.io/exampleflows/detachedsigning/). 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.

```mermaid
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](https://docs.laavat.io/solutions/cra-ready-stm32h5/#what-goes-on-the-device). The images in this example
are signed, not encrypted.

> **Tip: 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.

### LAAVAT's part in each link

| 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 |

> **Abstract: 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](https://docs.laavat.io/security/#key-custody-and-export).

## 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

```json
{
  "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>"] } }
  ]
}
```

```bash
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](https://docs.laavat.io/democlient/usage/#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](https://docs.laavat.io/usage/signing-encryption/mcuboot-signing/).

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

```json
{ "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" } ] }
```

```bash
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.

```bash
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.

```bash
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](https://docs.laavat.io/solutions/cra-ready-stm32h5/#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.

```bash
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:

```bash
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.

```bash
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](https://docs.laavat.io/cra-compliance/annex-i-mapping/) sets out which
requirements the platform supports and which remain yours.

## Related

- [Building CRA-ready nRF devices](https://docs.laavat.io/solutions/cra-ready-nrf/) — the same `SignMcuBoot`
  flow on a Nordic MCUboot chain
- [MCUboot signing](https://docs.laavat.io/usage/signing-encryption/mcuboot-signing/) — every
  `request.json` field
- [MCUboot example flow](https://docs.laavat.io/exampleflows/mcubootsigning/) — a single-image
  walkthrough, from product creation to verification
- [Digest signing with detached signature](https://docs.laavat.io/exampleflows/detachedsigning/)
  — signs a digest computed elsewhere, as the STiRoT path needs; not used in
  this example
- [Trusted device identities](https://docs.laavat.io/solutions/trusted-device-identities/) — if the
  controller also needs a device certificate
- [Security and architecture](https://docs.laavat.io/security/) — where the keys live and
  who can reach them

## References

- [ST wiki: Secure Boot for STM32H5](https://wiki.st.com/stm32mcu/wiki/Security:Secure_Boot_for_STM32H5)
  — the first stages and the option bytes that lock them
- [ST wiki: OEMiRoT and OEMuRoT for STM32H5](https://wiki.st.com/stm32mcu/wiki/Security:OEMiRoT_OEMuRoT_for_STM32H5)
- [STM32CubeH5](https://github.com/STMicroelectronics/STM32CubeH5) — the
  OEMiRoT projects and `ROT_Provisioning`; and
  [stm32-mw-mcuboot](https://github.com/STMicroelectronics/stm32-mw-mcuboot)
  — ST's MCUboot fork and its imgtool
- [MCUboot design](https://docs.mcuboot.com/design.html) — image format, TLVs,
  trailer and security counter
- [UM2238: STM32 Trusted Package Creator](https://www.st.com/resource/en/user_manual/um2238-stm32-trusted-package-creator-tool-software-description-stmicroelectronics.pdf)
  — OBKeys and debug-authentication file generation
