diff --git a/.github/workflows/trustzone-emulator-tests.yml b/.github/workflows/trustzone-emulator-tests.yml index 432421dc0a..469909f18c 100644 --- a/.github/workflows/trustzone-emulator-tests.yml +++ b/.github/workflows/trustzone-emulator-tests.yml @@ -119,6 +119,52 @@ jobs: grep -q "\\[BKPT\\] imm=0x7f" /tmp/m33mu-fwtpm.log grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-fwtpm.log + - name: Clean and build test with wolfHSM (stm32h5) + run: | + make clean distclean + cp config/examples/stm32h5-tz-wolfhsm.config .config + make + + - name: Prepare wolfHSM persistence directory + run: | + rm -rf /tmp/m33mu-wolfhsm-persist + mkdir -p /tmp/m33mu-wolfhsm-persist + rm -f /tmp/m33mu-wolfhsm-first.log /tmp/m33mu-wolfhsm-second.log + + - name: Run wolfHSM first boot (stm32h5) + run: | + cd /tmp/m33mu-wolfhsm-persist + m33mu "$GITHUB_WORKSPACE/wolfboot.bin" \ + "$GITHUB_WORKSPACE/test-app/image_v1_signed.bin:0x60000" \ + --persist --uart-stdout --timeout 120 --expect-bkpt 0x7d \ + | tee /tmp/m33mu-wolfhsm-first.log + + - name: Verify wolfHSM first boot (stm32h5) + run: | + grep -q "wolfHSM CommInit ok" /tmp/m33mu-wolfhsm-first.log + grep -q "wolfHSM RNG ok:" /tmp/m33mu-wolfhsm-first.log + grep -q "wolfHSM SHA256 ok" /tmp/m33mu-wolfhsm-first.log + grep -q "wolfHSM AES ok" /tmp/m33mu-wolfhsm-first.log + grep -q "wolfHSM first boot path, committing key to NVM" /tmp/m33mu-wolfhsm-first.log + grep -q "wolfHSM NSC tests passed" /tmp/m33mu-wolfhsm-first.log + grep -q "\\[BKPT\\] imm=0x7d" /tmp/m33mu-wolfhsm-first.log + grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-wolfhsm-first.log + + - name: Run wolfHSM second boot (stm32h5) + run: | + cd /tmp/m33mu-wolfhsm-persist + m33mu "$GITHUB_WORKSPACE/wolfboot.bin" \ + "$GITHUB_WORKSPACE/test-app/image_v1_signed.bin:0x60000" \ + --persist --uart-stdout --timeout 120 --expect-bkpt 0x7f \ + | tee /tmp/m33mu-wolfhsm-second.log + + - name: Verify wolfHSM second boot (stm32h5) + run: | + grep -q "wolfHSM second boot path, restored persisted key" /tmp/m33mu-wolfhsm-second.log + grep -q "wolfHSM NSC tests passed" /tmp/m33mu-wolfhsm-second.log + grep -q "\\[BKPT\\] imm=0x7f" /tmp/m33mu-wolfhsm-second.log + grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-wolfhsm-second.log + - name: Clean and build test with DICE attestation + OTP (stm32h5) run: | make clean distclean diff --git a/config/examples/stm32h5-tz-wolfhsm.config b/config/examples/stm32h5-tz-wolfhsm.config new file mode 100644 index 0000000000..92184707ef --- /dev/null +++ b/config/examples/stm32h5-tz-wolfhsm.config @@ -0,0 +1,35 @@ +ARCH?=ARM +TZEN?=1 +TARGET?=stm32h5 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +CORTEX_M33?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +WOLFBOOT_PARTITION_SIZE?=0xA0000 +WOLFBOOT_SECTOR_SIZE?=0x2000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x0C040000 +WOLFBOOT_KEYVAULT_SIZE?=0x1C000 +WOLFBOOT_NSC_ADDRESS?=0x0C05C000 +WOLFBOOT_NSC_SIZE?=0x4000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08060000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x0C100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x0C1A0000 +FLAGS_HOME=0 +DISABLE_BACKUP=0 +WOLFCRYPT_TZ=1 +WOLFCRYPT_TZ_WOLFHSM=1 +IMAGE_HEADER_SIZE?=1024 +ARMORED=1 diff --git a/docs/STM32-TZ.md b/docs/STM32-TZ.md index a20e0986d3..982546d3df 100644 --- a/docs/STM32-TZ.md +++ b/docs/STM32-TZ.md @@ -33,6 +33,19 @@ The `WOLFCRYPT_TZ_PSA` option provides a standard PSA Crypto interface using wolfPSA in the secure domain. The key storage uses the same secure flash keystore backend as PKCS11, exposed through the wolfPSA store API. +### wolfHSM API in non-secure world + +The `WOLFCRYPT_TZ_WOLFHSM` option hosts a wolfHSM server inside the secure +domain and exposes it to non-secure applications through a single non-secure +callable veneer. Non-secure code uses the standard wolfCrypt API with the +wolfHSM client cryptocb registered under `WH_DEV_ID`; key material, the +keystore, and crypto operations stay in the secure domain. Persistent keys +live in the same secure flash keystore region used by PKCS11 and PSA, with +two-partition journaling for power-fail safety. + +See [wolfHSM](wolfHSM.md) for the full configuration, build, flash, and +test recipe on STM32H5. + ### PSA Initial Attestation (DICE) When `WOLFCRYPT_TZ_PSA=1` is enabled, wolfBoot exposes the PSA Initial diff --git a/docs/wolfHSM.md b/docs/wolfHSM.md index b6fafbcb94..39827473e8 100644 --- a/docs/wolfHSM.md +++ b/docs/wolfHSM.md @@ -21,6 +21,7 @@ wolfBoot supports using wolfHSM on the following platforms: - wolfBoot simulator (using wolfHSM POSIX TCP transport) - AURIX TC3xx (shared memory transport) +- STM32H5 TrustZone (the secure-side wolfBoot hosts a wolfHSM server and exposes it to the non-secure application through a single NSC veneer; see [STM32H5 TrustZone Engine](#stm32h5-trustzone-engine) below) Details on configuring wolfBoot to use wolfHSM on each of these platforms can be found in the wolfBoot (and wolfHSM) documentation specific to that target, with the exception of the simulator, which is documented here. The remainder of this document focuses on the generic wolfHSM-related configuration options. @@ -238,3 +239,51 @@ When using wolfHSM server mode, no external server is required. wolfBoot include ``` The embedded wolfHSM server will automatically handle all cryptographic operations and key management using the file-based NVM storage(`wolfBoot_wolfHSM_NVM.bin`) that was generated above. + +## STM32H5 TrustZone Engine + +On STM32H5, wolfBoot can host a wolfHSM server in the secure TrustZone image and expose it to the non-secure application through a single non-secure-callable veneer (`wcs_wolfhsm_transmit`). The non-secure side runs the standard wolfHSM client API, which auto-registers a wolfCrypt cryptocb under `WH_DEV_ID`, so application-level wolfCrypt calls that pass that device ID transparently round-trip to the secure server. + +This is a separate deployment shape from the wolfHSM client/server modes documented above; it does not use `WOLFBOOT_ENABLE_WOLFHSM_CLIENT/SERVER` or the `hsmClientCtx`/`hsmServerCtx` HAL hooks, and is mutually exclusive with the other STM32H5 TrustZone engines (`WOLFCRYPT_TZ_PKCS11`, `WOLFCRYPT_TZ_PSA`, `WOLFCRYPT_TZ_FWTPM`). + +### Build + +```sh +cp config/examples/stm32h5-tz-wolfhsm.config .config +make +``` + +For on-board hardware testing, add `WOLFBOOT_TZ_TEST_NO_BKPT=1` so the auto-test prints a UART pass/fail line and idles in `while (1)` instead of issuing `bkpt #0x7f` (which HardFaults on real silicon without a debugger): + +```sh +make WOLFBOOT_TZ_TEST_NO_BKPT=1 +``` + +### Flash + +The wolfBoot helper programs the option bytes the secure boot path requires (`TZEN`, `SECBOOTADD`, `SECWM1`/`SECWM2`); see [STM32-TZ.md](STM32-TZ.md) for the option-byte details: + +```sh +./tools/scripts/set-stm32-tz-option-bytes.sh +STM32_Programmer_CLI -c port=swd -d wolfboot.bin 0x0C000000 +STM32_Programmer_CLI -c port=swd -d test-app/image_v1_signed.bin 0x08060000 +``` + +### Test + +The non-secure test application runs the wolfHSM auto-test at startup. A successful first boot ends with: + +```text +wolfHSM CommInit ok (client=1 server=...) +wolfHSM RNG ok: <16 random bytes> +wolfHSM SHA256 ok +wolfHSM AES ok +wolfHSM first boot path, committing key to NVM +wolfHSM NSC tests passed +``` + +The default build raises `bkpt #0x7d` on first-boot success and `bkpt #0x7f` on second-boot success (after the persisted key is reloaded from flash on reset). The `WOLFBOOT_TZ_TEST_NO_BKPT=1` build prints a final `WOLFHSM_TZ_TEST_PASS` UART line instead. Reset the board (no re-flash) to verify persistence; the second boot prints `wolfHSM second boot path, restored persisted key`. + +### Notes + +The wolfHSM NVM lives in the existing `FLASH_KEYVAULT` region (112 KiB at `0x0C040000`) shared with the other STM32H5 TrustZone engines. The flash adapter (`src/wolfhsm_flash_hal.c`) caches the affected sector, modifies it, and rewrites the whole 8 KiB sector in one erase + program cycle, mirroring `psa_store.c` / `pkcs11_store.c`. This satisfies the H5 quad-word ECC rule that each 16-byte unit may be programmed exactly once between erases, which wolfHSM's 8-byte-unit writes would otherwise violate. diff --git a/include/user_settings.h b/include/user_settings.h index b4eb7150c4..0c804e91e3 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -756,12 +756,14 @@ extern int tolower(int c); #endif #if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) || \ - defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) + defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) || \ + defined(WOLFCRYPT_TZ_WOLFHSM) # define WOLF_CRYPTO_CB # undef HAVE_ANONYMOUS_INLINE_AGGREGATES # define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 # define WOLFSSL_KEY_GEN -#endif /* WOLFBOOT_ENABLE_WOLFHSM_CLIENT || WOLFBOOT_ENABLE_WOLFHSM_SERVER */ +#endif /* WOLFBOOT_ENABLE_WOLFHSM_CLIENT || WOLFBOOT_ENABLE_WOLFHSM_SERVER || + WOLFCRYPT_TZ_WOLFHSM */ #if defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) && \ defined(WOLFBOOT_CERT_CHAIN_VERIFY) diff --git a/include/wolfboot/wcs_wolfhsm.h b/include/wolfboot/wcs_wolfhsm.h new file mode 100644 index 0000000000..da65c33dc3 --- /dev/null +++ b/include/wolfboot/wcs_wolfhsm.h @@ -0,0 +1,23 @@ +/* wcs_wolfhsm.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifndef WOLFBOOT_WCS_WOLFHSM_H +#define WOLFBOOT_WCS_WOLFHSM_H + +#include +#include "wolfboot/wc_secure.h" + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, + uint8_t *rsp, uint32_t *rspSz); + +void wcs_wolfhsm_init(void); + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ + +#endif /* WOLFBOOT_WCS_WOLFHSM_H */ diff --git a/include/wolfboot/wolfhsm_flash_hal.h b/include/wolfboot/wolfhsm_flash_hal.h new file mode 100644 index 0000000000..77720ff5b6 --- /dev/null +++ b/include/wolfboot/wolfhsm_flash_hal.h @@ -0,0 +1,30 @@ +/* wolfhsm_flash_hal.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifndef WOLFBOOT_WOLFHSM_FLASH_HAL_H +#define WOLFBOOT_WOLFHSM_FLASH_HAL_H + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include + +#include "wolfhsm/wh_flash.h" + +/* Per-call config / context for the adapter. base/size/partition_size are + * the only client-visible fields; the cache lives inside the static + * implementation in wolfhsm_flash_hal.c (mirroring psa_store.c). */ +typedef struct { + uint32_t base; + uint32_t size; + uint32_t partition_size; +} whFlashH5Ctx; + +extern const whFlashCb whFlashH5_Cb; + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ + +#endif /* WOLFBOOT_WOLFHSM_FLASH_HAL_H */ diff --git a/options.mk b/options.mk index b70cb4e6b8..22962288e9 100644 --- a/options.mk +++ b/options.mk @@ -1,4 +1,43 @@ WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/asn.o + +# Shared wolfHSM client/server object lists. Defined here at the top so any +# downstream block (legacy WOLFHSM_CLIENT/SERVER, or WOLFCRYPT_TZ_WOLFHSM TZ +# engine) can reference them by variable name without ordering hazards. +WOLFHSM_CLIENT_OBJS := \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_cryptocb.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_dma.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_dma.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_utils.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_comm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_comm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_customcb.o + +WOLFHSM_SERVER_OBJS := \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_utils.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_comm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_nvm_flash.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_keyid.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_flash_unit.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_counter.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_keystore.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_customcb.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_customcb.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_keystore.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_counter.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_comm.o + USE_CLANG?=0 ifeq ($(USE_CLANG),1) USE_GCC?=0 @@ -919,12 +958,24 @@ ifeq ($(WOLFCRYPT_TZ_PKCS11),1) ifeq ($(WOLFCRYPT_TZ_FWTPM),1) $(error WOLFCRYPT_TZ_PKCS11 and WOLFCRYPT_TZ_FWTPM are mutually exclusive) endif + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + $(error WOLFCRYPT_TZ_PKCS11 and WOLFCRYPT_TZ_WOLFHSM are mutually exclusive) + endif endif ifeq ($(WOLFCRYPT_TZ_PSA),1) ifeq ($(WOLFCRYPT_TZ_FWTPM),1) $(error WOLFCRYPT_TZ_PSA and WOLFCRYPT_TZ_FWTPM are mutually exclusive) endif + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + $(error WOLFCRYPT_TZ_PSA and WOLFCRYPT_TZ_WOLFHSM are mutually exclusive) + endif +endif + +ifeq ($(WOLFCRYPT_TZ_FWTPM),1) + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + $(error WOLFCRYPT_TZ_FWTPM and WOLFCRYPT_TZ_WOLFHSM are mutually exclusive) + endif endif ifeq ($(WOLFCRYPT_TZ_PKCS11),1) @@ -1078,6 +1129,47 @@ ifeq ($(WOLFCRYPT_TZ_FWTPM),1) STACK_USAGE=20000 endif +ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + CFLAGS+=-DWOLFCRYPT_TZ_WOLFHSM + CFLAGS+=-DWOLFCRYPT_SECURE_MODE + CFLAGS+=-DWOLFHSM_CFG_ENABLE_SERVER + CFLAGS+=-DWOLFHSM_CFG_COMM_DATA_LEN=1280 + CFLAGS+=-DWOLFHSM_CFG_PORT_STM32_TZ_NSC + CFLAGS+=-DWOLFHSM_CFG_NO_SYS_TIME + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz" + ifeq ($(USE_CLANG),1) + CLANG_MULTILIB_FLAGS:=$(filter -mthumb -mlittle-endian,$(LDFLAGS)) $(filter -mcpu=%,$(CFLAGS)) + LIBS+=$(shell $(CLANG_GCC_NAME) $(CLANG_MULTILIB_FLAGS) -print-file-name=libc.a) + LIBS+=$(shell $(CLANG_GCC_NAME) $(CLANG_MULTILIB_FLAGS) -print-libgcc-file-name) + else + LDFLAGS+=--specs=nano.specs + endif + WOLFCRYPT_OBJS+=src/store_sbrk.o + WOLFCRYPT_OBJS+=src/wolfhsm_callable.o + WOLFCRYPT_OBJS+=src/wolfhsm_flash_hal.o + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/cryptocb.o + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/coding.o + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/hmac.o + ifneq ($(SIGN),ED25519) + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha512.o + endif + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_encrypt.o + ifeq ($(ENCRYPT_WITH_AES128)$(ENCRYPT_WITH_AES256),) + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/aes.o + endif + WOLFCRYPT_OBJS+=$(RSA_OBJS) + ifeq ($(findstring ECC,$(SIGN)),) + ifeq ($(findstring ECC,$(SIGN_SECONDARY)),) + WOLFCRYPT_OBJS+=$(ECC_OBJS) + WOLFCRYPT_OBJS+=$(MATH_OBJS) + endif + endif + WOLFHSM_OBJS+=$(WOLFHSM_SERVER_OBJS) + WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz/wh_transport_nsc.o + STACK_USAGE=20000 +endif + OBJS+=$(PUBLIC_KEY_OBJS) ifneq ($(STAGE1),1) OBJS+=$(UPDATE_OBJS) @@ -1289,19 +1381,7 @@ ifeq ($(WOLFHSM_CLIENT),1) CFLAGS += -DWOLFHSM_CFG_COMM_DATA_LEN=5000 endif - WOLFHSM_OBJS += \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_nvm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_cryptocb.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_crypto.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_dma.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_crypto.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_dma.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_utils.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_comm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_comm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_nvm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_customcb.o + WOLFHSM_OBJS += $(WOLFHSM_CLIENT_OBJS) #includes CFLAGS += -I"$(WOLFBOOT_LIB_WOLFHSM)" # defines @@ -1341,26 +1421,7 @@ ifeq ($(WOLFHSM_SERVER),1) CFLAGS += -DWOLFHSM_CFG_COMM_DATA_LEN=5000 endif - WOLFHSM_OBJS += \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_utils.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_comm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_nvm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_nvm_flash.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_keyid.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_flash_unit.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_crypto.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_nvm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_crypto.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_counter.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_keystore.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_customcb.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_customcb.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_keystore.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_crypto.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_counter.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_nvm.o \ - $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_comm.o + WOLFHSM_OBJS += $(WOLFHSM_SERVER_OBJS) #includes CFLAGS += -I"$(WOLFBOOT_LIB_WOLFHSM)" diff --git a/src/wc_callable.c b/src/wc_callable.c index 1dd9761232..706c1c692a 100644 --- a/src/wc_callable.c +++ b/src/wc_callable.c @@ -34,6 +34,10 @@ #include "wolfboot/wcs_fwtpm.h" #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM +#include "wolfboot/wcs_wolfhsm.h" +#endif + static WC_RNG wcs_rng; int CSME_NSE_API wcs_get_random(uint8_t *rand, uint32_t size) @@ -48,6 +52,9 @@ void wcs_Init(void) #ifdef WOLFBOOT_TZ_FWTPM wcs_fwtpm_init(); #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM + wcs_wolfhsm_init(); +#endif } #endif /* WOLFCRYPT_SECURE_MODE */ diff --git a/src/wolfhsm_callable.c b/src/wolfhsm_callable.c new file mode 100644 index 0000000000..85b5804c70 --- /dev/null +++ b/src/wolfhsm_callable.c @@ -0,0 +1,165 @@ +/* wolfhsm_callable.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include + +#include "loader.h" +#include "store_sbrk.h" +#include "wolfboot/wcs_wolfhsm.h" +#include "wolfboot/wolfhsm_flash_hal.h" + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/random.h" + +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_server.h" + +#include "wh_transport_nsc.h" + +extern unsigned int _start_heap; +extern unsigned int _heap_size; + +void *_sbrk(unsigned int incr) +{ + static uint8_t *heap; + return wolfboot_store_sbrk(incr, &heap, (uint8_t *)&_start_heap, + (uint32_t)(&_heap_size)); +} + +#define WCS_WOLFHSM_SERVER_ID 56U + +/* Two 32 KiB partitions in the wolfBoot keyvault region: 64 KiB used out of + * 112 KiB, leaving headroom. Per-partition layout = 24 B state header + + * 32 directory entries * 56 B (= ~1.8 KiB) + ~30 KiB usable payload. */ +#define WCS_WOLFHSM_PARTITION_SIZE (32U * 1024U) + +/* Linker-provided symbols for the FLASH_KEYVAULT region defined in + * hal/stm32h5.ld; matches the PSA / PKCS11 stores' pattern. */ +extern uint32_t _flash_keyvault; +extern uint32_t _flash_keyvault_size; + +static whFlashH5Ctx g_flash_ctx; +/* Fields filled at runtime in wcs_wolfhsm_init: pointer-to-integer casts of + * linker symbols are not strictly conforming static initializers. */ +static whFlashH5Ctx g_flash_cfg; + +static whNvmFlashContext g_nvm_flash_ctx; +static whNvmFlashConfig g_nvm_flash_cfg = { + .cb = &whFlashH5_Cb, + .context = &g_flash_ctx, + .config = &g_flash_cfg, +}; +static whNvmCb g_nvm_flash_cb = WH_NVM_FLASH_CB; +static whNvmContext g_nvm_ctx; +static whNvmConfig g_nvm_cfg = { + .cb = &g_nvm_flash_cb, + .context = &g_nvm_flash_ctx, + .config = &g_nvm_flash_cfg, +}; + +static whServerCryptoContext g_crypto_ctx; +static whTransportNscServerContext g_srv_tx_ctx; +static whTransportNscServerConfig g_srv_tx_cfg = { { 0 } }; +static whCommServerConfig g_comm_cfg = { + .transport_context = &g_srv_tx_ctx, + .transport_cb = &whTransportNscServer_Cb, + .transport_config = &g_srv_tx_cfg, + .server_id = WCS_WOLFHSM_SERVER_ID, +}; +static whServerConfig g_server_cfg = { + .comm_config = &g_comm_cfg, + .nvm = &g_nvm_ctx, + .crypto = &g_crypto_ctx, +#if defined WOLF_CRYPTO_CB + .devId = INVALID_DEVID, +#endif +}; + +static whServerContext g_server; +static int g_wolfhsm_ready; + +void wcs_wolfhsm_init(void) +{ + int rc; + + g_flash_cfg.base = (uint32_t)&_flash_keyvault; + g_flash_cfg.size = (uint32_t)&_flash_keyvault_size; + g_flash_cfg.partition_size = WCS_WOLFHSM_PARTITION_SIZE; + + rc = wc_InitRng(g_crypto_ctx.rng); + if (rc != 0) { + wolfBoot_panic(); + } + rc = wh_Nvm_Init(&g_nvm_ctx, &g_nvm_cfg); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); + } + rc = wh_Server_Init(&g_server, &g_server_cfg); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); + } + rc = wh_Server_SetConnected(&g_server, WH_COMM_CONNECTED); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); + } + g_wolfhsm_ready = 1; +} + +int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, + uint8_t *rsp, uint32_t *rspSz) +{ + uint32_t rsp_capacity; + int rc; + + if (cmd == NULL || rsp == NULL || rspSz == NULL) { + return WH_ERROR_BADARGS; + } + /* volatile read forbids the compiler from re-fetching *rspSz later. */ + rsp_capacity = *(volatile const uint32_t *)rspSz; + + if (cmdSz == 0U || cmdSz > WH_COMM_MTU) { + return WH_ERROR_BADARGS; + } + if (rsp_capacity == 0U || rsp_capacity > WH_COMM_MTU) { + return WH_ERROR_BADARGS; + } + if (!g_wolfhsm_ready) { + return WH_ERROR_NOTREADY; + } + + g_srv_tx_ctx.req_buf = cmd; + g_srv_tx_ctx.req_size = (uint16_t)cmdSz; + g_srv_tx_ctx.rsp_buf = rsp; + g_srv_tx_ctx.rsp_capacity = (uint16_t)rsp_capacity; + g_srv_tx_ctx.rsp_size = 0; + g_srv_tx_ctx.request_pending = 1; + + rc = wh_Server_HandleRequestMessage(&g_server); + + if (rc == WH_ERROR_OK) { + *rspSz = (uint32_t)g_srv_tx_ctx.rsp_size; + } else { + *rspSz = 0; + } + + g_srv_tx_ctx.req_buf = NULL; + g_srv_tx_ctx.req_size = 0; + g_srv_tx_ctx.rsp_buf = NULL; + g_srv_tx_ctx.rsp_capacity = 0; + g_srv_tx_ctx.request_pending = 0; + + return rc; +} + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/src/wolfhsm_flash_hal.c b/src/wolfhsm_flash_hal.c new file mode 100644 index 0000000000..d11e730924 --- /dev/null +++ b/src/wolfhsm_flash_hal.c @@ -0,0 +1,227 @@ +/* wolfhsm_flash_hal.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include + +#include "hal.h" +#include "wolfboot/wolfhsm_flash_hal.h" + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/misc.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" + +#define WHFH5_SECTOR_SIZE (8U * 1024U) + +/* Sector-cached read-modify-erase-write, mirroring psa_store.c. STM32H5 + * flash programs in 16-byte quad-words with ECC; each quad-word can be + * programmed exactly once between erases. wolfHSM issues 8-byte unit + * writes which would otherwise re-program neighbouring qwords, so every + * Program call here loads the affected sector into RAM, modifies it, and + * rewrites the whole 8 KiB sector after an erase. */ +static uint8_t cached_sector[WHFH5_SECTOR_SIZE]; + +static int _Init(void *context, const void *config) +{ + const whFlashH5Ctx *cfg = (const whFlashH5Ctx *)config; + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + + if (ctx == NULL || cfg == NULL) { + return WH_ERROR_BADARGS; + } + + if (cfg->base == 0U || cfg->size == 0U || cfg->partition_size == 0U || + (cfg->base % WHFH5_SECTOR_SIZE) != 0U || + (cfg->size % WHFH5_SECTOR_SIZE) != 0U || + (cfg->partition_size % WHFH5_SECTOR_SIZE) != 0U || + cfg->partition_size > cfg->size / 2U) { + return WH_ERROR_BADARGS; + } + + *ctx = *cfg; + return WH_ERROR_OK; +} + +static int _Cleanup(void *context) +{ + if (context == NULL) { + return WH_ERROR_BADARGS; + } + return WH_ERROR_OK; +} + +static uint32_t _PartitionSize(void *context) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + return (ctx == NULL) ? 0U : ctx->partition_size; +} + +static int _WriteLock(void *context, uint32_t offset, uint32_t size) +{ + (void)context; + (void)offset; + (void)size; + return WH_ERROR_OK; +} + +static int _WriteUnlock(void *context, uint32_t offset, uint32_t size) +{ + (void)context; + (void)offset; + (void)size; + return WH_ERROR_OK; +} + +static int _Read(void *context, uint32_t offset, uint32_t size, uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if (size > 0U) { + memcpy(data, (const uint8_t *)(ctx->base + offset), size); + } + return WH_ERROR_OK; +} + +static int _Program(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + uint32_t written = 0U; + int hrc = 0; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if (size == 0U) { + return WH_ERROR_OK; + } + + hal_flash_unlock(); + while (written < size) { + uint32_t in_sector_off = (offset + written) % WHFH5_SECTOR_SIZE; + uint32_t sector_offset = (offset + written) - in_sector_off; + uint32_t chunk = WHFH5_SECTOR_SIZE - in_sector_off; + if (chunk > size - written) { + chunk = size - written; + } + + memcpy(cached_sector, + (const uint8_t *)(ctx->base + sector_offset), + WHFH5_SECTOR_SIZE); + memcpy(cached_sector + in_sector_off, data + written, chunk); + + hrc = hal_flash_erase(ctx->base + sector_offset, WHFH5_SECTOR_SIZE); + if (hrc == 0) { + hrc = hal_flash_write(ctx->base + sector_offset, cached_sector, + WHFH5_SECTOR_SIZE); + } + + /* Per-iteration wipe so a fault between sectors doesn't strand + * plaintext keystore bytes in the static cache. */ + wc_ForceZero(cached_sector, sizeof(cached_sector)); + + if (hrc != 0) { + break; + } + written += chunk; + } + hal_flash_lock(); + + return (hrc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} + +static int _Erase(void *context, uint32_t offset, uint32_t size) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + int rc; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if ((offset % WHFH5_SECTOR_SIZE) != 0U || + (size % WHFH5_SECTOR_SIZE) != 0U) { + return WH_ERROR_BADARGS; + } + if (size == 0U) { + return WH_ERROR_OK; + } + + hal_flash_unlock(); + rc = hal_flash_erase(ctx->base + offset, (int)size); + hal_flash_lock(); + return (rc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} + +static int _Verify(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if (size > 0U && + memcmp((const uint8_t *)(ctx->base + offset), data, size) != 0) { + return WH_ERROR_NOTVERIFIED; + } + return WH_ERROR_OK; +} + +static int _BlankCheck(void *context, uint32_t offset, uint32_t size) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + const uint8_t *p; + uint32_t i; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + p = (const uint8_t *)(ctx->base + offset); + for (i = 0U; i < size; i++) { + if (p[i] != 0xFFU) { + return WH_ERROR_NOTBLANK; + } + } + return WH_ERROR_OK; +} + +const whFlashCb whFlashH5_Cb = { + .Init = _Init, + .Cleanup = _Cleanup, + .PartitionSize = _PartitionSize, + .WriteLock = _WriteLock, + .WriteUnlock = _WriteUnlock, + .Read = _Read, + .Program = _Program, + .Erase = _Erase, + .Verify = _Verify, + .BlankCheck = _BlankCheck, +}; + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/Makefile b/test-app/Makefile index e5b06108c3..5c5efa1a8a 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -5,12 +5,16 @@ WOLFBOOT_LIB_WOLFSSL?=../lib/wolfssl WOLFBOOT_LIB_WOLFTPM?=../lib/wolfTPM +WOLFBOOT_LIB_WOLFHSM?=../lib/wolfHSM WOLFSSL_LOCAL_OBJDIR?=wolfssl_obj WOLFTPM_LOCAL_OBJDIR?=wolftpm_obj +WOLFHSM_LOCAL_OBJDIR?=wolfhsm_obj vpath %.c $(WOLFBOOT_LIB_WOLFSSL) vpath %.S $(WOLFBOOT_LIB_WOLFSSL) vpath %.c $(WOLFBOOT_LIB_WOLFTPM) vpath %.S $(WOLFBOOT_LIB_WOLFTPM) +vpath %.c $(WOLFBOOT_LIB_WOLFHSM) +vpath %.S $(WOLFBOOT_LIB_WOLFHSM) TARGET?=none ARCH?=ARM @@ -109,6 +113,11 @@ ifeq ($(WOLFCRYPT_TZ_FWTPM),1) WOLFCRYPT_TZ_FWTPM=1 endif +ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + WOLFCRYPT_TZ=1 + WOLFCRYPT_TZ_WOLFHSM=1 +endif + # Setup default linker flags LDFLAGS+=-T $(LSCRIPT) -Wl,-gc-sections -Wl,-Map=image.map -nostartfiles @@ -355,6 +364,26 @@ ifeq ($(TZEN),1) $(WOLFTPM_LOCAL_OBJDIR)/%, $(WOLFTPM_APP_OBJS)) APP_OBJS+=$(sort $(WOLFTPM_APP_OBJS)) endif + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + CFLAGS+=-DWOLFCRYPT_TZ_WOLFHSM + CFLAGS+=-DWOLFHSM_CFG_ENABLE_CLIENT + CFLAGS+=-DWOLFHSM_CFG_COMM_DATA_LEN=1280 + CFLAGS+=-DWOLFHSM_CFG_PORT_STM32_TZ_NSC + CFLAGS+=-DWOLFHSM_CFG_NO_SYS_TIME + ifeq ($(WOLFBOOT_TZ_TEST_NO_BKPT),1) + CFLAGS+=-DWOLFBOOT_TZ_TEST_NO_BKPT + endif + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz" + WOLFCRYPT_APP_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/cryptocb.o + APP_OBJS+=./wcs/wolfhsm_test.o + APP_OBJS+=./wcs/wolfhsm_stub.o + WOLFHSM_APP_OBJS+=$(WOLFHSM_CLIENT_OBJS) + WOLFHSM_APP_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz/wh_transport_nsc.o + WOLFHSM_APP_OBJS:=$(patsubst $(WOLFBOOT_LIB_WOLFHSM)/%, \ + $(WOLFHSM_LOCAL_OBJDIR)/%, $(WOLFHSM_APP_OBJS)) + APP_OBJS+=$(sort $(WOLFHSM_APP_OBJS)) + endif WOLFCRYPT_APP_OBJS := $(patsubst $(WOLFBOOT_LIB_WOLFSSL)/%, \ $(WOLFSSL_LOCAL_OBJDIR)/%, $(WOLFCRYPT_APP_OBJS)) ifneq ($(WOLFCRYPT_TZ_PKCS11),1) @@ -973,6 +1002,7 @@ endif # Capture final flags for locally built wolfSSL objects. WOLFSSL_CFLAGS:=$(CFLAGS) WOLFTPM_CFLAGS:=$(CFLAGS) +WOLFHSM_CFLAGS:=$(CFLAGS) ifeq ($(WOLFHSM_CLIENT),1) CFLAGS += -DSTRING_USER -I"$(WOLFBOOT_LIB_WOLFSSL)" @@ -1070,9 +1100,20 @@ $(WOLFTPM_LOCAL_OBJDIR)/%.o: %.S $(Q)mkdir -p $(dir $@) $(Q)$(CC) $(WOLFTPM_CFLAGS) -c $(OUTPUT_FLAG) $@ $< +$(WOLFHSM_LOCAL_OBJDIR)/%.o: %.c + @echo "\t[CC-$(ARCH)] $@" + $(Q)mkdir -p $(dir $@) + $(Q)$(CC) $(WOLFHSM_CFLAGS) -c $(OUTPUT_FLAG) $@ $< + +$(WOLFHSM_LOCAL_OBJDIR)/%.o: %.S + @echo "\t[AS-$(ARCH)] $@" + $(Q)mkdir -p $(dir $@) + $(Q)$(CC) $(WOLFHSM_CFLAGS) -c $(OUTPUT_FLAG) $@ $< + clean: $(Q)rm -f *.bin *.elf tags *.o $(LSCRIPT) $(APP_OBJS) wcs/*.o - $(Q)rm -rf $(WOLFSSL_LOCAL_OBJDIR) $(WOLFTPM_LOCAL_OBJDIR) + $(Q)rm -rf $(WOLFSSL_LOCAL_OBJDIR) $(WOLFTPM_LOCAL_OBJDIR) \ + $(WOLFHSM_LOCAL_OBJDIR) $(LSCRIPT): $(LSCRIPT_TEMPLATE) FORCE $(Q)printf "%d" $(WOLFBOOT_PARTITION_BOOT_ADDRESS) > .wolfboot-offset diff --git a/test-app/app_stm32h5.c b/test-app/app_stm32h5.c index 660718aa4a..adb89c682b 100644 --- a/test-app/app_stm32h5.c +++ b/test-app/app_stm32h5.c @@ -218,6 +218,9 @@ static int cmd_tpm_quote(const char *args); #ifdef WOLFBOOT_TZ_FWTPM static int cmd_fwtpm_test(const char *args); #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM +#include "wcs/wolfhsm_test.h" +#endif #define CMD_BUFFER_SIZE 256 @@ -1505,6 +1508,26 @@ void main(void) asm volatile ("bkpt #0x7e"); #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM + ret = cmd_wolfhsm_test(NULL); +#ifdef WOLFBOOT_TZ_TEST_NO_BKPT + if (ret == WOLFHSM_TEST_FIRST_BOOT_OK || ret == WOLFHSM_TEST_SECOND_BOOT_OK) { + printf("WOLFHSM_TZ_TEST_PASS\r\n"); + while (1) { } + } else { + printf("WOLFHSM_TZ_TEST_FAIL\r\n"); + while (1) { } + } +#else + if (ret == WOLFHSM_TEST_FIRST_BOOT_OK) + asm volatile ("bkpt #0x7d"); + else if (ret == WOLFHSM_TEST_SECOND_BOOT_OK) + asm volatile ("bkpt #0x7f"); + else + asm volatile ("bkpt #0x7e"); +#endif +#endif + #if defined(WOLFBOOT_ATTESTATION_TEST) && defined(WOLFCRYPT_TZ_PSA) (void)run_attestation_test(); #endif diff --git a/test-app/wcs/user_settings.h b/test-app/wcs/user_settings.h index 1f71ff86a8..711b9052c6 100644 --- a/test-app/wcs/user_settings.h +++ b/test-app/wcs/user_settings.h @@ -54,6 +54,14 @@ extern int tolower(int c); #define MAX_CRYPTO_DEVID_CALLBACKS 2 #endif +/* wolfHSM (TZ engine, NS client side) */ +#ifdef WOLFCRYPT_TZ_WOLFHSM + #define WOLF_CRYPTO_CB + #undef HAVE_ANONYMOUS_INLINE_AGGREGATES + #define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 + #define WOLFSSL_KEY_GEN +#endif + /* ECC */ @@ -156,7 +164,8 @@ extern int tolower(int c); #define HAVE_PKCS8 #define HAVE_PKCS12 -#if defined(SECURE_PKCS11) || defined(WOLFBOOT_TZ_FWTPM) +#if defined(SECURE_PKCS11) || defined(WOLFBOOT_TZ_FWTPM) || \ + defined(WOLFCRYPT_TZ_WOLFHSM) static inline int wcs_cmse_get_random(unsigned char* output, int sz) { diff --git a/test-app/wcs/wolfhsm_stub.c b/test-app/wcs/wolfhsm_stub.c new file mode 100644 index 0000000000..bff167f0c4 --- /dev/null +++ b/test-app/wcs/wolfhsm_stub.c @@ -0,0 +1,26 @@ +/* wolfhsm_stub.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +/* + * Non-secure side static buffers + transport context for the wolfHSM TZ + * NSC bridge. The transport callback table itself lives in the wolfHSM + * port file (port/stmicro/stm32-tz/wh_transport_nsc.c); this stub just + * provides the singleton context it operates on. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include + +#include "wh_transport_nsc.h" + +/* Static .bss singleton. The wolfHSM client passes a pointer to this in + * whCommClientConfig.transport_context; the transport callbacks stash the + * inbound/outbound packets in cmd_buf/rsp_buf. */ +whTransportNscClientContext g_wolfhsm_nsc_client_ctx; + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c new file mode 100644 index 0000000000..d7a4cd4c58 --- /dev/null +++ b/test-app/wcs/wolfhsm_test.c @@ -0,0 +1,289 @@ +/* wolfhsm_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include +#include + +#include "wolfhsm_test.h" + +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_keyid.h" + +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/misc.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/sha256.h" + +#include "wh_transport_nsc.h" + +/* NS-side singleton transport context lives in wolfhsm_stub.c. */ +extern whTransportNscClientContext g_wolfhsm_nsc_client_ctx; + +#define WCS_WOLFHSM_CLIENT_ID 1 + +static int wolfhsm_test_rng(void) +{ + WC_RNG rng; + uint8_t rnd[16]; + unsigned int i; + int rc; + + memset(&rng, 0, sizeof(rng)); + memset(rnd, 0, sizeof(rnd)); + + rc = wc_InitRng_ex(&rng, NULL, WH_DEV_ID); + if (rc != 0) { + printf("wolfHSM RNG init failed: %d\r\n", rc); + return rc; + } + + rc = wc_RNG_GenerateBlock(&rng, rnd, sizeof(rnd)); + if (rc != 0) { + printf("wolfHSM RNG generate failed: %d\r\n", rc); + (void)wc_FreeRng(&rng); + return rc; + } + + printf("wolfHSM RNG ok:"); + for (i = 0; i < sizeof(rnd); i++) { + printf(" %02x", rnd[i]); + } + printf("\r\n"); + + (void)wc_FreeRng(&rng); + return 0; +} + +static int wolfhsm_test_sha256(void) +{ + /* SHA256("abc") — FIPS 180-2, Appendix B.1. */ + static const uint8_t expected[WC_SHA256_DIGEST_SIZE] = { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad + }; + wc_Sha256 sha; + uint8_t digest[WC_SHA256_DIGEST_SIZE]; + int rc; + + memset(&sha, 0, sizeof(sha)); + memset(digest, 0, sizeof(digest)); + + rc = wc_InitSha256_ex(&sha, NULL, WH_DEV_ID); + if (rc != 0) { + printf("wolfHSM SHA256 init failed: %d\r\n", rc); + return rc; + } + + rc = wc_Sha256Update(&sha, (const uint8_t*)"abc", 3); + if (rc == 0) { + rc = wc_Sha256Final(&sha, digest); + } + wc_Sha256Free(&sha); + if (rc != 0) { + printf("wolfHSM SHA256 hash failed: %d\r\n", rc); + return rc; + } + + if (memcmp(digest, expected, sizeof(expected)) != 0) { + printf("wolfHSM SHA256 mismatch\r\n"); + return -1; + } + printf("wolfHSM SHA256 ok\r\n"); + return 0; +} + +static int wolfhsm_test_aes_cached(whClientContext *client) +{ + /* FIPS 197 Appendix B AES-128 vector. CBC with IV=0 yields the same + * first-block ciphertext as ECB, so a single block under CBC suffices + * to verify the key+algorithm wired through correctly. */ + static const uint8_t key[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const uint8_t pt[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff + }; + static const uint8_t expected[16] = { + 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, + 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a + }; + static const uint8_t iv[16] = { 0 }; + Aes aes; + uint8_t ct[16]; + uint16_t keyId = WH_KEYID_ERASED; + int aes_inited = 0; + int rc; + + memset(&aes, 0, sizeof(aes)); + memset(ct, 0, sizeof(ct)); + + rc = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, 0, + key, (uint16_t)sizeof(key), &keyId); + if (rc != WH_ERROR_OK) { + printf("wolfHSM KeyCache failed: %d\r\n", rc); + return rc; + } + + rc = wc_AesInit(&aes, NULL, WH_DEV_ID); + if (rc != 0) { + printf("wolfHSM AesInit failed: %d\r\n", rc); + goto out; + } + aes_inited = 1; + + rc = wh_Client_AesSetKeyId(&aes, keyId); + if (rc != WH_ERROR_OK) { + printf("wolfHSM AesSetKeyId failed: %d\r\n", rc); + goto out; + } + + rc = wc_AesSetIV(&aes, iv); + if (rc == 0) { + rc = wc_AesCbcEncrypt(&aes, ct, pt, (word32)sizeof(pt)); + } + if (rc != 0) { + printf("wolfHSM AES encrypt failed: %d\r\n", rc); + goto out; + } + + if (memcmp(ct, expected, sizeof(expected)) != 0) { + printf("wolfHSM AES mismatch\r\n"); + rc = -1; + goto out; + } + printf("wolfHSM AES ok\r\n"); + +out: + if (aes_inited) { + wc_AesFree(&aes); + } + (void)wh_Client_KeyEvict(client, keyId); + return rc; +} + +static int wolfhsm_test_persist(whClientContext *client, int *boot_state) +{ + static const uint8_t persist_key[16] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x00 + }; + uint16_t keyId = WH_MAKE_KEYID(0, WCS_WOLFHSM_CLIENT_ID, 1); + uint8_t out[sizeof(persist_key)]; + uint16_t outSz = (uint16_t)sizeof(out); + int rc; + + memset(out, 0, sizeof(out)); + rc = wh_Client_KeyExport(client, keyId, NULL, 0, out, &outSz); + if (rc == WH_ERROR_OK && outSz == sizeof(persist_key) && + memcmp(out, persist_key, sizeof(persist_key)) == 0) { + printf("wolfHSM second boot path, restored persisted key\r\n"); + *boot_state = WOLFHSM_TEST_SECOND_BOOT_OK; + wc_ForceZero(out, sizeof(out)); + return 0; + } + wc_ForceZero(out, sizeof(out)); + + printf("wolfHSM first boot path, committing key to NVM\r\n"); + rc = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, 0, + persist_key, (uint16_t)sizeof(persist_key), + &keyId); + if (rc != WH_ERROR_OK) { + printf("wolfHSM persist KeyCache failed: %d\r\n", rc); + return rc; + } + rc = wh_Client_KeyCommit(client, keyId); + if (rc != WH_ERROR_OK) { + printf("wolfHSM persist KeyCommit failed: %d\r\n", rc); + return rc; + } + (void)wh_Client_KeyEvict(client, keyId); + *boot_state = WOLFHSM_TEST_FIRST_BOOT_OK; + return 0; +} + +int cmd_wolfhsm_test(const char *args) +{ + static const whTransportNscClientConfig nsc_cfg = { { 0 } }; + whCommClientConfig comm_cfg; + whClientConfig cfg; + whClientContext client; + uint32_t out_clientid = 0; + uint32_t out_serverid = 0; + int boot_state = WOLFHSM_TEST_FAIL; + int rc; + + (void)args; + + memset(&comm_cfg, 0, sizeof(comm_cfg)); + comm_cfg.transport_cb = &whTransportNscClient_Cb; + comm_cfg.transport_context = &g_wolfhsm_nsc_client_ctx; + comm_cfg.transport_config = &nsc_cfg; + comm_cfg.client_id = WCS_WOLFHSM_CLIENT_ID; + + memset(&cfg, 0, sizeof(cfg)); + cfg.comm = &comm_cfg; + + memset(&client, 0, sizeof(client)); + + rc = wh_Client_Init(&client, &cfg); + if (rc != WH_ERROR_OK) { + printf("wolfHSM Init failed: %d\r\n", rc); + return WOLFHSM_TEST_FAIL; + } + + rc = wh_Client_CommInit(&client, &out_clientid, &out_serverid); + if (rc != WH_ERROR_OK) { + printf("wolfHSM CommInit failed: %d\r\n", rc); + (void)wh_Client_Cleanup(&client); + return WOLFHSM_TEST_FAIL; + } + + printf("wolfHSM CommInit ok (client=%u server=%u)\r\n", + (unsigned)out_clientid, (unsigned)out_serverid); + + rc = wolfhsm_test_rng(); + if (rc != 0) { + (void)wh_Client_Cleanup(&client); + return WOLFHSM_TEST_FAIL; + } + + rc = wolfhsm_test_sha256(); + if (rc != 0) { + (void)wh_Client_Cleanup(&client); + return WOLFHSM_TEST_FAIL; + } + + rc = wolfhsm_test_aes_cached(&client); + if (rc != 0) { + (void)wh_Client_Cleanup(&client); + return WOLFHSM_TEST_FAIL; + } + + rc = wolfhsm_test_persist(&client, &boot_state); + if (rc != 0) { + (void)wh_Client_Cleanup(&client); + return WOLFHSM_TEST_FAIL; + } + + printf("wolfHSM NSC tests passed\r\n"); + + (void)wh_Client_Cleanup(&client); + return boot_state; +} + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/wcs/wolfhsm_test.h b/test-app/wcs/wolfhsm_test.h new file mode 100644 index 0000000000..f84b15da8f --- /dev/null +++ b/test-app/wcs/wolfhsm_test.h @@ -0,0 +1,19 @@ +/* wolfhsm_test.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifndef WOLFBOOT_TEST_WOLFHSM_H +#define WOLFBOOT_TEST_WOLFHSM_H + +enum wolfhsm_test_result { + WOLFHSM_TEST_FAIL = -1, + WOLFHSM_TEST_FIRST_BOOT_OK = 1, + WOLFHSM_TEST_SECOND_BOOT_OK = 2 +}; + +int cmd_wolfhsm_test(const char *args); + +#endif /* WOLFBOOT_TEST_WOLFHSM_H */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 6aa9752bb1..207635132d 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -51,7 +51,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-enc-nvm-flagshome unit-delta unit-update-flash unit-update-flash-delta \ unit-update-flash-hook \ unit-update-flash-self-update \ - unit-update-flash-enc unit-update-ram unit-update-ram-nofixed unit-pkcs11_store unit-psa_store unit-disk \ + unit-update-flash-enc unit-update-ram unit-update-ram-nofixed unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \ unit-update-disk unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \ unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \ @@ -357,6 +357,12 @@ unit-pkcs11_store: ../../include/target.h unit-pkcs11_store.c unit-psa_store: ../../include/target.h unit-psa_store.c gcc -o $@ $(WOLFCRYPT_SRC) unit-psa_store.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) +unit-wolfhsm_flash_hal:CFLAGS+=-I$(WOLFBOOT_LIB_WOLFHSM) -DWOLFCRYPT_TZ_WOLFHSM -DWOLFHSM_CFG_NO_SYS_TIME -DMOCK_PARTITIONS +unit-wolfhsm_flash_hal:WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/misc.c +unit-wolfhsm_flash_hal: ../../include/target.h unit-wolfhsm_flash_hal.c + gcc -o $@ $(WOLFCRYPT_SRC) unit-wolfhsm_flash_hal.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) + gpt-sfdisk-test.h: truncate -s 131072 .gpt-tmp.img printf 'label: gpt\nfirst-lba: 34\nstart=34, size=67, name="boot"\nstart=101, size=100, name="rootfs"\n' \ diff --git a/tools/unit-tests/unit-wolfhsm_flash_hal.c b/tools/unit-tests/unit-wolfhsm_flash_hal.c new file mode 100644 index 0000000000..e3fa87ee80 --- /dev/null +++ b/tools/unit-tests/unit-wolfhsm_flash_hal.c @@ -0,0 +1,312 @@ +/* unit-wolfhsm_flash_hal.c + * + * Unit tests for the wolfHSM whFlashCb adapter (src/wolfhsm_flash_hal.c). + */ + +#include +#include +#include +#include +#include + +#include "user_settings.h" +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/misc.h" + +#include "hal.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" +#include "wolfboot/wolfhsm_flash_hal.h" + +#define MOCK_FLASH_BASE 0xCF000000U +#define MOCK_FLASH_SECTOR (8U * 1024U) +#define MOCK_FLASH_SECTORS 14U +#define MOCK_FLASH_SIZE (MOCK_FLASH_SECTORS * MOCK_FLASH_SECTOR) + +static int g_flash_locked = 1; +static int g_flash_write_fail = 0; +static int g_flash_erase_fail = 0; + +void hal_flash_unlock(void) { g_flash_locked = 0; } +void hal_flash_lock(void) { g_flash_locked = 1; } + +int hal_flash_erase(haladdr_t addr, int len) +{ + if (g_flash_locked || g_flash_erase_fail) { + return -1; + } + if (addr < MOCK_FLASH_BASE || + addr + (uintptr_t)len > MOCK_FLASH_BASE + MOCK_FLASH_SIZE) { + return -1; + } + memset((void *)addr, 0xFF, (size_t)len); + return 0; +} + +int hal_flash_write(haladdr_t addr, const uint8_t *data, int len) +{ + if (g_flash_locked || g_flash_write_fail) { + return -1; + } + if (addr < MOCK_FLASH_BASE || + addr + (uintptr_t)len > MOCK_FLASH_BASE + MOCK_FLASH_SIZE) { + return -1; + } + memcpy((void *)addr, data, (size_t)len); + return 0; +} + +#include "../../src/wolfhsm_flash_hal.c" + +static void mock_flash_init(void) +{ + void *p = mmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE, + PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ck_assert_ptr_eq(p, (void *)(uintptr_t)MOCK_FLASH_BASE); + memset((void *)(uintptr_t)MOCK_FLASH_BASE, 0xFF, MOCK_FLASH_SIZE); + g_flash_locked = 1; + g_flash_write_fail = 0; + g_flash_erase_fail = 0; +} + +static void mock_flash_fini(void) +{ + munmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE); +} + +START_TEST(test_init_rejects_null) +{ + whFlashH5Ctx ctx; + whFlashH5Ctx cfg; + + cfg.base = MOCK_FLASH_BASE; + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Init(NULL, &cfg), WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, NULL), WH_ERROR_BADARGS); +} +END_TEST + +START_TEST(test_init_rejects_bad_config) +{ + whFlashH5Ctx ctx; + whFlashH5Ctx cfg; + + cfg.base = 0; + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.base = MOCK_FLASH_BASE; + cfg.size = 0; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = 0; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.base = MOCK_FLASH_BASE + 4U; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.base = MOCK_FLASH_BASE; + cfg.size = MOCK_FLASH_SIZE + 4U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = 100U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.partition_size = MOCK_FLASH_SIZE; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); +} +END_TEST + +START_TEST(test_init_accepts_valid) +{ + whFlashH5Ctx ctx; + whFlashH5Ctx cfg; + + cfg.base = MOCK_FLASH_BASE; + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_OK); + ck_assert_uint_eq(whFlashH5_Cb.PartitionSize(&ctx), MOCK_FLASH_SIZE / 2U); +} +END_TEST + +START_TEST(test_read_bounds) +{ + whFlashH5Ctx ctx; + uint8_t buf[16]; + + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, MOCK_FLASH_SIZE + 1U, 0U, buf), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, MOCK_FLASH_SIZE + 1U, buf), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, 0U, NULL), WH_ERROR_OK); +} +END_TEST + +START_TEST(test_program_single_partial_preserves_neighbours) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t readback[8]; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + memcpy(readback, (void *)(uintptr_t)MOCK_FLASH_BASE, sizeof(readback)); + ck_assert_mem_eq(readback, data, sizeof(data)); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[8], 0xFFU); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[MOCK_FLASH_SECTOR - 1U], + 0xFFU); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_program_crosses_sector_boundary) +{ + whFlashH5Ctx ctx; + const uint32_t off = MOCK_FLASH_SECTOR - 8U; + uint8_t data[16]; + uint8_t readback[16]; + int i; + + for (i = 0; i < (int)sizeof(data); i++) { + data[i] = (uint8_t)(0x10 + i); + } + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, off, sizeof(data), data), + WH_ERROR_OK); + memcpy(readback, (void *)(uintptr_t)(MOCK_FLASH_BASE + off), + sizeof(readback)); + ck_assert_mem_eq(readback, data, sizeof(data)); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off - 1U], 0xFFU); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off + 16U], 0xFFU); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_program_propagates_write_failure) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + g_flash_write_fail = 1; + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_ABORTED); + g_flash_write_fail = 0; + mock_flash_fini(); +} +END_TEST + +START_TEST(test_erase_alignment) +{ + whFlashH5Ctx ctx; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 100U, MOCK_FLASH_SECTOR), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 0U, 100U), WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_OK); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_verify) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t bad[8] = { 0 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, sizeof(bad), bad), + WH_ERROR_NOTVERIFIED); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_blank_check) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.BlankCheck(&ctx, 0U, MOCK_FLASH_SIZE), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.BlankCheck(&ctx, 0U, sizeof(data)), + WH_ERROR_NOTBLANK); + mock_flash_fini(); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("wolfHSM-flash-hal"); + TCase *tc = tcase_create("flash-hal"); + + tcase_add_test(tc, test_init_rejects_null); + tcase_add_test(tc, test_init_rejects_bad_config); + tcase_add_test(tc, test_init_accepts_valid); + tcase_add_test(tc, test_read_bounds); + tcase_add_test(tc, test_program_single_partial_preserves_neighbours); + tcase_add_test(tc, test_program_crosses_sector_boundary); + tcase_add_test(tc, test_program_propagates_write_failure); + tcase_add_test(tc, test_erase_alignment); + tcase_add_test(tc, test_verify); + tcase_add_test(tc, test_blank_check); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = wolfboot_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +}