diff --git a/.github/workflows/test-sim-self-update.yml b/.github/workflows/test-sim-self-update.yml index a447f23508..0a78ccd3f4 100644 --- a/.github/workflows/test-sim-self-update.yml +++ b/.github/workflows/test-sim-self-update.yml @@ -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 diff --git a/Makefile b/Makefile index 6319e0ee30..ab8db988ec 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/config/examples/aurix-tc375-hsm-self-update-wolfHSM-certs-rsa4096.config b/config/examples/aurix-tc375-hsm-self-update-wolfHSM-certs-rsa4096.config new file mode 100644 index 0000000000..d014ec4333 --- /dev/null +++ b/config/examples/aurix-tc375-hsm-self-update-wolfHSM-certs-rsa4096.config @@ -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 diff --git a/config/examples/sim-self-header-ext.config b/config/examples/sim-self-header-ext.config new file mode 100644 index 0000000000..5255555be6 --- /dev/null +++ b/config/examples/sim-self-header-ext.config @@ -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 diff --git a/config/examples/sim-self-header.config b/config/examples/sim-self-header.config new file mode 100644 index 0000000000..2e9c1a110a --- /dev/null +++ b/config/examples/sim-self-header.config @@ -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 diff --git a/docs/Signing.md b/docs/Signing.md index bed298f243..af21fa6855 100644 --- a/docs/Signing.md +++ b/docs/Signing.md @@ -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 diff --git a/docs/firmware_image.md b/docs/firmware_image.md index 4cceaa9a14..2daded06ab 100644 --- a/docs/firmware_image.md +++ b/docs/firmware_image.md @@ -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. + diff --git a/docs/firmware_update.md b/docs/firmware_update.md index f528d0a773..971edcc845 100644 --- a/docs/firmware_update.md +++ b/docs/firmware_update.md @@ -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=` | Yes | Flash address where the header is stored. **Must be sector-aligned.** | +| `WOLFBOOT_SELF_HEADER_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 diff --git a/docs/flash_partitions.md b/docs/flash_partitions.md index 4ea5106797..d31ce7cc33 100644 --- a/docs/flash_partitions.md +++ b/docs/flash_partitions.md @@ -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 diff --git a/hal/aurix_tc3xx.c b/hal/aurix_tc3xx.c index 5bdc174c7b..b11d81fd56 100644 --- a/hal/aurix_tc3xx.c +++ b/hal/aurix_tc3xx.c @@ -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 */ } diff --git a/include/image.h b/include/image.h index 6f9b5924b8..c9e7fcba10 100644 --- a/include/image.h +++ b/include/image.h @@ -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)) diff --git a/include/target.h.in b/include/target.h.in index 9e4a7d3558..8458fb9ab3 100644 --- a/include/target.h.in +++ b/include/target.h.in @@ -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@ diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index f380835910..eda5055677 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -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); diff --git a/options.mk b/options.mk index 175206dba0..ff57622796 100644 --- a/options.mk +++ b/options.mk @@ -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 diff --git a/src/image.c b/src/image.c index 7c11d06715..a5688c672e 100644 --- a/src/image.c +++ b/src/image.c @@ -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. * diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 3bfee3476b..b5dc650a72 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -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. * diff --git a/src/update_flash.c b/src/update_flash.c index b2213d076f..2c03c31453 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -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 +#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(); } diff --git a/test-app/Makefile b/test-app/Makefile index f0f1bd0878..c636a1dc26 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -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) diff --git a/test-app/app_sim.c b/test-app/app_sim.c index ae8ae7dbbd..716287f0fa 100644 --- a/test-app/app_sim.c +++ b/test-app/app_sim.c @@ -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; } diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 07ee8507a9..c3389f9385 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -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); diff --git a/tools/test.mk b/tools/test.mk index 75ec2fddde..112d35dc0c 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -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) ==="