Add self-header feature with support for sim and AURIX TC3xx

pull/696/head
Brett Nicholas 2025-11-19 14:08:14 -07:00 committed by Daniele Lacamera
parent 9397988f01
commit 4d31ef5502
21 changed files with 718 additions and 26 deletions

View File

@ -1,4 +1,4 @@
name: Simulator self-update test
name: Simulator self-update and self-header tests
on:
push:
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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).
@ -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

View File

@ -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 */
}

View File

@ -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);
@ -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))

View File

@ -95,6 +95,16 @@
#ifndef WOLFBOOT_PARTITION_SIZE
#define WOLFBOOT_PARTITION_SIZE @WOLFBOOT_PARTITION_SIZE@
#endif
#ifdef WOLFBOOT_SELF_HEADER
#if defined(WOLFBOOT_PART_USE_ARCH_OFFSET)
#if !defined(EXT_FLASH) || (defined(EXT_FLASH) && !defined(WOLFBOOT_SELF_HEADER_EXT))
#define WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS (ARCH_FLASH_OFFSET + @WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS@)
#endif
#endif
#ifndef WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS
#define WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS @WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS@
#endif
#endif /* WOLFBOOT_SELF_HEADER */
#endif
#define WOLFBOOT_DTS_BOOT_ADDRESS @WOLFBOOT_DTS_BOOT_ADDRESS@

View File

@ -152,6 +152,27 @@ extern "C" {
# endif
#endif
#ifdef WOLFBOOT_SELF_HEADER
#ifndef WOLFBOOT_SELF_HEADER_SIZE
#define WOLFBOOT_SELF_HEADER_SIZE IMAGE_HEADER_SIZE
#endif
#if (WOLFBOOT_SELF_HEADER_SIZE < IMAGE_HEADER_SIZE)
#error "WOLFBOOT_SELF_HEADER_SIZE must be at least IMAGE_HEADER_SIZE"
#endif
#ifdef __WOLFBOOT
#if !defined(WOLFBOOT_PART_USE_ARCH_OFFSET)
#if (WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS % WOLFBOOT_SECTOR_SIZE)
#error "WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS must be sector aligned"
#endif
#endif
#endif
#ifdef WOLFBOOT_SELF_HEADER_EXT
#ifndef EXT_FLASH
#error "WOLFBOOT_SELF_HEADER_EXT requires EXT_FLASH"
#endif
#endif
#endif /* WOLFBOOT_SELF_HEADER */
#ifdef BIG_ENDIAN_ORDER
# define WOLFBOOT_MAGIC 0x574F4C46 /* WOLF */
# define WOLFBOOT_MAGIC_TRAIL 0x424F4F54 /* BOOT */
@ -433,6 +454,7 @@ extern "C" {
#define PART_BOOT 0
#define PART_UPDATE 1
#define PART_SWAP 2
#define PART_SELF 3
#define PART_NONE 0xFF
#define PART_DTS (0x10)
@ -473,6 +495,10 @@ uint32_t wolfBoot_image_size(uint8_t *image);
uint32_t wolfBoot_get_blob_version(uint8_t *blob);
uint16_t wolfBoot_get_blob_type(uint8_t *blob);
uint32_t wolfBoot_get_blob_diffbase_version(uint8_t *blob);
#ifdef WOLFBOOT_SELF_HEADER
uint8_t* wolfBoot_get_self_header(void);
uint32_t wolfBoot_get_self_version(void);
#endif
uint16_t wolfBoot_find_header(uint8_t *haystack, uint16_t type, uint8_t **ptr);

View File

@ -74,6 +74,21 @@ ifeq ($(WOLFBOOT_TPM_SEAL),1)
endif
endif
## Persist wolfBoot self header at fixed address
## Invariants and defaults are enforced in wolfboot.h
ifeq ($(WOLFBOOT_SELF_HEADER),1)
CFLAGS+=-DWOLFBOOT_SELF_HEADER
ifeq ($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS),)
$(error WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS must be set when WOLFBOOT_SELF_HEADER=1)
endif
ifneq ($(WOLFBOOT_SELF_HEADER_SIZE),)
CFLAGS+=-D"WOLFBOOT_SELF_HEADER_SIZE=$(WOLFBOOT_SELF_HEADER_SIZE)"
endif
ifeq ($(SELF_HEADER_EXT),1)
CFLAGS+=-DWOLFBOOT_SELF_HEADER_EXT
endif
endif
## DSA Settings
ifeq ($(SIGN),NONE)
SIGN_OPTIONS+=--no-sign

View File

@ -1436,6 +1436,67 @@ int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part,
#endif /* WOLFBOOT_FIXED_PARTITIONS */
#ifdef WOLFBOOT_SELF_HEADER
/**
* @brief Open wolfBoot's own image for verification.
*
* This function initializes a wolfBoot_image structure to represent wolfBoot
* itself, using the persisted self-header and the bootloader's flash location.
* The resulting image can be passed to wolfBoot_verify_integrity() and
* wolfBoot_verify_authenticity() to verify the bootloader.
*
* @param img Pointer to a wolfBoot_image structure to be initialized.
*
* @return 0 on success, -1 on failure (NULL pointer or invalid self-header).
*/
int wolfBoot_open_self(struct wolfBoot_image* img)
{
uint8_t* hdr;
int ret;
if (img == NULL) {
return -1;
}
hdr = wolfBoot_get_self_header();
if (hdr == NULL) {
return -1;
}
ret = wolfBoot_open_self_address(img, hdr, (uint8_t*)ARCH_FLASH_OFFSET);
if (ret == 0) {
/* PART_SELF may be marked external for header storage, but wolfBoot
* firmware bytes are always in internal flash at ARCH_FLASH_OFFSET. */
img->not_ext = 1;
}
return ret;
}
/*
* Directly accesses flash, suitable for non-internal-wolfBoot usage
*/
int wolfBoot_open_self_address(struct wolfBoot_image* img, uint8_t* hdr,
uint8_t* image)
{
uint32_t magic;
XMEMSET(img, 0, sizeof(struct wolfBoot_image));
magic = *((uint32_t*)hdr);
if (magic != WOLFBOOT_MAGIC) {
return -1;
}
img->hdr = hdr;
img->fw_size = wolfBoot_image_size(hdr);
img->fw_base = image;
img->part = PART_SELF;
img->hdr_ok = 1;
return 0;
}
#endif
/**
* @brief Verify the integrity of the image using the stored SHA hash.
*

View File

@ -1174,6 +1174,45 @@ static uint8_t* wolfBoot_get_image_from_part(uint8_t part)
return image;
}
#ifdef WOLFBOOT_SELF_HEADER
uint8_t* wolfBoot_get_self_header(void)
{
#if defined(EXT_FLASH) && defined(WOLFBOOT_SELF_HEADER_EXT)
static uint8_t hdr_buf[IMAGE_HEADER_SIZE];
uint32_t magic;
ext_flash_read((uintptr_t)WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS, hdr_buf,
IMAGE_HEADER_SIZE);
magic = *((uint32_t*)hdr_buf);
if (magic != WOLFBOOT_MAGIC) {
return NULL;
}
return hdr_buf;
#else
uint8_t* hdr = (uint8_t*)WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS;
uint32_t magic = *((uint32_t*)hdr);
if (magic != WOLFBOOT_MAGIC) {
return NULL;
}
return hdr;
#endif
}
uint32_t wolfBoot_get_self_version(void)
{
uint8_t* hdr = wolfBoot_get_self_header();
if (hdr == NULL) {
return 0;
}
return wolfBoot_get_blob_version(hdr);
}
#endif
/**
* @brief Get image version for a partition.
*

View File

@ -28,6 +28,7 @@
#include "hal.h"
#include "spi_flash.h"
#include "target.h"
#include "wolfboot/wolfboot.h"
#include "delta.h"
#include "printf.h"
@ -75,6 +76,73 @@ static void RAMFUNCTION wolfBoot_erase_bootloader(void)
#include <string.h>
#ifdef WOLFBOOT_SELF_HEADER
static void RAMFUNCTION wolfBoot_update_self_header(struct wolfBoot_image* src)
{
uint32_t offset = 0;
uint8_t buffer[FLASHBUFFER_SIZE];
int dst_ext = 0;
uintptr_t dst_int_addr;
uintptr_t dst_ext_addr = WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS;
dst_int_addr = WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS;
/* Determine destination flash type */
#if defined(EXT_FLASH) && defined(WOLFBOOT_SELF_HEADER_EXT)
dst_ext = 1;
#endif
#ifdef EXT_FLASH
/* Erase the self-header sector - sets all bytes to 0xFF */
if (dst_ext) {
ext_flash_unlock();
ext_flash_erase(dst_ext_addr, WOLFBOOT_SELF_HEADER_SIZE);
}
else
#endif
{
hal_flash_erase(dst_int_addr, WOLFBOOT_SELF_HEADER_SIZE);
}
/* Write only the actual header data (IMAGE_HEADER_SIZE bytes).
* Any reserved space beyond IMAGE_HEADER_SIZE remains 0xFF from erase. */
while (offset < IMAGE_HEADER_SIZE) {
uint32_t chunk = IMAGE_HEADER_SIZE - offset;
if (chunk > FLASHBUFFER_SIZE) {
chunk = FLASHBUFFER_SIZE;
}
#ifdef EXT_FLASH
if (PART_IS_EXT(src)) {
ext_flash_check_read((uintptr_t)(src->hdr) + offset, (void*)buffer,
chunk);
}
else
#endif
{
memcpy(buffer, (uint8_t*)(src->hdr + offset), chunk);
}
#ifdef EXT_FLASH
if (dst_ext) {
ext_flash_write(dst_ext_addr + offset, buffer, chunk);
}
else
#endif
{
hal_flash_write(dst_int_addr + offset, buffer, chunk);
}
offset += chunk;
}
#ifdef EXT_FLASH
if (dst_ext) {
ext_flash_lock();
}
#endif
}
#endif
static void RAMFUNCTION wolfBoot_self_update(struct wolfBoot_image *src)
{
uintptr_t pos = 0;
@ -109,6 +177,11 @@ static void RAMFUNCTION wolfBoot_self_update(struct wolfBoot_image *src)
pos += FLASHBUFFER_SIZE;
}
}
#ifdef WOLFBOOT_SELF_HEADER
wolfBoot_update_self_header(src);
#endif
hal_flash_lock();
arch_reboot();
}

View File

@ -360,6 +360,20 @@ ifeq ($(TARGET),sim)
endif
endif
# Self-header verification requires image.c (for verify_integrity/verify_authenticity)
# and keystore (for public key lookup), plus wolfcrypt for crypto operations
ifeq ($(WOLFBOOT_SELF_HEADER),1)
CFLAGS+=-D"WOLFBOOT_SELF_HEADER"
CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFSSL)"
# Test app needs addtional image, keystore, and crypto objects to do verification
APP_OBJS+=../src/image.o ../src/keystore.o
# Only add wolfcrypt objs if WOLFHSM blocks won't provide them
ifeq ($(WOLFHSM_CLIENT)$(WOLFHSM_SERVER),)
# remove duplicates for certain object files
APP_OBJS+=$(sort $(WOLFCRYPT_OBJS))
endif
endif
ifeq ($(EXT_FLASH),1)
CFLAGS+=-D"EXT_FLASH=1" -D"PART_UPDATE_EXT=1"
ifeq ($(NO_XIP),1)

View File

@ -29,6 +29,9 @@
#include "target.h"
#include "wolfboot/wolfboot.h"
#ifdef WOLFBOOT_SELF_HEADER
#include "image.h"
#endif
#ifdef DUALBANK_SWAP
uint32_t hal_sim_get_dualbank_state(void);
@ -119,6 +122,42 @@ int do_cmd(const char *cmd)
printf("TLV 0x%x: not found!\r\n", tlv);
}
}
#ifdef WOLFBOOT_SELF_HEADER
if (strcmp(cmd, "verify_self") == 0) {
struct wolfBoot_image img;
int ret;
printf("=== Self-Header Verification Test ===\n");
/* Open bootloader image using persisted self-header */
ret = wolfBoot_open_self(&img);
if (ret != 0) {
printf("FAIL: wolfBoot_open_self returned %d\n", ret);
return -1;
}
printf("open_self: OK (fw_size=%u, part=%d)\n", (unsigned)img.fw_size,
img.part);
/* Verify integrity (hash check) */
ret = wolfBoot_verify_integrity(&img);
if (ret != 0) {
printf("FAIL: wolfBoot_verify_integrity returned %d\n", ret);
return -1;
}
printf("verify_integrity: OK\n");
/* Verify authenticity (signature check) */
ret = wolfBoot_verify_authenticity(&img);
if (ret != 0) {
printf("FAIL: wolfBoot_verify_authenticity returned %d\n", ret);
return -1;
}
printf("verify_authenticity: OK\n");
printf("=== Self-header verification PASSED ===\n");
return 0;
}
#endif
/* wrong command */
return -1;
}

View File

@ -251,6 +251,7 @@ struct cmd_options {
int policy_sign;
int self_update;
int sha_only;
int header_only;
int encrypt;
int hash_algo;
int sign;
@ -1113,7 +1114,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
{
uint32_t header_idx;
uint8_t *header;
FILE *f, *f2, *fek, *fef;
FILE *f = NULL, *f2 = NULL, *fek = NULL, *fef = NULL;
uint32_t fw_version32;
struct stat attrib;
uint16_t image_type;
@ -1747,20 +1748,26 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
}
fwrite(header, 1, header_idx, f);
/* Copy image to output */
f2 = fopen(image_file, "rb");
pos = 0;
while (pos < image_sz) {
read_sz = image_sz;
if (read_sz > sizeof(buf))
read_sz = sizeof(buf);
read_sz = (uint32_t)fread(buf, 1, read_sz, f2);
if ((read_sz == 0) && (feof(f2)))
break;
fwrite(buf, 1, read_sz, f);
pos += read_sz;
if (!CMD.header_only) {
f2 = fopen(image_file, "rb");
pos = 0;
while (pos < image_sz) {
read_sz = image_sz;
if (read_sz > sizeof(buf)) {
read_sz = sizeof(buf);
}
read_sz = (uint32_t)fread(buf, 1, read_sz, f2);
if ((read_sz == 0) && (feof(f2))) {
break;
}
fwrite(buf, 1, read_sz, f);
pos += read_sz;
}
fclose(f2);
f2 = NULL;
}
if ((CMD.encrypt != ENC_OFF) && CMD.encrypt_key_file) {
if (!CMD.header_only && (CMD.encrypt != ENC_OFF) && CMD.encrypt_key_file) {
uint8_t key[ENC_MAX_KEY_SZ], iv[ENC_MAX_IV_SZ];
uint8_t enc_buf[ENC_MAX_BLOCK_SZ];
int ivSz, keySz, encBlockSz;
@ -1855,8 +1862,12 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
}
printf("Output image(s) successfully created.\n");
ret = 0;
fclose(f2);
fclose(f);
if (f2) {
fclose(f2);
}
if (f) {
fclose(f);
}
failure:
if (cert_chain)
free(cert_chain);
@ -2602,6 +2613,9 @@ int main(int argc, char** argv)
CMD.self_update = 1;
CMD.partition_id = 0;
}
else if (strcmp(argv[i], "--header-only") == 0) {
CMD.header_only = 1;
}
else if (strcmp(argv[i], "--id") == 0) {
long id = strtol(argv[++i], NULL, 10);
if ((id < 0 || id > 15) || ((id == 0) && (argv[i][0] != '0'))) {
@ -2811,9 +2825,12 @@ int main(int argc, char** argv)
if (tmpstr) {
*tmpstr = '\0'; /* null terminate at last "." */
}
snprintf(CMD.output_image_file, sizeof(CMD.output_image_file) - 1,
"%s_v%s_%s.bin", (char*)buf, CMD.fw_version,
CMD.sha_only ? "digest" : "signed");
{
const char* artifact =
CMD.header_only ? "header" : (CMD.sha_only ? "digest" : "signed");
snprintf(CMD.output_image_file, sizeof(CMD.output_image_file) - 1,
"%s_v%s_%s.bin", (char*)buf, CMD.fw_version, artifact);
}
snprintf(CMD.output_encrypted_image_file,
sizeof(CMD.output_encrypted_image_file),
@ -2855,9 +2872,10 @@ int main(int argc, char** argv)
"%s_v%s_signed_diff_encrypted.bin",
(char*)buf, CMD.fw_version);
}
printf("Output %6s: %s\n", CMD.sha_only ? "digest" : "image",
CMD.output_image_file);
if (CMD.encrypt) {
printf("Output %6s: %s\n",
CMD.header_only ? "header" : (CMD.sha_only ? "digest" : "image"),
CMD.output_image_file);
if (CMD.encrypt && !CMD.header_only) {
printf("Encrypted output: %s\n", CMD.output_encrypted_image_file);
}
printf("Target partition id : %hu ", CMD.partition_id);

View File

@ -266,6 +266,101 @@ test-sim-self-update: wolfboot.bin FORCE
@# Verify dummy payload was written to bootloader region, indicating the self update swapped images as expected
$(Q)cmp -n $$(wc -c < dummy_update.bin | awk '{print $$1}') dummy_update.bin internal_flash.dd && echo "=== Self-update test PASSED ==="
# Test self-header cryptographic verification (hash + signature validation)
#
# Verifies that an application can cryptographically verify the bootloader using
# the persisted self-header. Uses the real wolfboot.bin (not a dummy) so the
# header hash matches the actual firmware bytes in flash.
#
# First triggers a self-update so the bootloader persists its signed header,
# then verifies the persisted header matches the signing tool's --header-only
# output byte-for-byte, and finally boots into the test-app which calls
# open_self/verify_integrity/verify_authenticity to simulate an external entity
# veriyfing the bootloader image
#
test-sim-self-header-verify: wolfboot.bin test-app/image_v1_signed.bin FORCE
@echo "=== Simulator Self-Header Verification Test ==="
@# Sign real wolfboot.bin as v2 update (header hash will match firmware bytes)
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update wolfboot.bin $(PRIVATE_KEY) 2
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update --header-only wolfboot.bin $(PRIVATE_KEY) 2
@# Create partition images and assemble flash with pBOOT trigger to initiate self-update
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd
$(Q)dd if=wolfboot_v2_signed.bin of=update_part.dd bs=1 conv=notrunc
$(Q)printf "pBOOT" | dd of=update_part.dd bs=1 seek=$$(($(WOLFBOOT_PARTITION_SIZE) - 5)) conv=notrunc
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_part.dd
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > erased_sec.dd
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > self_hdr.dd
$(Q)$(BINASSEMBLE) internal_flash.dd \
0 wolfboot.bin \
$$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) self_hdr.dd \
$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_part.dd \
$$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS) - $(ARCH_FLASH_OFFSET))) update_part.dd \
$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS) - $(ARCH_FLASH_OFFSET))) erased_sec.dd
@# Run simulator — triggers self-update, which persists the header to flash
$(Q)./wolfboot.elf get_version || true
@# Extract the persisted header that the self-update wrote
$(Q)dd if=internal_flash.dd bs=1 skip=$$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) count=$$(($(WOLFBOOT_SECTOR_SIZE))) of=persisted_hdr.dd 2>/dev/null
@# Verify persisted header matches the signing tool's --header-only output
$(Q)cmp -n 256 persisted_hdr.dd wolfboot_v2_header.bin
@# Reassemble flash with test-app in boot partition so the app runs after wolfboot
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part_empty.dd
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_with_app.dd
$(Q)dd if=test-app/image_v1_signed.bin of=boot_with_app.dd bs=1 conv=notrunc
$(Q)$(BINASSEMBLE) internal_flash.dd \
0 wolfboot.bin \
$$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) persisted_hdr.dd \
$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_with_app.dd \
$$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS) - $(ARCH_FLASH_OFFSET))) update_part_empty.dd \
$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS) - $(ARCH_FLASH_OFFSET))) erased_sec.dd
@# Boot into test-app which calls verify_self to validate bootloader hash + signature
$(Q)./wolfboot.elf verify_self && echo "=== Self-Header Cryptographic Verification PASSED ==="
# Test self-header cryptographic verification with external self-header storage.
#
# Same verification chain as test-sim-self-header-verify, but the self-header is
# persisted to external flash instead of internal.
#
test-sim-self-header-ext-verify: wolfboot.bin test-app/image_v1_signed.bin FORCE
@echo "=== Simulator External Self-Header Verification Test ==="
@# Sign real wolfboot.bin as v2 update (header hash will match firmware bytes)
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update wolfboot.bin $(PRIVATE_KEY) 2
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update --header-only wolfboot.bin $(PRIVATE_KEY) 2
@# Create partition images and assemble internal + external flash
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd
$(Q)dd if=wolfboot_v2_signed.bin of=update_part.dd bs=1 conv=notrunc
$(Q)printf "pBOOT" | dd of=update_part.dd bs=1 seek=$$(($(WOLFBOOT_PARTITION_SIZE) - 5)) conv=notrunc
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_part.dd
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > erased_sec.dd
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > self_hdr.dd
$(Q)$(BINASSEMBLE) internal_flash.dd \
0 wolfboot.bin \
$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_part.dd
$(Q)$(BINASSEMBLE) external_flash.dd \
0 update_part.dd \
$(WOLFBOOT_PARTITION_SIZE) erased_sec.dd \
$(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) self_hdr.dd
@# Run simulator — triggers self-update, which persists the header to external flash
$(Q)./wolfboot.elf get_version || true
@# Confirm internal flash self-header location remains erased (header went to external only)
$(Q)dd if=/dev/zero bs=256 count=1 2>/dev/null | tr '\000' '\377' > erased_256.dd
$(Q)dd if=internal_flash.dd bs=1 skip=$$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) count=256 2>/dev/null | cmp -s erased_256.dd - && echo "=== Internal Self-Header location remains erased PASSED ==="
@# Extract persisted header from external flash and verify it matches the signed header
$(Q)dd if=external_flash.dd bs=1 skip=$(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) count=$$(($(WOLFBOOT_SECTOR_SIZE))) of=persisted_hdr.dd 2>/dev/null
$(Q)cmp -n 256 persisted_hdr.dd wolfboot_v2_header.bin
@# Reassemble flash with test-app in boot and the persisted external header
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part_empty.dd
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_with_app.dd
$(Q)dd if=test-app/image_v1_signed.bin of=boot_with_app.dd bs=1 conv=notrunc
$(Q)$(BINASSEMBLE) internal_flash.dd \
0 wolfboot.bin \
$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_with_app.dd
$(Q)$(BINASSEMBLE) external_flash.dd \
0 update_part_empty.dd \
$(WOLFBOOT_PARTITION_SIZE) erased_sec.dd \
$(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) persisted_hdr.dd
@# Boot into test-app which calls verify_self to validate bootloader hash + signature
$(Q)./wolfboot.elf verify_self && echo "=== External Self-Header Cryptographic Verification PASSED ==="
# Test bootloader self-update mechanism with external flash
test-sim-self-update-ext: wolfboot.bin FORCE
@echo "=== Simulator Self-Update Test (External Flash) ==="