diff --git a/.github/workflows/test-external-library-paths.yml b/.github/workflows/test-external-library-paths.yml index 4d437547cb..f34a6c3351 100644 --- a/.github/workflows/test-external-library-paths.yml +++ b/.github/workflows/test-external-library-paths.yml @@ -86,4 +86,5 @@ jobs: echo "=== Building unit tests with external paths ===" make -C tools/unit-tests \ WOLFBOOT_LIB_WOLFSSL="$(realpath ../external-libs/wolfssl)" \ - WOLFBOOT_LIB_WOLFPKCS11="$(realpath ../external-libs/wolfPKCS11)" + WOLFBOOT_LIB_WOLFPKCS11="$(realpath ../external-libs/wolfPKCS11)" \ + WOLFBOOT_LIB_WOLFHSM="$(realpath ../external-libs/wolfHSM)" 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 18fc6cd22a..cc7fbc1f43 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -758,12 +758,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/lib/wolfHSM b/lib/wolfHSM index 977bf187e7..16cfc435f5 160000 --- a/lib/wolfHSM +++ b/lib/wolfHSM @@ -1 +1 @@ -Subproject commit 977bf187e7a57a184493dcd216eb9a328f381865 +Subproject commit 16cfc435f5b43e6f869593f4b8623f66e8afdc05 diff --git a/options.mk b/options.mk index 442e20143a..a912bba163 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 @@ -935,12 +974,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) @@ -1094,6 +1145,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_ARMV8M_TZ_NSC + CFLAGS+=-DWOLFHSM_CFG_NO_SYS_TIME + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-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/armv8m-tz/wh_transport_nsc.o + STACK_USAGE=20000 +endif + OBJS+=$(PUBLIC_KEY_OBJS) ifneq ($(STAGE1),1) OBJS+=$(UPDATE_OBJS) @@ -1317,19 +1409,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 @@ -1375,26 +1455,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..71ab752351 --- /dev/null +++ b/src/wolfhsm_callable.c @@ -0,0 +1,182 @@ +/* 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; + +static whNvmFlashContext g_nvm_flash_ctx; +static whNvmFlashConfig g_nvm_flash_cfg = { + .cb = &whFlashH5_Cb, + .context = &g_flash_ctx, + /* .config is set at runtime in wcs_wolfhsm_init */ +}; +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, +#ifdef WOLF_CRYPTO_CB + .devId = INVALID_DEVID, +#endif +}; + +static whServerContext g_server; +static int g_wolfhsm_ready; + +void wcs_wolfhsm_init(void) +{ + whFlashH5Ctx flash_cfg; + int rc; + + if (g_wolfhsm_ready) { + return; + } + + memset(&g_srv_tx_ctx, 0, sizeof(g_srv_tx_ctx)); + + flash_cfg.base = (uint32_t)&_flash_keyvault; + flash_cfg.size = (uint32_t)&_flash_keyvault_size; + flash_cfg.partition_size = WCS_WOLFHSM_PARTITION_SIZE; + g_nvm_flash_cfg.config = &flash_cfg; + + /* g_crypto_ctx.rng is an embedded WC_RNG[1] (see wh_server.h) */ + rc = wc_InitRng(&g_crypto_ctx.rng[0]); + 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; + } + /* single-fetch *rspSz so it cannot be re-read after validation */ + rsp_capacity = *(volatile const uint32_t *)rspSz; + + if (cmdSz == 0U || cmdSz > WH_COMM_MTU) { + *rspSz = 0; + return WH_ERROR_BADARGS; + } + if (rsp_capacity == 0U || rsp_capacity > WH_COMM_MTU) { + *rspSz = 0; + return WH_ERROR_BADARGS; + } + if (!g_wolfhsm_ready) { + *rspSz = 0; + 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) { + /* clamp: dispatcher must honor rsp_capacity; defend against regression */ + if ((uint32_t)g_srv_tx_ctx.rsp_size > rsp_capacity) { + *rspSz = 0; + rc = WH_ERROR_ABORTED; + } + else { + *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..13caab7b90 --- /dev/null +++ b/src/wolfhsm_flash_hal.c @@ -0,0 +1,250 @@ +/* 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 RMW, mirrors psa_store.c. H5 flash programs as 16-byte + * qwords ECC; one program per erase. wolfHSM 8-byte writes would clobber + * neighbours, so Program loads the sector, modifies, erases, rewrites. + * + * Single static cache: non-reentrant by design (one secure-side server, + * synchronous per-NSC dispatch, no threads or interrupts on this path). + * Residual plaintext may remain in cache for one programming cycle if a + * synchronous fault aborts hal_flash_write; disable debug in production. */ +static uint8_t cached_sector[WHFH5_SECTOR_SIZE]; + +static int whFlashH5_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 whFlashH5_Cleanup(void *context) +{ + if (context == NULL) { + return WH_ERROR_BADARGS; + } + return WH_ERROR_OK; +} + +static uint32_t whFlashH5_PartitionSize(void *context) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + return (ctx == NULL) ? 0U : ctx->partition_size; +} + +static int whFlashH5_WriteLock(void *context, uint32_t offset, uint32_t size) +{ + (void)context; + (void)offset; + (void)size; + return WH_ERROR_OK; +} + +static int whFlashH5_WriteUnlock(void *context, uint32_t offset, uint32_t size) +{ + (void)context; + (void)offset; + (void)size; + return WH_ERROR_OK; +} + +static int whFlashH5_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 whFlashH5_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; + } + + /* defensive wipe in case a prior call faulted before the per-iter wipe */ + wc_ForceZero(cached_sector, sizeof(cached_sector)); + + 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 whFlashH5_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 (size == 0U) { + return WH_ERROR_OK; + } + if ((offset % WHFH5_SECTOR_SIZE) != 0U || + (size % WHFH5_SECTOR_SIZE) != 0U) { + return WH_ERROR_BADARGS; + } + + /* hal_flash_erase takes int; loop over sector-sized chunks so the cast + * stays well-defined regardless of how large size grows. */ + hal_flash_unlock(); + { + uint32_t erased = 0U; + rc = 0; + while (erased < size) { + rc = hal_flash_erase(ctx->base + offset + erased, + (int)WHFH5_SECTOR_SIZE); + if (rc != 0) { + break; + } + erased += WHFH5_SECTOR_SIZE; + } + } + hal_flash_lock(); + return (rc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} + +static int whFlashH5_Verify(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + const uint8_t *p; + uint8_t acc = 0; + uint32_t i; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + /* constant-time compare; verified data may be key material */ + p = (const uint8_t *)(ctx->base + offset); + for (i = 0U; i < size; i++) { + acc |= (uint8_t)(p[i] ^ data[i]); + } + return (acc == 0U) ? WH_ERROR_OK : WH_ERROR_NOTVERIFIED; +} + +static int whFlashH5_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 = whFlashH5_Init, + .Cleanup = whFlashH5_Cleanup, + .PartitionSize = whFlashH5_PartitionSize, + .WriteLock = whFlashH5_WriteLock, + .WriteUnlock = whFlashH5_WriteUnlock, + .Read = whFlashH5_Read, + .Program = whFlashH5_Program, + .Erase = whFlashH5_Erase, + .Verify = whFlashH5_Verify, + .BlankCheck = whFlashH5_BlankCheck, +}; + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/Makefile b/test-app/Makefile index 5a8f3f77ef..a46ed929bc 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_ARMV8M_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/armv8m-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/armv8m-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) @@ -1007,6 +1036,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)" @@ -1104,9 +1134,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..8d8343f274 --- /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/armv8m-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..22c7d0fee5 --- /dev/null +++ b/test-app/wcs/wolfhsm_test.c @@ -0,0 +1,292 @@ +/* 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) { + printf("wolfHSM AesSetIV failed: %d\r\n", rc); + goto out; + } + 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); + (void)wh_Client_KeyEvict(client, keyId); + 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 ee0b6398e4..b29c12d574 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -10,6 +10,7 @@ WOLFBOOT_LIB_WOLFSSL?=../../lib/wolfssl WOLFBOOT_LIB_WOLFPKCS11?=../../lib/wolfPKCS11 WOLFBOOT_LIB_WOLFPSA?=../../lib/wolfPSA WOLFBOOT_LIB_WOLFTPM?=../../lib/wolfTPM +WOLFBOOT_LIB_WOLFHSM?=../../lib/wolfHSM ifeq ($(wildcard $(WOLFBOOT_LIB_WOLFPSA)),) WOLFBOOT_LIB_WOLFPSA=../../../external-libs/wolfPSA @@ -17,12 +18,16 @@ endif ifeq ($(wildcard $(WOLFBOOT_LIB_WOLFTPM)),) WOLFBOOT_LIB_WOLFTPM=../../../external-libs/wolfTPM endif +ifeq ($(wildcard $(WOLFBOOT_LIB_WOLFHSM)),) +WOLFBOOT_LIB_WOLFHSM=../../../external-libs/wolfHSM +endif # Convert to absolute paths for standalone usage WOLFBOOT_LIB_WOLFSSL:=$(abspath $(WOLFBOOT_LIB_WOLFSSL)) WOLFBOOT_LIB_WOLFPKCS11:=$(abspath $(WOLFBOOT_LIB_WOLFPKCS11)) WOLFBOOT_LIB_WOLFPSA:=$(abspath $(WOLFBOOT_LIB_WOLFPSA)) WOLFBOOT_LIB_WOLFTPM:=$(abspath $(WOLFBOOT_LIB_WOLFTPM)) +WOLFBOOT_LIB_WOLFHSM:=$(abspath $(WOLFBOOT_LIB_WOLFHSM)) CFLAGS=-I. -I../../src -I../../include -I$(WOLFBOOT_LIB_WOLFSSL) CFLAGS+=-g -ggdb @@ -51,7 +56,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-enc-nvm-flagshome unit-delta unit-gzip 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 \ @@ -374,6 +379,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..6f7b159881 --- /dev/null +++ b/tools/unit-tests/unit-wolfhsm_flash_hal.c @@ -0,0 +1,453 @@ +/* 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) +{ + /* Prefer MAP_FIXED_NOREPLACE so the kernel refuses to clobber an + * existing mapping at MOCK_FLASH_BASE (Linux >= 4.17). Older kernels + * fall back to plain MAP_FIXED. */ + int flags = MAP_PRIVATE | MAP_ANONYMOUS; +#ifdef MAP_FIXED_NOREPLACE + flags |= MAP_FIXED_NOREPLACE; +#else + flags |= MAP_FIXED; +#endif + void *p = mmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE, + PROT_READ | PROT_WRITE, flags, -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 + +START_TEST(test_cleanup) +{ + whFlashH5Ctx ctx; + + ck_assert_int_eq(whFlashH5_Cb.Cleanup(NULL), WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Cleanup(&ctx), WH_ERROR_OK); +} +END_TEST + +START_TEST(test_program_propagates_erase_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_erase_fail = 1; + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_ABORTED); + g_flash_erase_fail = 0; + mock_flash_fini(); +} +END_TEST + +START_TEST(test_read_returns_flash_contents) +{ + whFlashH5Ctx ctx; + uint8_t data[16]; + uint8_t buf[16]; + int i; + + for (i = 0; i < (int)sizeof(data); i++) { + data[i] = (uint8_t)(0xA0 + 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, 0U, sizeof(data), data), + WH_ERROR_OK); + memset(buf, 0, sizeof(buf)); + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, sizeof(buf), buf), + WH_ERROR_OK); + ck_assert_mem_eq(buf, data, sizeof(data)); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_callbacks_reject_null_context) +{ + uint8_t data[8] = { 0 }; + + ck_assert_int_eq(whFlashH5_Cb.Read(NULL, 0U, sizeof(data), data), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Program(NULL, 0U, sizeof(data), data), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Erase(NULL, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Verify(NULL, 0U, sizeof(data), data), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.BlankCheck(NULL, 0U, sizeof(data)), + WH_ERROR_BADARGS); + ck_assert_uint_eq(whFlashH5_Cb.PartitionSize(NULL), 0U); +} +END_TEST + +START_TEST(test_callbacks_reject_null_data) +{ + whFlashH5Ctx ctx; + + 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, 8U, NULL), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, 8U, NULL), + WH_ERROR_BADARGS); +} +END_TEST + +START_TEST(test_program_spans_three_sectors) +{ + whFlashH5Ctx ctx; + const uint32_t off = MOCK_FLASH_SECTOR - 4U; + const uint32_t sz = (2U * MOCK_FLASH_SECTOR) + 8U; + uint8_t data[(2U * MOCK_FLASH_SECTOR) + 8U]; + uint8_t readback[sizeof(data)]; + uint32_t i; + + for (i = 0U; i < sizeof(data); i++) { + data[i] = (uint8_t)(i & 0xFFU); + } + 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, sz, data), WH_ERROR_OK); + memcpy(readback, (void *)(uintptr_t)(MOCK_FLASH_BASE + off), sz); + ck_assert_mem_eq(readback, data, sz); + 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 + sz], 0xFFU); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_writelock_writeunlock_noop) +{ + whFlashH5Ctx ctx; + + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.WriteLock(&ctx, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.WriteUnlock(&ctx, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_OK); +} +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); + tcase_add_test(tc, test_cleanup); + tcase_add_test(tc, test_program_propagates_erase_failure); + tcase_add_test(tc, test_read_returns_flash_contents); + tcase_add_test(tc, test_callbacks_reject_null_context); + tcase_add_test(tc, test_callbacks_reject_null_data); + tcase_add_test(tc, test_program_spans_three_sectors); + tcase_add_test(tc, test_writelock_writeunlock_noop); + 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; +}