diff --git a/arch.mk b/arch.mk index 0da7cdd03f..05ab795ecf 100644 --- a/arch.mk +++ b/arch.mk @@ -483,6 +483,7 @@ ifeq ($(ARCH),ARM) ifeq ($(TARGET),stm32l0) CORTEX_M0=1 SPI_TARGET=stm32 + FLASH_ERASE_UNIT?=128 endif ifeq ($(TARGET),stm32c0) diff --git a/docs/HAL.md b/docs/HAL.md index 054ff61220..20170c840b 100644 --- a/docs/HAL.md +++ b/docs/HAL.md @@ -62,9 +62,11 @@ by the bootloader at the end of every write and erase operations. Called by the bootloader to erase part of the flash memory to allow subsequent boots. Erase operations must be performed via the specific IAP interface of the target microcontroller. `address` marks the start of the area that the bootloader wants to erase, and `len` specifies -the size of the area to be erased. `address` is guaranteed to be aligned to `WOLFBOOT_SECTOR_SIZE`, -and `len` is guaranteed to be a multiple of `WOLFBOOT_SECTOR_SIZE`. This function must take into account -the geometry of the flash sectors, and erase all the sectors in between. +the size of the area to be erased. Most requests are aligned to `WOLFBOOT_SECTOR_SIZE` and have +a length that is a multiple of it. Encrypted-key maintenance may issue smaller requests aligned +to `WOLFBOOT_FLASH_ERASE_UNIT`, configured with `FLASH_ERASE_UNIT` (which defaults to +`WOLFBOOT_SECTOR_SIZE`). The HAL must support both request sizes and take the flash geometry into +account when erasing. `void hal_prepare_boot(void)` diff --git a/docs/compile.md b/docs/compile.md index 6c6415f4e0..1457b531a5 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -320,6 +320,18 @@ and a journal record is 16 bytes, or one write unit if larger. | [Journal](#trailer-storage-option-nvm_flash_journal-and-nvm_flash_journal_dual) | `NVM_FLASH_JOURNAL=1` | sector size / record size >= 3N + 4, slow erases | 1 record program | 1 | yes | | [Journal in two sectors](#redundant-sector-journaling-nvm_flash_journal_dual) | `NVM_FLASH_JOURNAL=1` and `NVM_FLASH_JOURNAL_DUAL=1` | slow erases, and a reset must never damage the log | 1 record program | 2 | yes | +### Flash erase unit + +`FLASH_ERASE_UNIT=` sets the smallest aligned erase range wolfBoot may request +from `hal_flash_erase()` while updating the encrypted key and trailer. It must be a +power of two that divides `WOLFBOOT_SECTOR_SIZE`. If omitted, it defaults to +`WOLFBOOT_SECTOR_SIZE`; the STM32L0 target defaults to 128 bytes. + +The HAL must support erasing ranges aligned to this unit. Most other erase requests +remain sector-sized. In non-write-once builds, `NVM_CACHE_SIZE` defaults to this +erase unit and must be at least that large. In `NVM_FLASH_WRITEONCE` builds, the +cache remains sector-sized because the two trailer copies must stay sector-aligned. + For a specific platform example, the AURIX TC3xx platform has these flash constraints: - The smallest unit it can program is a 32-byte page, and a page can be programmed only once per erase. diff --git a/docs/encrypted_partitions.md b/docs/encrypted_partitions.md index 59b6c85e9f..121d7d21fa 100644 --- a/docs/encrypted_partitions.md +++ b/docs/encrypted_partitions.md @@ -262,8 +262,10 @@ all the needed symbols in RAM. ### Using a custom buffer as encrypt/decrypt cache -By default, encryption support requires a buffer of the same size as the external flash page size to be allocated in RAM. -You can provide a custom pre-allocated buffer by passing its address via the option `ENCRYPT_CACHE`, e.g.: +By default, encryption support uses a buffer sized by `NVM_CACHE_SIZE`. In non-write-once +builds, this defaults to `WOLFBOOT_FLASH_ERASE_UNIT`; in write-once builds it remains +`WOLFBOOT_SECTOR_SIZE`. A custom pre-allocated buffer must be at least `NVM_CACHE_SIZE` +bytes and can be provided by passing its address via `ENCRYPT_CACHE`, e.g.: `ENCRYPT_CACHE=0x20010000` @@ -278,4 +280,3 @@ An example of encrypted update trigger can be found in the [stm32wb test applica - diff --git a/options.mk b/options.mk index 0592944472..60ac81f546 100644 --- a/options.mk +++ b/options.mk @@ -1289,6 +1289,10 @@ ifneq ($(FLASH_WRITE_UNIT),) CFLAGS+= -D"WOLFBOOT_FLASH_WRITE_UNIT=$(FLASH_WRITE_UNIT)" endif +ifneq ($(FLASH_ERASE_UNIT),) + CFLAGS+= -D"WOLFBOOT_FLASH_ERASE_UNIT=$(FLASH_ERASE_UNIT)" +endif + ifeq ($(CUSTOM_PARTITION_TRAILER),1) CFLAGS+= -D"CUSTOM_PARTITION_TRAILER" endif diff --git a/src/libwolfboot.c b/src/libwolfboot.c index bcb5d37c10..98bef9b273 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -161,8 +161,29 @@ int wolfBoot_initialize_encryption(void) # define NULL (void *)0 #endif +#ifndef WOLFBOOT_FLASH_ERASE_UNIT +#define WOLFBOOT_FLASH_ERASE_UNIT WOLFBOOT_SECTOR_SIZE +#endif + +#if WOLFBOOT_FLASH_ERASE_UNIT <= 0 + #error WOLFBOOT_FLASH_ERASE_UNIT must be positive +#elif (WOLFBOOT_FLASH_ERASE_UNIT & (WOLFBOOT_FLASH_ERASE_UNIT - 1)) != 0 + #error WOLFBOOT_FLASH_ERASE_UNIT must be a power of two +#elif (WOLFBOOT_SECTOR_SIZE % WOLFBOOT_FLASH_ERASE_UNIT) != 0 + #error WOLFBOOT_FLASH_ERASE_UNIT must divide WOLFBOOT_SECTOR_SIZE +#endif + #ifndef NVM_CACHE_SIZE -#define NVM_CACHE_SIZE WOLFBOOT_SECTOR_SIZE + #ifdef NVM_FLASH_WRITEONCE + #define NVM_CACHE_SIZE WOLFBOOT_SECTOR_SIZE + #else + #define NVM_CACHE_SIZE WOLFBOOT_FLASH_ERASE_UNIT + #endif +#endif + +#ifndef NVM_FLASH_WRITEONCE +typedef char wolfBoot_encrypt_cache_size_check[ + (NVM_CACHE_SIZE >= WOLFBOOT_FLASH_ERASE_UNIT) ? 1 : -1]; #endif #ifdef BUILD_TOOL @@ -2720,6 +2741,24 @@ void RAMFUNCTION wolfBoot_crypto_set_iv(const uint8_t *nonce, uint32_t iv_counte #ifndef CUSTOM_ENCRYPT_KEY /* Under CUSTOM_ENCRYPT_KEY the platform supplies the key, so the * partition-resident storage below (needs WOLFBOOT_PARTITION_*) is dropped. */ +#if !defined(NVM_FLASH_WRITEONCE) && !defined(MMU) && \ + !defined(WOLFBOOT_RENESAS_TSIP) +static void RAMFUNCTION hal_set_key_cache_copy(uint8_t *cache, + uintptr_t page_addr, uintptr_t field_addr, const uint8_t *data, + size_t length) +{ + uintptr_t page_end = page_addr + WOLFBOOT_FLASH_ERASE_UNIT; + uintptr_t field_end = field_addr + length; + uintptr_t copy_start = field_addr > page_addr ? field_addr : page_addr; + uintptr_t copy_end = field_end < page_end ? field_end : page_end; + + if (copy_start < copy_end) { + XMEMCPY(cache + (copy_start - page_addr), + data + (copy_start - field_addr), copy_end - copy_start); + } +} +#endif + static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) { #ifdef WOLFBOOT_RENESAS_TSIP @@ -2732,10 +2771,11 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) XMEMCPY(ENCRYPT_KEY + ENCRYPT_KEY_SIZE, nonce, ENCRYPT_NONCE_SIZE); return 0; #else - uintptr_t addr, addr_align, addr_off; + uintptr_t addr, addr_align; int ret = 0; - uint32_t trailer_relative_off = 4; #ifdef NVM_FLASH_WRITEONCE + uintptr_t addr_off; + uint32_t trailer_relative_off = 4; int sel_sec = 0; #endif #if !defined(WOLFBOOT_SMALL_STACK) && !defined(NVM_FLASH_WRITEONCE) && \ @@ -2743,30 +2783,21 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) uint8_t ENCRYPT_CACHE[NVM_CACHE_SIZE] XALIGNED_STACK(32); #endif +#ifdef NVM_FLASH_WRITEONCE addr = ENCRYPT_TMP_SECRET_OFFSET + WOLFBOOT_PARTITION_BOOT_ADDRESS; addr_align = addr & (~(WOLFBOOT_SECTOR_SIZE - 1)); addr_off = addr & (WOLFBOOT_SECTOR_SIZE - 1); -#ifdef NVM_FLASH_WRITEONCE /* we read from the populated sector, now write to the erased sector */ sel_sec = nvm_select_fresh_sector(PART_BOOT); addr_align -= (sel_sec * WOLFBOOT_SECTOR_SIZE); -#endif hal_flash_unlock(); /* casting to unsigned long to abide compilers on 64bit architectures */ XMEMCPY(ENCRYPT_CACHE, (void*)(unsigned long)(addr_align), WOLFBOOT_SECTOR_SIZE); -#ifdef NVM_FLASH_WRITEONCE /* we read from the populated sector, now write to the erased sector */ addr_align = addr & (~(WOLFBOOT_SECTOR_SIZE - 1)); addr_align -= (!sel_sec * WOLFBOOT_SECTOR_SIZE); -#else - /* erase the old key */ - ret = hal_flash_erase(addr_align, WOLFBOOT_SECTOR_SIZE); - if (ret != 0) - goto exit_lock; -#endif - /* Populate key + nonce in the cache */ XMEMCPY(ENCRYPT_CACHE + addr_off, k, ENCRYPT_KEY_SIZE); XMEMCPY(ENCRYPT_CACHE + addr_off + ENCRYPT_KEY_SIZE, nonce, @@ -2776,10 +2807,6 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) XMEMCPY(ENCRYPT_CACHE + addr_off - trailer_relative_off, &wolfboot_magic_trail, 4); #ifdef FLAGS_HOME - /* If flags are stored in BOOT partition, take into account the offset - * of the flags used for the update partition too, to avoid erasing the - * sector. - */ trailer_relative_off += (PART_BOOT_ENDFLAGS - PART_UPDATE_ENDFLAGS); XMEMCPY(ENCRYPT_CACHE + addr_off - trailer_relative_off, &wolfboot_magic_trail, 4); @@ -2787,7 +2814,6 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) /* Writing cache back to sector "!sel_sec" */ ret = hal_flash_write(addr_align, ENCRYPT_CACHE, WOLFBOOT_SECTOR_SIZE); -#ifdef NVM_FLASH_WRITEONCE if (ret != 0) goto exit_lock; /* Erasing original sector "sel_sec", @@ -2796,6 +2822,75 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) addr_align = addr & (~(WOLFBOOT_SECTOR_SIZE - 1)); addr_align -= (sel_sec * WOLFBOOT_SECTOR_SIZE); ret = hal_flash_erase(addr_align, WOLFBOOT_SECTOR_SIZE); +#else + uintptr_t boot_base = WOLFBOOT_PARTITION_BOOT_ADDRESS; + uintptr_t boot_end, unit, first_addr, end_addr, first_unit, last_unit; +#ifdef FLAGS_HOME + uintptr_t flag_magic_off, flag_magic_addr; +#endif + + if (boot_base > UINTPTR_MAX - WOLFBOOT_PARTITION_SIZE) + return -1; + boot_end = boot_base + WOLFBOOT_PARTITION_SIZE; + unit = WOLFBOOT_FLASH_ERASE_UNIT; + if ((boot_base % unit) != 0 || + (WOLFBOOT_PARTITION_SIZE % unit) != 0 || + (uintptr_t)ENCRYPT_TMP_SECRET_OFFSET > boot_end - boot_base) + return -1; + addr = boot_base + ENCRYPT_TMP_SECRET_OFFSET; + if (addr < boot_base + sizeof(uint32_t) || + (size_t)ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE > boot_end - addr) + return -1; + first_addr = addr - sizeof(uint32_t); + end_addr = addr + ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE; + +#ifdef FLAGS_HOME + if ((uintptr_t)PART_BOOT_ENDFLAGS < (uintptr_t)PART_UPDATE_ENDFLAGS) + return -1; + flag_magic_off = (uintptr_t)PART_BOOT_ENDFLAGS - + (uintptr_t)PART_UPDATE_ENDFLAGS; + if (flag_magic_off > addr - boot_base - sizeof(uint32_t)) + return -1; + flag_magic_addr = addr - sizeof(uint32_t) - flag_magic_off; + first_addr = flag_magic_addr; +#endif + first_unit = first_addr & ~(unit - 1); + last_unit = (end_addr - 1) & ~(unit - 1); + + hal_flash_unlock(); + for (addr_align = first_unit; addr_align <= last_unit; + addr_align += unit) { + if (addr_align < boot_base || addr_align >= boot_end || + unit > boot_end - addr_align) { + ret = -1; + goto exit_lock; + } + XMEMCPY(ENCRYPT_CACHE, + (void *)(unsigned long)addr_align, unit); + ret = hal_flash_erase(addr_align, unit); + if (ret != 0) + goto exit_lock; + +#ifdef FLAGS_HOME + hal_set_key_cache_copy((uint8_t *)(uintptr_t)ENCRYPT_CACHE, + addr_align, flag_magic_addr, + (const uint8_t *)&wolfboot_magic_trail, + sizeof(wolfboot_magic_trail)); +#endif + hal_set_key_cache_copy((uint8_t *)(uintptr_t)ENCRYPT_CACHE, + addr_align, addr - sizeof(uint32_t), + (const uint8_t *)&wolfboot_magic_trail, + sizeof(wolfboot_magic_trail)); + hal_set_key_cache_copy((uint8_t *)(uintptr_t)ENCRYPT_CACHE, + addr_align, addr, k, ENCRYPT_KEY_SIZE); + hal_set_key_cache_copy((uint8_t *)(uintptr_t)ENCRYPT_CACHE, + addr_align, addr + ENCRYPT_KEY_SIZE, nonce, + ENCRYPT_NONCE_SIZE); + + ret = hal_flash_write(addr_align, ENCRYPT_CACHE, unit); + if (ret != 0) + goto exit_lock; + } #endif exit_lock: /* Scrub the staged key/nonce on every build where wolfBoot owns the @@ -3402,8 +3497,8 @@ static uint8_t RAMFUNCTION part_address(uintptr_t a) * written per pass must be a block multiple: a partial block at the end of a * pass would be written as stale cache content and would leave the address * unaligned, desynchronising the keystream from ext_flash_decrypt_read(). - * NVM_CACHE_SIZE defaults to WOLFBOOT_SECTOR_SIZE, which is always a multiple, - * but it can be overridden. */ + * NVM_CACHE_SIZE defaults to the erase unit in non-write-once builds and to + * WOLFBOOT_SECTOR_SIZE in write-once builds. */ #define ENCRYPT_STAGE_SIZE \ ((NVM_CACHE_SIZE) - ((NVM_CACHE_SIZE) % ENCRYPT_BLOCK_SIZE)) typedef char wolfBoot_encrypt_stage_size_check[ diff --git a/tools/config.mk b/tools/config.mk index e14724025e..3215c26dc1 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -121,7 +121,8 @@ WATCHDOG_HANDOFF?=0 CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO_DRIVERS \ MCUXPRESSO_CMSIS FREEDOM_E_SDK STM32CUBE CYPRESS_PDL CYPRESS_CORE_LIB CYPRESS_TARGET_LIB DEBUG VTOR \ CORTEX_M0 CORTEX_M7 CORTEX_M23 CORTEX_M33 CORTEX_M55 NO_ASM EXT_FLASH SPI_FLASH NO_XIP UART_FLASH ALLOW_DOWNGRADE NVM_FLASH_WRITEONCE CUSTOM_PARTITION_TRAILER \ - NVM_FLASH_JOURNAL NVM_FLASH_JOURNAL_DUAL NVM_FLASH_ECC FLASH_WRITE_UNIT \ + NVM_FLASH_JOURNAL NVM_FLASH_JOURNAL_DUAL NVM_FLASH_ECC \ + FLASH_WRITE_UNIT FLASH_ERASE_UNIT \ DISABLE_BACKUP WOLFBOOT_VERSION V NO_MPU ENCRYPT FLAGS_HOME FLAGS_INVERT \ SPMATH SPMATHALL RAM_CODE DUALBANK_SWAP IMAGE_HEADER_SIZE PKA TZEN PSOC6_CRYPTO \ WOLFTPM WOLFBOOT_TPM_VERIFY MEASURED_BOOT WOLFBOOT_TPM_SEAL WOLFBOOT_TPM_KEYSTORE WOLFBOOT_SECURE_APP WOLFBOOT_SECURE_HANDOFF_ADDRESS \ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 12a69a11e7..ddc14fa909 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -62,6 +62,8 @@ TESTS:=unit-parser unit-parser-large-header unit-fdt unit-extflash unit-string \ unit-aes256 unit-chacha20 unit-pci unit-pci-4gib unit-mock-state \ unit-sectorflags unit-max-space \ unit-image unit-image-hybrid unit-image-rsa unit-nvm unit-nvm-flagshome unit-enc-nvm \ + unit-enc-key unit-enc-key-aes256 unit-enc-key-straddle \ + unit-enc-key-sector unit-enc-key-flagshome \ unit-enc-nvm-flagshome unit-delta unit-gzip unit-update-flash unit-update-flash-delta \ unit-update-flash-db \ unit-update-flash-hook \ @@ -369,6 +371,36 @@ unit-enc-nvm:WOLFCRYPT_SRC+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c unit-enc-nvm-flagshome:CFLAGS+=-DNVM_FLASH_WRITEONCE -DMOCK_PARTITIONS \ -DEXT_ENCRYPTED -DENCRYPT_WITH_CHACHA -DEXT_FLASH -DHAVE_CHACHA -DFLAGS_HOME unit-enc-nvm-flagshome:WOLFCRYPT_SRC+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c +unit-enc-key:CFLAGS+=-DMOCK_PARTITIONS -DEXT_FLASH -DEXT_ENCRYPTED \ + -DENCRYPT_WITH_CHACHA -DHAVE_CHACHA -DWOLFBOOT_SMALL_STACK \ + -DWOLFBOOT_SECTOR_SIZE=0x1000 -DWOLFBOOT_FLASH_ERASE_UNIT=128 \ + -DMOCK_FLASH_ERASE_UNIT=128 +unit-enc-key:WOLFCRYPT_SRC+= \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c +unit-enc-key-aes256:CFLAGS+=-DMOCK_PARTITIONS -DEXT_FLASH -DEXT_ENCRYPTED \ + -DENCRYPT_WITH_AES256 -DWOLFSSL_AES_DIRECT -DWOLFSSL_AES_256 \ + -DWOLFSSL_AES_COUNTER -DWOLFBOOT_SMALL_STACK \ + -DWOLFBOOT_SECTOR_SIZE=0x1000 -DWOLFBOOT_FLASH_ERASE_UNIT=128 \ + -DMOCK_FLASH_ERASE_UNIT=128 +unit-enc-key-aes256:WOLFCRYPT_SRC+= \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/aes.c +unit-enc-key-straddle:CFLAGS+=-DMOCK_PARTITIONS -DEXT_FLASH -DEXT_ENCRYPTED \ + -DENCRYPT_WITH_CHACHA -DHAVE_CHACHA -DWOLFBOOT_SMALL_STACK \ + -DWOLFBOOT_SECTOR_SIZE=0x1000 -DWOLFBOOT_FLASH_ERASE_UNIT=64 \ + -DMOCK_FLASH_ERASE_UNIT=64 -DTRAILER_SKIP=24 +unit-enc-key-straddle:WOLFCRYPT_SRC+= \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c +unit-enc-key-sector:CFLAGS+=-DMOCK_PARTITIONS -DEXT_FLASH -DEXT_ENCRYPTED \ + -DENCRYPT_WITH_CHACHA -DHAVE_CHACHA -DWOLFBOOT_SMALL_STACK \ + -DWOLFBOOT_SECTOR_SIZE=0x1000 -DMOCK_FLASH_ERASE_UNIT=0x1000 +unit-enc-key-sector:WOLFCRYPT_SRC+= \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c +unit-enc-key-flagshome:CFLAGS+=-DMOCK_PARTITIONS -DEXT_FLASH -DEXT_ENCRYPTED \ + -DENCRYPT_WITH_CHACHA -DHAVE_CHACHA -DWOLFBOOT_SMALL_STACK \ + -DWOLFBOOT_SECTOR_SIZE=0x1000 -DWOLFBOOT_FLASH_ERASE_UNIT=128 \ + -DMOCK_FLASH_ERASE_UNIT=128 -DFLAGS_HOME +unit-enc-key-flagshome:WOLFCRYPT_SRC+= \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c unit-delta:CFLAGS+=-DNVM_FLASH_WRITEONCE -DMOCK_PARTITIONS -DDELTA_UPDATES -DDELTA_BLOCK_SIZE=512 unit-pkcs11_store:CFLAGS+=-I$(WOLFBOOT_LIB_WOLFPKCS11) -DMOCK_PARTITIONS -DMOCK_KEYVAULT -DSECURE_PKCS11 -DWOLFPKCS11_USER_SETTINGS # Same suite against a flash-read cache that only refreshes on @@ -1140,6 +1172,12 @@ unit-enc-nvm: ../../include/target.h unit-enc-nvm.c unit-enc-nvm-flagshome: ../../include/target.h unit-enc-nvm.c gcc -o $@ $(WOLFCRYPT_SRC) unit-enc-nvm.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) +unit-enc-key unit-enc-key-aes256 unit-enc-key-straddle \ +unit-enc-key-sector unit-enc-key-flagshome: \ + ../../include/target.h unit-enc-key.c + gcc -o $@ $(WOLFCRYPT_SRC) unit-enc-key.c $(CFLAGS) \ + $(WOLFCRYPT_CFLAGS) $(LDFLAGS) + unit-delta: ../../include/target.h unit-delta.c gcc -o $@ unit-delta.c $(CFLAGS) $(LDFLAGS) diff --git a/tools/unit-tests/unit-enc-key.c b/tools/unit-tests/unit-enc-key.c new file mode 100644 index 0000000000..933b4c1701 --- /dev/null +++ b/tools/unit-tests/unit-enc-key.c @@ -0,0 +1,238 @@ +/* unit-enc-key.c + * + * Unit tests for encrypted key storage on small flash erase units. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfBoot; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#define WOLFBOOT_HASH_SHA256 +#define IMAGE_HEADER_SIZE 256 +#define MOCK_ADDRESS 0xCC000000 +#define MOCK_ADDRESS_BOOT 0xCD000000 +#define MOCK_ADDRESS_SWAP 0xCE000000 +const char ENCRYPT_KEY[] = "0123456789abcdef0123456789abcdef0123456789abcdef"; + +#include +#include +#include +#include "image.h" +#include "encrypt.h" +#include "libwolfboot.c" +#include +#include +#include +#include + +const char *argv0; + +#include "unit-mock-flash.c" + +static void reset_erase_log(void) +{ + mock_flash_erase_count = 0; + mock_flash_erase_violations = 0; +} + +static void mark_expected_range(uint8_t *expected, + uintptr_t address, size_t length) +{ + uintptr_t unit = address & ~(MOCK_FLASH_ERASE_UNIT - 1); + uintptr_t last = (address + length - 1) & ~(MOCK_FLASH_ERASE_UNIT - 1); + + while (unit <= last) { + size_t index = (unit - WOLFBOOT_PARTITION_BOOT_ADDRESS) / + MOCK_FLASH_ERASE_UNIT; + expected[index] = 1; + unit += MOCK_FLASH_ERASE_UNIT; + } +} + +static void check_erase_log(void) +{ + uint8_t expected[WOLFBOOT_PARTITION_SIZE / MOCK_FLASH_ERASE_UNIT] = { 0 }; + uintptr_t base = WOLFBOOT_PARTITION_BOOT_ADDRESS; + uintptr_t key_addr = base + ENCRYPT_TMP_SECRET_OFFSET; + uintptr_t magic_addr = key_addr - sizeof(uint32_t); + size_t i, expected_count = 0; + +#ifdef FLAGS_HOME + mark_expected_range(expected, magic_addr - + (PART_BOOT_ENDFLAGS - PART_UPDATE_ENDFLAGS), sizeof(uint32_t)); +#endif + mark_expected_range(expected, magic_addr, sizeof(uint32_t)); + mark_expected_range(expected, key_addr, ENCRYPT_KEY_SIZE); + mark_expected_range(expected, key_addr + ENCRYPT_KEY_SIZE, + ENCRYPT_NONCE_SIZE); + + for (i = 0; i < sizeof(expected); i++) { + if (!expected[i]) + continue; + expected_count++; + ck_assert_msg(mock_flash_erased_address[expected_count - 1] == + base + i * MOCK_FLASH_ERASE_UNIT, + "unexpected erase address at log index %lu", + (unsigned long)(expected_count - 1)); + ck_assert_int_eq(mock_flash_erased_length[expected_count - 1], + MOCK_FLASH_ERASE_UNIT); + } + ck_assert_int_eq(mock_flash_erase_count, expected_count); + ck_assert_int_eq(mock_flash_erase_violations, 0); +} + +static void check_flash_contents(const uint8_t *key, const uint8_t *nonce, + int erased) +{ + const uint8_t *boot = (const uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS; + uintptr_t base = WOLFBOOT_PARTITION_BOOT_ADDRESS; + uintptr_t key_addr = base + ENCRYPT_TMP_SECRET_OFFSET; + uintptr_t magic_addr = key_addr - sizeof(uint32_t); + uint8_t magic[sizeof(uint32_t)]; + size_t i; + + XMEMCPY(magic, &wolfboot_magic_trail, sizeof(magic)); + for (i = 0; i < WOLFBOOT_PARTITION_SIZE; i++) { + uint8_t expected = 0x5A; + +#ifdef FLAGS_HOME + uintptr_t flags_magic = magic_addr - + (PART_BOOT_ENDFLAGS - PART_UPDATE_ENDFLAGS); + if (i >= flags_magic - base && + i < flags_magic - base + sizeof(magic)) + expected = magic[i - (flags_magic - base)]; +#endif + if (i >= magic_addr - base && + i < magic_addr - base + sizeof(magic)) + expected = magic[i - (magic_addr - base)]; + if (i >= key_addr - base && + i < key_addr - base + ENCRYPT_KEY_SIZE) + expected = erased ? 0xFF : key[i - (key_addr - base)]; + if (i >= key_addr - base + ENCRYPT_KEY_SIZE && + i < key_addr - base + ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE) + expected = erased ? 0xFF : + nonce[i - (key_addr - base) - ENCRYPT_KEY_SIZE]; + ck_assert_msg(boot[i] == expected, + "unexpected byte at BOOT offset 0x%lx: got 0x%02x expected 0x%02x", + (unsigned long)i, boot[i], expected); + } +} + +static void check_cache_scrubbed(void) +{ + size_t i; + + for (i = 0; i < sizeof(ENCRYPT_CACHE); i++) + ck_assert_msg(ENCRYPT_CACHE[i] == 0, + "encryption cache byte %lu was not scrubbed", + (unsigned long)i); +} + +START_TEST(test_set_and_erase_encrypt_key) +{ + static const uint8_t key[ENCRYPT_KEY_SIZE] = { 0x11 }; + static const uint8_t nonce[ENCRYPT_NONCE_SIZE] = { 0x22 }; + uint8_t *boot = (uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS; + int ret; + + ret = mmap_file("/tmp/wolfboot-unit-enc-key-update.bin", + (uint8_t *)MOCK_ADDRESS, WOLFBOOT_PARTITION_SIZE, NULL); + ck_assert_int_ge(ret, 0); + ret = mmap_file("/tmp/wolfboot-unit-enc-key-boot.bin", + (uint8_t *)MOCK_ADDRESS_BOOT, WOLFBOOT_PARTITION_SIZE, NULL); + ck_assert_int_ge(ret, 0); + ret = mmap_file("/tmp/wolfboot-unit-enc-key-swap.bin", + (uint8_t *)MOCK_ADDRESS_SWAP, WOLFBOOT_SECTOR_SIZE, NULL); + ck_assert_int_ge(ret, 0); + + memset(boot, 0x5A, WOLFBOOT_PARTITION_SIZE); + reset_erase_log(); + ret = wolfBoot_set_encrypt_key(key, nonce); + ck_assert_int_eq(ret, 0); + ck_assert(locked); + check_erase_log(); + check_flash_contents(key, nonce, 0); + check_cache_scrubbed(); + + reset_erase_log(); + ret = wolfBoot_erase_encrypt_key(); + ck_assert_int_eq(ret, 0); + ck_assert(locked); + check_erase_log(); + check_flash_contents(key, nonce, 1); + check_cache_scrubbed(); + + reset_erase_log(); + hal_flash_erase_fail = 1; + ret = wolfBoot_set_encrypt_key(key, nonce); + ck_assert_int_eq(ret, -1); + ck_assert(locked); + check_cache_scrubbed(); + + hal_flash_write_fail = 1; + ret = wolfBoot_set_encrypt_key(key, nonce); + ck_assert_int_eq(ret, -1); + ck_assert(locked); + check_cache_scrubbed(); + + ret = wolfBoot_set_encrypt_key(key, nonce); + ck_assert_int_eq(ret, 0); + ck_assert(locked); + check_cache_scrubbed(); + + hal_flash_erase_fail = 1; + ret = wolfBoot_erase_encrypt_key(); + ck_assert_int_eq(ret, -1); + ck_assert(locked); + check_cache_scrubbed(); + + ret = wolfBoot_set_encrypt_key(key, nonce); + ck_assert_int_eq(ret, 0); + ck_assert(locked); + check_cache_scrubbed(); + + hal_flash_write_fail = 1; + ret = wolfBoot_erase_encrypt_key(); + ck_assert_int_eq(ret, -1); + ck_assert(locked); + check_cache_scrubbed(); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *suite = suite_create("encrypted key erase unit"); + TCase *test_case = tcase_create("key storage"); + + tcase_add_test(test_case, test_set_and_erase_encrypt_key); + suite_add_tcase(suite, test_case); + return suite; +} + +int main(int argc, char *argv[]) +{ + int failures; + SRunner *runner; + + (void)argc; + argv0 = strdup(argv[0]); + runner = srunner_create(wolfboot_suite()); + srunner_run_all(runner, CK_NORMAL); + failures = srunner_ntests_failed(runner); + srunner_free(runner); + free((void *)argv0); + return failures; +} diff --git a/tools/unit-tests/unit-mock-flash.c b/tools/unit-tests/unit-mock-flash.c index 0af2e81eea..daee325d03 100644 --- a/tools/unit-tests/unit-mock-flash.c +++ b/tools/unit-tests/unit-mock-flash.c @@ -35,6 +35,13 @@ static int erased_nvm_bank0 = 0; static int erased_nvm_bank1 = 0; static int erased_vault = 0; static int hal_flash_write_fail = 0; +#ifdef MOCK_FLASH_ERASE_UNIT +static int hal_flash_erase_fail = 0; +static unsigned int mock_flash_erase_count = 0; +static unsigned int mock_flash_erase_violations = 0; +static uintptr_t mock_flash_erased_address[16]; +static int mock_flash_erased_length[16]; +#endif const char *argv0; #ifdef NVM_FLASH_ECC @@ -254,6 +261,24 @@ int hal_flash_write(haladdr_t address, const uint8_t *data, int len) int hal_flash_erase(haladdr_t address, int len) { ck_assert_msg(!locked, "Attempting to erase a locked FLASH"); +#ifdef MOCK_FLASH_ERASE_UNIT + if (hal_flash_erase_fail) { + hal_flash_erase_fail = 0; + return -1; + } + if (len <= 0 || + (address % MOCK_FLASH_ERASE_UNIT) != 0 || + (len % MOCK_FLASH_ERASE_UNIT) != 0 || + mock_flash_erase_count >= + sizeof(mock_flash_erased_address) / + sizeof(mock_flash_erased_address[0])) { + mock_flash_erase_violations++; + return -1; + } + mock_flash_erased_address[mock_flash_erase_count] = address; + mock_flash_erased_length[mock_flash_erase_count] = len; + mock_flash_erase_count++; +#endif if ((address >= WOLFBOOT_PARTITION_BOOT_ADDRESS) && (address < WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE)) { #ifdef NVM_FLASH_ECC