Skip to content
Closed
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
14 changes: 13 additions & 1 deletion .github/workflows/test-sim-self-update.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Simulator self-update test
name: Simulator self-update and self-header tests

on:
push:
Expand Down Expand Up @@ -71,3 +71,15 @@ jobs:
make clean
cp config/examples/sim-self-update-ext.config .config
make test-sim-self-update-ext

- name: Run self-header verification test (internal flash)
run: |
make clean
cp config/examples/sim-self-header.config .config
make test-sim-self-header-verify

- name: Run self-header verification test (external flash)
run: |
make clean
cp config/examples/sim-self-header-ext.config .config
make test-sim-self-header-ext-verify
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,8 @@ include/target.h: $(TARGET_H_TEMPLATE) FORCE
sed -e "s/@WOLFBOOT_DTS_BOOT_ADDRESS@/$(WOLFBOOT_DTS_BOOT_ADDRESS)/g" | \
sed -e "s/@WOLFBOOT_DTS_UPDATE_ADDRESS@/$(WOLFBOOT_DTS_UPDATE_ADDRESS)/g" | \
sed -e "s/@WOLFBOOT_LOAD_ADDRESS@/$(WOLFBOOT_LOAD_ADDRESS)/g" | \
sed -e "s/@WOLFBOOT_LOAD_DTS_ADDRESS@/$(WOLFBOOT_LOAD_DTS_ADDRESS)/g" \
sed -e "s/@WOLFBOOT_LOAD_DTS_ADDRESS@/$(WOLFBOOT_LOAD_DTS_ADDRESS)/g" | \
sed -e "s/@WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS@/$(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS)/g" \
> $@

delta: tools/delta/bmdiff
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
ARCH?=AURIX_TC3
TARGET?=aurix_tc3xx
AURIX_TC3_HSM=1
SIGN?=RSA4096
HASH?=SHA256
DEBUG?=0
WOLFBOOT_VERSION?=1
V?=0
SPMATH?=1
RAM_CODE?=1
EXT_FLASH?=1
EXT_BOOT=1
EXT_UPDATE=1
EXT_SWAP=1
FLAGS_INVERT=1
FLASH_MULTI_SECTOR_ERASE=1
DEBUG_UART=1
PRINTF_ENABLED=1

# wolfHSM options
WOLFHSM_SERVER=1

# Cert chain options
CERT_CHAIN_VERIFY=1

# RSA4096 cert chains need the larger header and stack
WOLFBOOT_HUGE_STACK=1
IMAGE_HEADER_SIZE=4096

# self-header feature (persist header in external flash)
WOLFBOOT_SELF_HEADER=1
SELF_HEADER_EXT=1

ARCH_FLASH_OFFSET=0x80028000
WOLFBOOT_SECTOR_SIZE=0x4000
WOLFBOOT_PARTITION_SIZE=0x30000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80038000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x80068000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x80098000
WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS=0x8009C000
26 changes: 26 additions & 0 deletions config/examples/sim-self-header-ext.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
ARCH=sim
TARGET=sim
SIGN?=ED25519
HASH?=SHA256
WOLFBOOT_SMALL_STACK?=0
SPI_FLASH=0
EXT_FLASH=1
DEBUG=1
RAM_CODE=1
WOLFBOOT_VERSION=1

# Partition size increased to accommodate test-app with verify APIs
WOLFBOOT_PARTITION_SIZE=0x80000
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
# Update and swap on external flash (address 0x00000)
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x80000

# Self-header feature (persist header in external flash)
WOLFBOOT_SELF_HEADER=1
SELF_HEADER_EXT=1
WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS=0x90000

# Required for keytools
WOLFBOOT_FIXED_PARTITIONS=1
22 changes: 22 additions & 0 deletions config/examples/sim-self-header.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
ARCH=sim
TARGET=sim
SIGN?=ED25519
HASH?=SHA256
WOLFBOOT_SMALL_STACK?=0
SPI_FLASH=0
DEBUG=1
RAM_CODE=1
WOLFBOOT_VERSION=1

# Partition size increased to 512KB to accommodate test-app with verify APIs
WOLFBOOT_PARTITION_SIZE=0x80000
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000

WOLFBOOT_FIXED_PARTITIONS=1

# Self-header feature
WOLFBOOT_SELF_HEADER=1
WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS=0x40000
17 changes: 17 additions & 0 deletions docs/Signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,23 @@ the bootloader itself is stored.
* `--wolfboot-update` Indicate that the image contains a signed self-update
package for the bootloader. Equivalent to `--id 0`.

#### Header-only output (wolfBoot self header)

Use `--header-only` to emit only the manifest header without copying the
firmware bytes into the output file. This is useful when persisting
wolfBoot's own header at a separate flash address for external measurement:

```
$ tools/keytools/sign --wolfboot-update --header-only wolfboot.bin key.der 1
# Produces wolfboot_v1_header.bin (header only)
$ tools/keytools/sign --wolfboot-update wolfboot.bin key.der 1
# Produces wolfboot_v1_signed.bin (header + firmware)
```

For complete documentation of the self-header feature — including
configuration, update flow, and runtime verification — see
[firmware_update.md](firmware_update.md#self-header-persisting-the-bootloader-manifest).

#### Encryption using a symmetric key

Although signed to be authenticated, by default the image is not encrypted and
Expand Down
8 changes: 8 additions & 0 deletions docs/firmware_image.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ In order to boot a different image, wolfBoot will have to swap the content of th

For more information on how firmware images are stored and managed within the two partitions, see [Flash partitions](flash_partitions.md)

The same manifest header format is also used by the **self-header** feature
(`WOLFBOOT_SELF_HEADER`), where a copy of the bootloader's manifest header is
persisted at a separate flash address. This allows external components to
cryptographically verify the bootloader's authenticity and version using the
standard wolfBoot verification APIs. See
[firmware_update.md](firmware_update.md#self-header-persisting-the-bootloader-manifest)
for full details.




119 changes: 119 additions & 0 deletions docs/firmware_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,125 @@ rebooting.
wolfBoot can be used to deploy new bootloader versions as well as
update keys.

#### Self-header: persisting the bootloader manifest

In a typical wolfBoot deployment the bootloader verifies the application
firmware, but no entity verifies the bootloader itself. When an external
component — such as a [wolfHSM](wolfHSM.md) server, or another
secure co-processor — needs to measure and authenticate the running
bootloader, it must be able to read the bootloader's signed manifest
header independently. The **self-header** feature makes this possible by
persisting a copy of the bootloader's manifest header at a dedicated,
fixed flash address after every self-update.

This completes the chain of trust:

```
External verifier --verifies--> wolfBoot -->verifies--> Application
(wolfHSM/TPM) (self-header) (BOOT partition)
```

##### Configuration options

| Build option | Required | Description |
|---|---|---|
| `WOLFBOOT_SELF_HEADER=1` | Yes | Enable the self-header feature |
| `WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS=<addr>` | Yes | Flash address where the header is stored. **Must be sector-aligned.** |
| `WOLFBOOT_SELF_HEADER_SIZE=<size>` | No | Erase span at the header address. Defaults to `IMAGE_HEADER_SIZE`. Must be ≥ `IMAGE_HEADER_SIZE`. |
| `SELF_HEADER_EXT=1` | No | Store the header in external flash. Requires `EXT_FLASH=1`. |

##### Flash layout

The self-header occupies its own region in the flash map, separate from
the bootloader binary and the firmware partitions:

```
Internal flash (example)
┌─────────────────────┐ 0x00000
│ wolfBoot │
│ (bootloader) │
├─────────────────────┤ WOLFBOOT_PARTITION_BOOT_ADDRESS
│ BOOT partition │
├─────────────────────┤ WOLFBOOT_PARTITION_UPDATE_ADDRESS
│ UPDATE partition │
├─────────────────────┤ WOLFBOOT_PARTITION_SWAP_ADDRESS
│ SWAP partition │
├─────────────────────┤ WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS
│ Self-header │ (IMAGE_HEADER_SIZE or WOLFBOOT_SELF_HEADER_SIZE)
└─────────────────────┘
```

When `SELF_HEADER_EXT=1` is set the self-header is stored in external
flash instead. See [Flash partitions](flash_partitions.md) for more
detail on the overall partition layout.

##### Update flow

During a self-update with `WOLFBOOT_SELF_HEADER` enabled, the following
steps occur:

1. A new signed bootloader image (created with `--wolfboot-update`) is
placed in the UPDATE partition.
2. The application triggers the update (e.g. `wolfBoot_update_trigger()`).
3. On reboot, wolfBoot validates the new bootloader image — verifying both
integrity and signature.
4. The new bootloader binary is copied to flash, overwriting the old one in-place.
5. **After** the firmware copy completes, the manifest header is written
to `WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS`.
6. The system reboots into the new bootloader.

##### Runtime verification API

Applications and external verifiers can use the following functions
(declared in `include/image.h` and `include/wolfboot/wolfboot.h`) when linking
against wolfBoot as a library to read and verify the persisted self-header:

- **`wolfBoot_get_self_header()`** — returns a pointer to the persisted
header bytes, or `NULL` if the header is missing or invalid.
- **`wolfBoot_get_self_version()`** — returns the version number stored
in the persisted header, or `0` if the header is invalid.
- **`wolfBoot_open_self(struct wolfBoot_image *img)`** — opens the
self-header and populates `img` so that the standard verification
functions can be used on it. Returns `0` on success.
- **`wolfBoot_open_self_address(struct wolfBoot_image *img, uint8_t *hdr, uint8_t *image)`**
— like `wolfBoot_open_self()` but accepts explicit header and firmware
base addresses. Useful for opening any self-header and image combination.

After opening the image with `wolfBoot_open_self()`, the caller can
verify the bootloader using the standard verification functions:

```c
struct wolfBoot_image img;
if (wolfBoot_open_self(&img) == 0) {
wolfBoot_verify_integrity(&img);
wolfBoot_verify_authenticity(&img);
}
```

**NOTE: An application verifying its own integrity and authenticity almost never provides meaningful security.**

The self-header feature exists to support verification of an *untrusted* wolfBoot image by an external entity that has its own independent root of trust, before execution is transferred to wolfBoot.
This is intended for platforms where the silicon does not support ROM-based verification of a first-stage bootloader.

A common use case is in automotive multicore systems used with wolfHSM, where an HSM core boots first and is responsible for authenticating and releasing the remaining cores in the system.

##### Factory programming

At manufacturing time the self-header must be programmed alongside the
bootloader binary. Use `--header-only` with the sign tool to generate a
standalone header binary:

```
tools/keytools/sign --wolfboot-update --header-only wolfboot.bin key.der 1
```

This produces a `wolfboot_v1_header.bin` containing only the manifest
header. Program it at `WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS` and the
regular signed image at the bootloader origin.

See [Signing.md](Signing.md#header-only-output-wolfboot-self-header) for
more detail on the `--header-only` sign tool option.

### Incremental updates (aka: 'delta' updates)

wolfBoot supports incremental updates, based on a specific older version. The sign tool
Expand Down
44 changes: 44 additions & 0 deletions docs/flash_partitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ The flash memory of the target is partitioned into the following areas:
- Swapping space (SWAP partition) starting at address `WOLFBOOT_PARTITION_SWAP_ADDRESS`
- the swap space size is defined as `WOLFBOOT_SECTOR_SIZE` and must be as big as the
largest sector used in either BOOT/UPDATE partitions.
- (Optional) Self-header partition starting at address `WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS`,
used when `WOLFBOOT_SELF_HEADER=1` is enabled. See [Self-header partition](#self-header-partition) below.

A proper partitioning configuration must be set up for the specific use, by setting
the values for offsets and sizes in [include/target.h](../include/target.h).
Expand All @@ -34,6 +36,48 @@ This partition is usually very small, and only contains the bootloader code and
Public keys pre-authorized during factory image creations are automatically stored
as part of the firmware image.

### Self-header partition

When `WOLFBOOT_SELF_HEADER=1` is enabled, an additional flash region is
reserved for the bootloader's own signed manifest header. This allows
external components (e.g. wolfHSM server, a secure co-processor, etc.) to read
and cryptographically verify the bootloader.

Configuration:

- `WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS` — the flash address for the
header. **Must be aligned to `WOLFBOOT_SECTOR_SIZE`.**
- `WOLFBOOT_SELF_HEADER_SIZE` — (optional) the erase span at the header
address. Defaults to `IMAGE_HEADER_SIZE` and must be at least
`IMAGE_HEADER_SIZE`.
- `SELF_HEADER_EXT=1` — store the self-header in external flash instead
of internal flash. Requires `EXT_FLASH=1`.

The self-header partition sits alongside the other partitions in the
flash map:

```
┌─────────────────────┐
│ wolfBoot │
├─────────────────────┤ BOOT_ADDRESS
│ BOOT partition │
├─────────────────────┤ UPDATE_ADDRESS
│ UPDATE partition │
├─────────────────────┤ SWAP_ADDRESS
│ SWAP partition │
├─────────────────────┤ SELF_HEADER_ADDRESS
│ Self-header │
└─────────────────────┘
```

The actual placement is flexible — the self-header can be located
anywhere in the flash map as long as it does not overlap with any other
partition and is sector-aligned.

For full details on the self-header feature — including the update flow,
runtime verification API, and factory programming — see
[firmware_update.md](firmware_update.md#self-header-persisting-the-bootloader-manifest).

### BOOT partition

This is the only partition from where it is possible to chain-load and execute a
Expand Down
6 changes: 4 additions & 2 deletions hal/aurix_tc3xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,11 @@ void hal_init(void)
#ifdef DEBUG_UART
uart_init();
#ifndef WOLFBOOT_AURIX_TC3XX_HSM
wolfBoot_printf("Hello from TC3xx wolfBoot on Tricore\n");
wolfBoot_printf("Hello from TC3xx wolfBoot on Tricore: V%d\n",
WOLFBOOT_VERSION);
#else
wolfBoot_printf("Hello from TC3xx wolfBoot on HSM\n");
wolfBoot_printf("Hello from TC3xx wolfBoot on HSM: V%d\n",
WOLFBOOT_VERSION);
#endif
#endif /* DEBUG_UART */
}
Expand Down
13 changes: 12 additions & 1 deletion include/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,11 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part);
int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part, uint8_t* addr);
#endif
int wolfBoot_open_image_address(struct wolfBoot_image* img, uint8_t* image);
#ifdef WOLFBOOT_SELF_HEADER
int wolfBoot_open_self(struct wolfBoot_image *img);
int wolfBoot_open_self_address(struct wolfBoot_image *img, uint8_t *hdr,
uint8_t *image);
#endif
int wolfBoot_verify_integrity(struct wolfBoot_image *img);
int wolfBoot_verify_authenticity(struct wolfBoot_image *img);
int wolfBoot_set_partition_state(uint8_t part, uint8_t newst);
Expand Down Expand Up @@ -1293,10 +1298,16 @@ int keyslot_id_by_sha(const uint8_t *hint);
# else
# define SWAP_EXT 0
# endif
# if defined(WOLFBOOT_SELF_HEADER_EXT)
# define SELF_HEADER_EXT 1
# else
# define SELF_HEADER_EXT 0
# endif
# define PARTN_IS_EXT(pn) \
((pn == PART_BOOT || pn == PART_DTS_BOOT) ? BOOT_EXT: \
((pn == PART_UPDATE || pn == PART_DTS_UPDATE) ? UPDATE_EXT : \
((pn == PART_SWAP) ? SWAP_EXT : 0)))
((pn == PART_SWAP) ? SWAP_EXT : \
((pn == PART_SELF) ? SELF_HEADER_EXT : 0))))
# define PART_IS_EXT(x) (!(x)->not_ext) && PARTN_IS_EXT(((x)->part))


Expand Down
Loading
Loading