Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,11 @@ endif

ifeq ($(TARGET),stm32h5)
# Don't build a contiguous image
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
ifeq ($(WOLFBOOT_SECURE_APP),1)
MAIN_TARGET:=wolfboot.bin
else
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
endif
endif

ifeq ($(TARGET),stm32n6)
Expand Down
36 changes: 36 additions & 0 deletions config/examples/stm32h5-tz-wolftrust.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
ARCH?=ARM
TZEN?=1
TARGET?=stm32h5
SIGN?=ECC256
HASH?=SHA256
DEBUG?=0
VTOR?=1
CORTEX_M0?=0
CORTEX_M33?=1
NO_ASM?=0
NO_MPU=1
EXT_FLASH?=0
SPI_FLASH?=0
ALLOW_DOWNGRADE?=0
NVM_FLASH_WRITEONCE?=1
WOLFBOOT_VERSION?=1
SPMATH?=1
RAM_CODE?=1
DUALBANK_SWAP?=0
WOLFBOOT_SECURE_APP?=1
WOLFBOOT_SECURE_HANDOFF_ADDRESS?=0x30020000
WOLFBOOT_PARTITION_SIZE?=0x20000
WOLFBOOT_SECTOR_SIZE?=0x2000
WOLFBOOT_KEYVAULT_ADDRESS?=0x0C05C000
WOLFBOOT_KEYVAULT_SIZE?=0
WOLFBOOT_NSC_ADDRESS?=0x0C05C000
WOLFBOOT_NSC_SIZE?=0x4000
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x0C060000
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x0C100000
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x0C120000
FLAGS_HOME=0
DISABLE_BACKUP=0
WOLFCRYPT_TZ=1
WOLFCRYPT_TZ_PKCS11=0
IMAGE_HEADER_SIZE?=1024
ARMORED=1
97 changes: 97 additions & 0 deletions docs/secure_app_handoff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Secure Application Handoff

`WOLFBOOT_SECURE_APP` tells wolfBoot that the image it boots is a signed
**Secure-state** runtime (for example a TrustZone-M Secure application such as
wolfTrust) rather than a Non-secure application. wolfBoot authenticates and
measures the image, publishes a measured-boot record at a fixed secure-RAM
address, and then branches to the image's reset vector **without leaving Secure
state**.

This is the wolfBoot analog of MCUboot's shared-data area consumed by TF-M: the
first stage measures what it launched and hands the measurement to the runtime.

## Default TrustZone boot vs. the secure-app handoff

| | Default TZ boot | `WOLFBOOT_SECURE_APP` |
| --- | --- | --- |
| Target state | Non-secure | stays Secure |
| Stack / entry | `msp_ns`, `bxns` to the NS vector | `msp`, branch to the Secure reset vector |
| Record | none | measured-boot record written before the jump |

The Secure jump lives in `src/boot_arm.c` under `WOLFBOOT_SECURE_APP`: it turns
the wolfBoot MPU off, sets `MSP`, re-enables interrupts, and branches to the
image entry, leaving the runtime to install its own memory map from its
`Reset_Handler`.

## The measured-boot record

`include/wolfboot/secure_handoff.h` defines the record and a single builder,
`wolfBoot_secure_handoff_build()`. wolfBoot writes it from
`wolfBoot_prepare_secure_handoff()` in `src/update_flash.c` immediately before
`do_boot()`, and panics if the record cannot be built.

| Field | Type | Meaning |
| --- | --- | --- |
| `magic` | `uint32_t` | `0x5742484F`, written **last** as the valid flag |
| `magic_inverse` | `uint32_t` | `~magic`, torn-write guard |
| `version` | `uint16_t` | record layout version (`1`) |
| `size` | `uint16_t` | `sizeof(record)` (56) |
| `lifecycle` | `uint32_t` | PSA lifecycle from `hal_attestation_get_lifecycle()` |
| `image_version` | `uint32_t` | version of the booted image |
| `hash_algorithm` | `uint16_t` | `1` = SHA-256 |
| `measurement_size` | `uint16_t` | digest length (32) |
| `measurement[32]` | `uint8_t` | SHA-256 of the booted image |

The record lives at `WOLFBOOT_SECURE_HANDOFF_ADDRESS`, a secure-RAM address the
port reserves and the config supplies. The builder writes `magic_inverse` and
then `magic` last, fenced with `dmb`/`dsb`, so a consumer that observes `magic`
sees a complete record.

A consumer validates `magic` and `magic_inverse`, checks `version`/`size`, uses
the fields, and then clears the record.

## Enabling it

```
WOLFBOOT_SECURE_APP=1
WOLFBOOT_SECURE_HANDOFF_ADDRESS=0x30020000 # secure-RAM address of the record
```

The handoff requires authenticated boot: `SIGN=NONE`/`WOLFBOOT_NO_SIGN` and
`WOLFBOOT_SKIP_BOOT_VERIFY` are rejected at configure time, and the record
currently requires `HASH=SHA256`. Setting `WOLFBOOT_SECURE_APP` without
`WOLFBOOT_SECURE_HANDOFF_ADDRESS` is a compile-time error.

## Porting the handoff to a new target

The handoff mechanism (record, measurement, Secure jump, build knob) is
target-independent. A new port becomes a clean secure-runtime loader by
supplying three things:

1. **Config only** — `WOLFBOOT_SECURE_APP=1`, `WOLFBOOT_SECURE_HANDOFF_ADDRESS`,
and the usual `TZEN`/partition layout. No code.
2. **`hal_attestation_get_lifecycle()`** *(optional)* — returns the chip's PSA
lifecycle. A weak default in `hal/hal.c` reports "unknown", so a port links
and hands off without it; implement it for a real lifecycle value.
3. **TrustZone setup that keeps the secure app Secure across the jump** — the
port's own SAU / security-controller code must keep the secure app's flash
and RAM Secure and must not unsecure peripherals when `WOLFBOOT_SECURE_APP`
is set. Every SoC's security fabric differs (GTZC, AHBSC, TRDC, …), so this
is the one piece that is inherently per-port; it is the TrustZone init a port
already writes, guarded on `WOLFBOOT_SECURE_APP`.

## STM32H5 reference

`config/examples/stm32h5-tz-wolftrust.config` is the reference consumer. On the
STM32H5 the record lives at `0x30020000`, `hal_attestation_get_lifecycle()`
derives the PSA lifecycle from the flash product state and the debug
authentication status (`hal/stm32h5_lifecycle.h`), and `hal/stm32_tz.c` keeps
the whole SRAM1 window and the secure app flash Secure across the handoff.

## Tests

- `tools/unit-tests/unit-secure-handoff.c` — record layout, the builder, and the
STM32H5 lifecycle mapping.
- `tools/unit-tests/test-secure-handoff-config.sh` — the configure-time guards
(rejects `SIGN=NONE` and `WOLFBOOT_SKIP_BOOT_VERIFY`) and the STM32H5 stack
floors.
36 changes: 31 additions & 5 deletions hal/stm32_tz.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ static void RAMFUNCTION hal_flash_nonsecure_lock(void)

static int is_range_nonsecure(uint32_t address, int len)
{
#if defined(WOLFBOOT_SECURE_APP)
(void)address;
(void)len;
return 0;
#else
#ifndef DUALBANK_SWAP
/* The non secure area begins at the BOOT partition */
uint32_t min = WOLFBOOT_PARTITION_BOOT_ADDRESS;
Expand Down Expand Up @@ -89,6 +94,7 @@ static int is_range_nonsecure(uint32_t address, int len)
return 1;
return 0;
#endif
#endif /* WOLFBOOT_SECURE_APP */
}


Expand Down Expand Up @@ -215,11 +221,16 @@ void hal_gtzc_init(void)
* 0: Non-secure access only to block
*/

/* Configure SRAM1 as secure (Low 256 KB).
* wolfBoot links its own RAM/RAM_HEAP into the SRAM1 secure alias
* (0x30000000-0x3003FFFF, see hal/stm32h5.ld), so SRAM1 must stay
* secure for wolfBoot's .bss/stack/heap to remain accessible. */
/* Configure SRAM1 as secure. The secure application handoff enters with
* its MSP at 0x300A0000, so the whole 512-KiB SRAM1 window must stay
* Secure until the secure runtime installs its own memory split. */
#if defined(WOLFBOOT_SECURE_APP)
for (i = 0; i < 32; i++) {
#else
/* wolfBoot links its own RAM/RAM_HEAP into the lower SRAM1 secure alias
* (0x30000000-0x3003FFFF, see hal/stm32h5.ld). */
for (i = 0; i < 16; i++) {
#endif
SET_GTZC1_MPCBBx_SECCFGR_VCTR(1, i, 0xFFFFFFFF);
}

Expand All @@ -230,20 +241,32 @@ void hal_gtzc_init(void)
* unprivileged; with the reset default (PRIVCFGR=0xFFFFFFFF) the
* DMA's descriptor/buffer reads from SRAM2 raise illegal-access
* (TZIC1_SR4 bit 26) and the channel suspends with TPS=6 (TBU). */
#if defined(WOLFBOOT_SECURE_APP)
for (i = 0; i < 4; i++) {
SET_GTZC1_MPCBBx_SECCFGR_VCTR(2, i, 0xFFFFFFFF);
}
#else
for (i = 0; i < 4; i++) {
SET_GTZC1_MPCBBx_SECCFGR_VCTR(2, i, 0x0);
SET_GTZC1_MPCBBx_PRIVCFGR_VCTR(2, i, 0x0);
}
#endif

/* Configure SRAM3 as non-secure (320 KB) but PRIVILEGED. The NS CPU
* runs privileged (Thread mode) and can use SRAM3 freely; only the
* unprivileged ETH DMA master needs unprivileged RAM, and its
* descriptors/buffers are pinned to SRAM2 (.eth_buffers). Leaving
* SRAM3 privileged lets a future NS OS own the unprivileged
* boundary. */
#if defined(WOLFBOOT_SECURE_APP)
for (i = 0; i < 20; i++) {
SET_GTZC1_MPCBBx_SECCFGR_VCTR(3, i, 0xFFFFFFFF);
}
#else
for (i = 0; i < 20; i++) {
SET_GTZC1_MPCBBx_SECCFGR_VCTR(3, i, 0x0);
}
#endif
}

#elif defined(TARGET_stm32u5)
Expand Down Expand Up @@ -327,9 +350,12 @@ void hal_tz_sau_init(void)
sau_init_region(0, WOLFBOOT_NSC_ADDRESS,
WOLFBOOT_NSC_ADDRESS + WOLFBOOT_NSC_SIZE - 1, 1);

/* Non-secure flash alias (boot partition only) */
/* Non-secure flash alias (boot partition only). A secure application is
* deliberately kept out of the SAU NS window and is entered Secure. */
#if !defined(WOLFBOOT_SECURE_APP)
sau_init_region(1, WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 1, 0);
#endif

/* Non-secure RAM region: SRAM2 (64 KB) + SRAM3 (320 KB).
* Lower bound widened from 0x20050000 to 0x20040000 to cover SRAM2,
Expand Down
18 changes: 16 additions & 2 deletions hal/stm32h5.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,17 @@
#if TZ_SECURE()
static int is_flash_nonsecure(uint32_t address)
{
#if defined(WOLFBOOT_SECURE_APP)
(void)address;
return 0;
#else
if (address >= WOLFBOOT_PARTITION_BOOT_ADDRESS &&
address < WOLFBOOT_PARTITION_BOOT_ADDRESS +
WOLFBOOT_PARTITION_SIZE) {
return 1;
}
return 0;
#endif
}
#endif

Expand Down Expand Up @@ -301,11 +306,17 @@ int hal_uds_derive_key(uint8_t *out, size_t out_len)

int hal_attestation_get_lifecycle(uint32_t *lifecycle)
{
uint32_t debugAuthStatus;
uint32_t productState;

if (lifecycle == NULL) {
return -1;
}

*lifecycle = 0x3000u; /* PSA_LIFECYCLE_SECURED (default) */
productState = (FLASH_OPTSR_CUR & FLASH_OPTSR_PRODUCT_STATE_MASK) >>
FLASH_OPTSR_PRODUCT_STATE_SHIFT;
debugAuthStatus = *(volatile uint32_t *)CORTEX_M_DAUTHSTATUS_ADDRESS;
*lifecycle = stm32h5_attestation_lifecycle(productState, debugAuthStatus);
return 0;
}

Expand Down Expand Up @@ -776,9 +787,12 @@ void hal_init(void)
void hal_prepare_boot(void)
{

/* Keep clock settings when staging a NS-application */
/* Keep clock settings when staging a NS-application. A secure application
* owns the TrustZone peripherals after the handoff. */
#if (TZ_SECURE())
#if !defined(WOLFBOOT_SECURE_APP)
periph_unsecure();
#endif
#else
#ifdef WOLFBOOT_RESTORE_CLOCK
clock_pll_off();
Expand Down
5 changes: 4 additions & 1 deletion hal/stm32h5.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#ifndef STM32H5_DEF_INCLUDED
#define STM32H5_DEF_INCLUDED

#include "stm32h5_lifecycle.h"

#define PERIPH_CLOCK_FREQ (64000000)

/* Assembly helpers */
Expand Down Expand Up @@ -280,7 +282,8 @@
#define FLASH_OPTSR_CUR (*(volatile uint32_t *)(FLASH_BASE + 0x50))
#define FLASH_OPTSR_PRG (*(volatile uint32_t *)(FLASH_BASE + 0x54))
#define FLASH_OPTSR_SWAP_BANK (1 << 31)

#define FLASH_OPTSR_PRODUCT_STATE_SHIFT 8
#define FLASH_OPTSR_PRODUCT_STATE_MASK (0xFFu << FLASH_OPTSR_PRODUCT_STATE_SHIFT)

/* Register values (for both secure and non secure registers)
* RM0481 Table 75 */
Expand Down
Loading
Loading