From 6c5c811e44cd4783e31d223517b39700410a2b51 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 30 Apr 2026 14:22:00 -0700 Subject: [PATCH 01/13] Add WOLFCRYPT_TZ_WOLFHSM scaffolding for STM32H5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New WOLFCRYPT_TZ_WOLFHSM build flag, mutually exclusive with WOLFCRYPT_TZ_PKCS11 / _PSA / _FWTPM - Extract WOLFHSM_CLIENT_OBJS and WOLFHSM_SERVER_OBJS shared variables; legacy WOLFHSM_CLIENT/SERVER blocks now reference them to share file lists with the new TZ engine - New config/examples/stm32h5-tz-wolfhsm.config (one-line delta from stm32h5-tz-fwtpm.config) - NS test-app RNG seed routes through wcs_get_random (same pattern as PKCS11 / fwTPM) No NSC entries or wolfHSM-specific code yet — Phase 0 only wires up the build flag and validates existing-engine compatibility. Builds cleanly with stm32h5-tz-wolfhsm.config; existing TZ configs and the sim wolfHSM client/server configs continue to build. The four files to stage: - options.mk (mutex guards, shared-vars refactor, TZ_WOLFHSM block) - test-app/Makefile (NS-side TZ_WOLFHSM block + alias) - test-app/wcs/user_settings.h (RNG seed CFG) - config/examples/stm32h5-tz-wolfhsm.config (new) --- config/examples/stm32h5-tz-wolfhsm.config | 35 ++++++++ options.mk | 101 +++++++++++++++------- test-app/Makefile | 11 +++ test-app/wcs/user_settings.h | 3 +- 4 files changed, 116 insertions(+), 34 deletions(-) create mode 100644 config/examples/stm32h5-tz-wolfhsm.config 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/options.mk b/options.mk index 442e20143a..2e7f9c5fee 100644 --- a/options.mk +++ b/options.mk @@ -935,12 +935,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 +1106,22 @@ 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+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + 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 + STACK_USAGE=20000 +endif + OBJS+=$(PUBLIC_KEY_OBJS) ifneq ($(STAGE1),1) OBJS+=$(UPDATE_OBJS) @@ -1301,6 +1329,44 @@ ifeq ($(WOLFBOOT_TEST_SIM_CRYPTOCB),1) endif endif +# Shared wolfHSM client/server object lists. Both the legacy WOLFHSM_CLIENT=1 / +# WOLFHSM_SERVER=1 flags and the WOLFCRYPT_TZ_WOLFHSM=1 TZ engine reference +# these to avoid object-list duplication. +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 + # wolfHSM client options ifeq ($(WOLFHSM_CLIENT),1) WOLFCRYPT_OBJS += \ @@ -1317,19 +1383,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 +1429,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/test-app/Makefile b/test-app/Makefile index 5a8f3f77ef..723daabb35 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -109,6 +109,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 +360,12 @@ 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+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + endif WOLFCRYPT_APP_OBJS := $(patsubst $(WOLFBOOT_LIB_WOLFSSL)/%, \ $(WOLFSSL_LOCAL_OBJDIR)/%, $(WOLFCRYPT_APP_OBJS)) ifneq ($(WOLFCRYPT_TZ_PKCS11),1) diff --git a/test-app/wcs/user_settings.h b/test-app/wcs/user_settings.h index 1f71ff86a8..c52a2c37d4 100644 --- a/test-app/wcs/user_settings.h +++ b/test-app/wcs/user_settings.h @@ -156,7 +156,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) { From 6641ed4c7d89dd0a9859753518167a09a083c09c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 30 Apr 2026 16:34:33 -0700 Subject: [PATCH 02/13] Add wolfHSM TrustZone server + NSC bridge for STM32H5 Phase 1 of the WOLFCRYPT_TZ_WOLFHSM=1 lane: hosts a wolfHSM server in the secure world and exposes it across the NSC boundary via a single packet- shaped veneer (wcs_wolfhsm_transmit), mirroring fwTPM lane 2's shape. NS test-app runs a wolfHSM client; wc_RNG_GenerateBlock with WH_DEV_ID round-trips through the bridge. - include/wolfboot/wcs_wolfhsm.h: NSC entry declarations - src/wolfhsm_callable.c: secure-side server init (RNG + ramsim NVM + comm + transport) and the single-NSC veneer with TOCTOU single-fetch defense on *rspSz - src/wc_callable.c: hook wcs_wolfhsm_init() into wcs_Init() - test-app/wcs/wolfhsm_stub.c: NS .bss buffers + transport context - test-app/wcs/wolfhsm_test.c: client init + CommInit handshake + RNG-via-WH_DEV_ID exerciser; auto-runs at boot - options.mk + test-app/Makefile: wire the WOLFHSM_*_OBJS lists, the ramsim NVM, the wolfssl crypto primitives, and a separate wolfhsm_obj/ build dir so NS-side compiles wolfHSM with WOLFHSM_CFG_ENABLE_CLIENT while the secure side gets WOLFHSM_CFG_ENABLE_SERVER - user_settings.h (both): set WOLF_CRYPTO_CB / HAVE_ANONYMOUS_INLINE_- AGGREGATES=1 / WOLFSSL_KEY_GEN whenever WOLFCRYPT_TZ_WOLFHSM is set; NS RNG seed routes through wcs_get_random - D25 hardware test recipe wired in via WOLFBOOT_TZ_TEST_NO_BKPT Builds clean against stm32h5-tz-wolfhsm.config; m33mu CI passes: wolfHSM CommInit ok (client=1 server=56) wolfHSM RNG ok: ... wolfHSM NSC tests passed [BKPT] imm=0x7f / [EXPECT BKPT] Success --- include/user_settings.h | 6 +- include/wolfboot/wcs_wolfhsm.h | 32 +++++++ options.mk | 64 +++++++++++++ src/wc_callable.c | 7 ++ src/wolfhsm_callable.c | 159 +++++++++++++++++++++++++++++++++ test-app/Makefile | 28 +++++- test-app/app_stm32h5.c | 21 +++++ test-app/wcs/user_settings.h | 8 ++ test-app/wcs/wolfhsm_stub.c | 26 ++++++ test-app/wcs/wolfhsm_test.c | 118 ++++++++++++++++++++++++ 10 files changed, 466 insertions(+), 3 deletions(-) create mode 100644 include/wolfboot/wcs_wolfhsm.h create mode 100644 src/wolfhsm_callable.c create mode 100644 test-app/wcs/wolfhsm_stub.c create mode 100644 test-app/wcs/wolfhsm_test.c 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..e0bc69a9ff --- /dev/null +++ b/include/wolfboot/wcs_wolfhsm.h @@ -0,0 +1,32 @@ +/* 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 + +/* Match wolfHSM's WH_COMM_MTU; bridge buffers are sized to this. */ +#ifndef WCS_WOLFHSM_MAX_REQ_SIZE +#define WCS_WOLFHSM_MAX_REQ_SIZE 1288U +#endif + +#ifndef WCS_WOLFHSM_MAX_RSP_SIZE +#define WCS_WOLFHSM_MAX_RSP_SIZE 1288U +#endif + +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/options.mk b/options.mk index 2e7f9c5fee..474b3e78ba 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 @@ -1111,7 +1150,10 @@ ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) 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) @@ -1119,6 +1161,28 @@ ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) else LDFLAGS+=--specs=nano.specs endif + WOLFCRYPT_OBJS+=src/store_sbrk.o + WOLFCRYPT_OBJS+=src/wolfhsm_callable.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)/src/wh_flash_ramsim.o + WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz/wh_transport_nsc.o STACK_USAGE=20000 endif 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..1d4644f8ab --- /dev/null +++ b/src/wolfhsm_callable.c @@ -0,0 +1,159 @@ +/* wolfhsm_callable.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include + +#include "store_sbrk.h" +#include "wolfboot/wcs_wolfhsm.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_flash_ramsim.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)); +} + +/* Phase 1b uses a 32 KiB ramsim partition pair for the NVM backend; Phase 3 + * swaps this for a flash-backed adapter over wolfBoot's hal_flash_*. + * pageSize must match WHFU_BYTES_PER_UNIT (8) — wolfHSM programs the flash + * one unit at a time, so a larger pageSize causes the modulo check in + * whFlashRamsim_Program to fail. */ +#define WCS_WOLFHSM_RAMSIM_SIZE (32U * 1024U) +#define WCS_WOLFHSM_RAMSIM_SECTOR (8U * 1024U) +#define WCS_WOLFHSM_RAMSIM_PAGE 8U + +static uint8_t g_ramsim_buf[WCS_WOLFHSM_RAMSIM_SIZE]; +static whFlashRamsimCtx g_ramsim_ctx; +static whFlashRamsimCfg g_ramsim_cfg = { + .memory = g_ramsim_buf, + .size = WCS_WOLFHSM_RAMSIM_SIZE, + .sectorSize = WCS_WOLFHSM_RAMSIM_SECTOR, + .pageSize = WCS_WOLFHSM_RAMSIM_PAGE, + .erasedByte = 0xFFU, + .initData = NULL, +}; +static whFlashCb g_flash_cb = WH_FLASH_RAMSIM_CB; + +static whNvmFlashContext g_nvm_flash_ctx; +static whNvmFlashConfig g_nvm_flash_cfg = { + .cb = &g_flash_cb, + .context = &g_ramsim_ctx, + .config = &g_ramsim_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 = 56, /* server identifier; NS client uses client_id=1 */ +}; +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; + + rc = wc_InitRng(g_crypto_ctx.rng); + if (rc != 0) { + return; + } + rc = wh_Nvm_Init(&g_nvm_ctx, &g_nvm_cfg); + if (rc != WH_ERROR_OK) { + return; + } + rc = wh_Server_Init(&g_server, &g_server_cfg); + if (rc != WH_ERROR_OK) { + return; + } + (void)wh_Server_SetConnected(&g_server, WH_COMM_CONNECTED); + g_wolfhsm_ready = 1; +} + +/* Single NSC veneer. Per call: validate the NS pointers/sizes (single-fetch + * defeats TOCTOU on *rspSz), park the buffers in the secure-side transport + * context, run wh_Server_HandleRequestMessage exactly once, write back the + * captured response size. */ +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 of the caller-supplied capacity; subsequent code uses + * only this local copy. The NS caller cannot mutate it under us. */ + rsp_capacity = *rspSz; + + if (cmdSz == 0U || cmdSz > WCS_WOLFHSM_MAX_REQ_SIZE) { + return WH_ERROR_BADARGS; + } + if (rsp_capacity == 0U || rsp_capacity > WCS_WOLFHSM_MAX_RSP_SIZE) { + 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; + } + return rc; +} + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/Makefile b/test-app/Makefile index 723daabb35..578d3b9b94 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -7,10 +7,13 @@ WOLFBOOT_LIB_WOLFSSL?=../lib/wolfssl WOLFBOOT_LIB_WOLFTPM?=../lib/wolfTPM 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 @@ -364,7 +367,18 @@ ifeq ($(TZEN),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 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)) @@ -1018,6 +1032,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)" @@ -1115,9 +1130,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..a81e98098d 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 +extern int cmd_wolfhsm_test(const char *args); +#endif #define CMD_BUFFER_SIZE 256 @@ -1505,6 +1508,24 @@ 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 == 0) { + printf("WOLFHSM_TZ_TEST_PASS\r\n"); + while (1) { } + } else { + printf("WOLFHSM_TZ_TEST_FAIL\r\n"); + while (1) { } + } +#else + if (ret == 0) + 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 c52a2c37d4..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 */ 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..c6ab12bae6 --- /dev/null +++ b/test-app/wcs/wolfhsm_test.c @@ -0,0 +1,118 @@ +/* wolfhsm_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include +#include + +#include "wolfboot/wcs_wolfhsm.h" + +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" + +#include "wolfssl/wolfcrypt/random.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; +} + +/* + * Phase 1c exerciser. Initializes the wolfHSM client (which auto-registers + * the wolfCrypt cryptocb under WH_DEV_ID), runs the CommInit handshake, then + * exercises a real crypto op (RNG) routed through the secure-side server. + */ +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 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 rc; + } + + 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 rc; + } + + 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 rc; + } + + printf("wolfHSM NSC tests passed\r\n"); + + (void)wh_Client_Cleanup(&client); + return 0; +} + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ From c4dfb6fa29bb6b8bad01aac7c00d829606edc92a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 10:14:30 -0700 Subject: [PATCH 03/13] Test on H5 HW wire WOLFBOOT_TZ_TEST_NO_BKPT make-flag through CFLAGS The auto-test block in app_stm32h5.c gates its bkpt #0x7f / #0x7e on WOLFBOOT_TZ_TEST_NO_BKPT, but the make-flag was never propagated to CFLAGS. Building with make WOLFBOOT_TZ_TEST_NO_BKPT=1 now actually swaps the BKPTs for printf(WOLFHSM_TZ_TEST_{PASS,FAIL}) + while(1) loops, which is the canonical hardware-test path (per D25) since real silicon HardFaults on bkpt without a debugger attached. Verified on NUCLEO-H563ZI hardware: wolfHSM CommInit ok (client=1 server=56) wolfHSM RNG ok: <16 random bytes> wolfHSM NSC tests passed WOLFHSM_TZ_TEST_PASS --- test-app/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-app/Makefile b/test-app/Makefile index 578d3b9b94..6961a0f1db 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -369,6 +369,9 @@ ifeq ($(TZEN),1) 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 From 5bd3ea0eceb4835f9cfbc2b1cf0a75595e0473ea Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:07:16 -0700 Subject: [PATCH 04/13] Phase 1 cleanup: drop debug-era artifacts and Phase labels - include/wolfboot/wcs_wolfhsm.h: remove WCS_WOLFHSM_MAX_REQ_SIZE / MAX_RSP_SIZE macros that hardcoded 1288U; the value is just WH_COMM_MTU and silently desyncs if WOLFHSM_CFG_COMM_DATA_LEN changes - src/wolfhsm_callable.c: use WH_COMM_MTU directly in size guards; replace bare 56 server_id with WCS_WOLFHSM_SERVER_ID; drop Phase 1b / Phase 3 comment, keep only the real-HW pageSize=8 invariant - test-app/wcs/wolfhsm_test.c: drop unused wolfboot/wcs_wolfhsm.h include; reword the Phase 1c exerciser header to a stable description m33mu still green (CommInit + RNG round-trip + BKPT 0x7f). --- include/wolfboot/wcs_wolfhsm.h | 9 --------- src/wolfhsm_callable.c | 12 ++++++------ test-app/wcs/wolfhsm_test.c | 10 +++------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/include/wolfboot/wcs_wolfhsm.h b/include/wolfboot/wcs_wolfhsm.h index e0bc69a9ff..da65c33dc3 100644 --- a/include/wolfboot/wcs_wolfhsm.h +++ b/include/wolfboot/wcs_wolfhsm.h @@ -13,15 +13,6 @@ #ifdef WOLFCRYPT_TZ_WOLFHSM -/* Match wolfHSM's WH_COMM_MTU; bridge buffers are sized to this. */ -#ifndef WCS_WOLFHSM_MAX_REQ_SIZE -#define WCS_WOLFHSM_MAX_REQ_SIZE 1288U -#endif - -#ifndef WCS_WOLFHSM_MAX_RSP_SIZE -#define WCS_WOLFHSM_MAX_RSP_SIZE 1288U -#endif - int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, uint8_t *rsp, uint32_t *rspSz); diff --git a/src/wolfhsm_callable.c b/src/wolfhsm_callable.c index 1d4644f8ab..09cf680d81 100644 --- a/src/wolfhsm_callable.c +++ b/src/wolfhsm_callable.c @@ -36,15 +36,15 @@ void *_sbrk(unsigned int incr) (uint32_t)(&_heap_size)); } -/* Phase 1b uses a 32 KiB ramsim partition pair for the NVM backend; Phase 3 - * swaps this for a flash-backed adapter over wolfBoot's hal_flash_*. - * pageSize must match WHFU_BYTES_PER_UNIT (8) — wolfHSM programs the flash +/* pageSize must match WHFU_BYTES_PER_UNIT (8) — wolfHSM programs the flash * one unit at a time, so a larger pageSize causes the modulo check in * whFlashRamsim_Program to fail. */ #define WCS_WOLFHSM_RAMSIM_SIZE (32U * 1024U) #define WCS_WOLFHSM_RAMSIM_SECTOR (8U * 1024U) #define WCS_WOLFHSM_RAMSIM_PAGE 8U +#define WCS_WOLFHSM_SERVER_ID 56U + static uint8_t g_ramsim_buf[WCS_WOLFHSM_RAMSIM_SIZE]; static whFlashRamsimCtx g_ramsim_ctx; static whFlashRamsimCfg g_ramsim_cfg = { @@ -78,7 +78,7 @@ static whCommServerConfig g_comm_cfg = { .transport_context = &g_srv_tx_ctx, .transport_cb = &whTransportNscServer_Cb, .transport_config = &g_srv_tx_cfg, - .server_id = 56, /* server identifier; NS client uses client_id=1 */ + .server_id = WCS_WOLFHSM_SERVER_ID, }; static whServerConfig g_server_cfg = { .comm_config = &g_comm_cfg, @@ -129,10 +129,10 @@ int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, * only this local copy. The NS caller cannot mutate it under us. */ rsp_capacity = *rspSz; - if (cmdSz == 0U || cmdSz > WCS_WOLFHSM_MAX_REQ_SIZE) { + if (cmdSz == 0U || cmdSz > WH_COMM_MTU) { return WH_ERROR_BADARGS; } - if (rsp_capacity == 0U || rsp_capacity > WCS_WOLFHSM_MAX_RSP_SIZE) { + if (rsp_capacity == 0U || rsp_capacity > WH_COMM_MTU) { return WH_ERROR_BADARGS; } if (!g_wolfhsm_ready) { diff --git a/test-app/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c index c6ab12bae6..ba46899883 100644 --- a/test-app/wcs/wolfhsm_test.c +++ b/test-app/wcs/wolfhsm_test.c @@ -11,8 +11,6 @@ #include #include -#include "wolfboot/wcs_wolfhsm.h" - #include "wolfhsm/wh_client.h" #include "wolfhsm/wh_comm.h" #include "wolfhsm/wh_error.h" @@ -59,11 +57,9 @@ static int wolfhsm_test_rng(void) return 0; } -/* - * Phase 1c exerciser. Initializes the wolfHSM client (which auto-registers - * the wolfCrypt cryptocb under WH_DEV_ID), runs the CommInit handshake, then - * exercises a real crypto op (RNG) routed through the secure-side server. - */ +/* Initializes the wolfHSM client (auto-registers the wolfCrypt cryptocb + * under WH_DEV_ID), runs the CommInit handshake, exercises one crypto + * round-trip (RNG) through the secure-side server. */ int cmd_wolfhsm_test(const char *args) { static const whTransportNscClientConfig nsc_cfg = { 0 }; From fce7bff1cf8956128f7746439a3ac98513a9b199 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:24:48 -0700 Subject: [PATCH 05/13] Phase 2: SHA256 + AES cached-key round-trips through the bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmd_wolfhsm_test now exercises three crypto round-trips: - SHA256(abc) → digest matches FIPS 180-2 Appendix B.1 vector. - AES-128-CBC encrypt with a cached HSM key: wh_Client_KeyCache(WH_NVM_FLAGS_USAGE_ENCRYPT) imports the key to the server's keystore; wc_AesInit + wh_Client_AesSetKeyId links the wolfCrypt Aes struct to the cached keyId; the cryptocb dispatches wc_AesCbcEncrypt to the server, which runs the AES op against its in-cache key. Ciphertext compared against the FIPS 197 Appendix B vector. Key evicted on exit. - RNG via WH_DEV_ID (already in Phase 1). Each step prints a labelled UART line (wolfHSM RNG/SHA256/AES ok). Verified on m33mu; the AES path forces WH_NVM_FLAGS_USAGE_ENCRYPT since cached keys without usage flags fail with WH_ERROR_USAGE. Stack budget unchanged (STACK_USAGE=20000 sufficient). PKCS11 / PSA / --- test-app/wcs/wolfhsm_test.c | 128 +++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) diff --git a/test-app/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c index ba46899883..8469b27e09 100644 --- a/test-app/wcs/wolfhsm_test.c +++ b/test-app/wcs/wolfhsm_test.c @@ -12,10 +12,14 @@ #include #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 "wolfssl/wolfcrypt/aes.h" #include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/sha256.h" #include "wh_transport_nsc.h" @@ -57,9 +61,117 @@ static int wolfhsm_test_rng(void) 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 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; + } + + 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: + wc_AesFree(&aes); + (void)wh_Client_KeyEvict(client, keyId); + return rc; +} + /* Initializes the wolfHSM client (auto-registers the wolfCrypt cryptocb - * under WH_DEV_ID), runs the CommInit handshake, exercises one crypto - * round-trip (RNG) through the secure-side server. */ + * under WH_DEV_ID), runs the CommInit handshake, exercises crypto + * round-trips (RNG, SHA256, AES with cached key) through the + * secure-side server. */ int cmd_wolfhsm_test(const char *args) { static const whTransportNscClientConfig nsc_cfg = { 0 }; @@ -105,6 +217,18 @@ int cmd_wolfhsm_test(const char *args) return rc; } + rc = wolfhsm_test_sha256(); + if (rc != 0) { + (void)wh_Client_Cleanup(&client); + return rc; + } + + rc = wolfhsm_test_aes_cached(&client); + if (rc != 0) { + (void)wh_Client_Cleanup(&client); + return rc; + } + printf("wolfHSM NSC tests passed\r\n"); (void)wh_Client_Cleanup(&client); From c1f70eeffe285d04aba22013468bd9480a8ddfb3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:43:01 -0700 Subject: [PATCH 06/13] Phase 3: flash-backed NVM via whFlashCb adapter over hal_flash_* Replaces the Phase 1 ramsim NVM with a real flash-backed store living in the existing wolfBoot keyvault region (FLASH_KEYVAULT, 112 KiB at 0x0C040000), so wolfHSM-cached keys persist across reset. - include/wolfboot/wolfhsm_flash_hal.h: whFlashH5Ctx (base / size / partition_size) and whFlashH5_Cb extern. - src/wolfhsm_flash_hal.c: 10-callback whFlashCb adapter wrapping hal_flash_unlock/lock/write/erase. PartitionSize is configurable via the context (default 32 KiB per partition; two partitions = 64 KiB used, 48 KiB headroom in the 112 KiB keyvault). Direct memory reads for Read/Verify/BlankCheck. WriteLock/Unlock are no-ops on H5 (lock is global). - src/wolfhsm_callable.c: drop ramsim ctx/cfg + wh_flash_ramsim include; wire wh_NvmFlashConfig to the new adapter; vault address / size sourced from the linker symbols _flash_keyvault / _flash_keyvault_size, matching the PSA / PKCS11 store pattern. - options.mk: drop wh_flash_ramsim.o, add src/wolfhsm_flash_hal.o. Verified on m33mu with --persist: CommInit handshake + RNG + SHA256 + AES cached-key round-trips all pass through wolfHSM's two-partition journaling layer talking to actual flash. Persistence-across-reset test (P3.3) follows in a separate commit. PKCS11 / PSA / fwTPM regression builds remain clean. --- include/wolfboot/wolfhsm_flash_hal.h | 37 +++++ options.mk | 2 +- src/wolfhsm_callable.c | 44 +++--- src/wolfhsm_flash_hal.c | 206 +++++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 24 deletions(-) create mode 100644 include/wolfboot/wolfhsm_flash_hal.h create mode 100644 src/wolfhsm_flash_hal.c diff --git a/include/wolfboot/wolfhsm_flash_hal.h b/include/wolfboot/wolfhsm_flash_hal.h new file mode 100644 index 0000000000..b06f84bae7 --- /dev/null +++ b/include/wolfboot/wolfhsm_flash_hal.h @@ -0,0 +1,37 @@ +/* wolfhsm_flash_hal.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +/* + * Adapter that exposes wolfBoot's hal_flash_*() API as a wolfHSM whFlashCb, + * letting the secure-side wolfHSM server persist its NVM in real flash + * instead of the ramsim used during bring-up. + */ + +#ifndef WOLFBOOT_WOLFHSM_FLASH_HAL_H +#define WOLFBOOT_WOLFHSM_FLASH_HAL_H + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include + +#include "wolfhsm/wh_flash.h" + +typedef struct { + uint32_t base; /* Absolute flash address of the wolfHSM NVM + region (must be 8 KiB-aligned) */ + uint32_t size; /* Size of the region in bytes (>= 2 * + partition_size, multiple of 8 KiB) */ + uint32_t partition_size; /* Per-partition size in bytes; wolfHSM uses + two partitions (active + backup) for + journaling. Must be a multiple of 8 KiB. */ +} 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 474b3e78ba..e05496381e 100644 --- a/options.mk +++ b/options.mk @@ -1163,6 +1163,7 @@ ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) 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 @@ -1181,7 +1182,6 @@ ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) endif endif WOLFHSM_OBJS+=$(WOLFHSM_SERVER_OBJS) - WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/src/wh_flash_ramsim.o WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz/wh_transport_nsc.o STACK_USAGE=20000 endif diff --git a/src/wolfhsm_callable.c b/src/wolfhsm_callable.c index 09cf680d81..ea8722e7f5 100644 --- a/src/wolfhsm_callable.c +++ b/src/wolfhsm_callable.c @@ -12,6 +12,7 @@ #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" @@ -19,7 +20,6 @@ #include "wolfhsm/wh_comm.h" #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_flash.h" -#include "wolfhsm/wh_flash_ramsim.h" #include "wolfhsm/wh_nvm.h" #include "wolfhsm/wh_nvm_flash.h" #include "wolfhsm/wh_server.h" @@ -36,32 +36,30 @@ void *_sbrk(unsigned int incr) (uint32_t)(&_heap_size)); } -/* pageSize must match WHFU_BYTES_PER_UNIT (8) — wolfHSM programs the flash - * one unit at a time, so a larger pageSize causes the modulo check in - * whFlashRamsim_Program to fail. */ -#define WCS_WOLFHSM_RAMSIM_SIZE (32U * 1024U) -#define WCS_WOLFHSM_RAMSIM_SECTOR (8U * 1024U) -#define WCS_WOLFHSM_RAMSIM_PAGE 8U - -#define WCS_WOLFHSM_SERVER_ID 56U - -static uint8_t g_ramsim_buf[WCS_WOLFHSM_RAMSIM_SIZE]; -static whFlashRamsimCtx g_ramsim_ctx; -static whFlashRamsimCfg g_ramsim_cfg = { - .memory = g_ramsim_buf, - .size = WCS_WOLFHSM_RAMSIM_SIZE, - .sectorSize = WCS_WOLFHSM_RAMSIM_SECTOR, - .pageSize = WCS_WOLFHSM_RAMSIM_PAGE, - .erasedByte = 0xFFU, - .initData = NULL, +#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 const whFlashH5Ctx g_flash_cfg = { + .base = (uint32_t)&_flash_keyvault, + .size = (uint32_t)&_flash_keyvault_size, + .partition_size = WCS_WOLFHSM_PARTITION_SIZE, }; -static whFlashCb g_flash_cb = WH_FLASH_RAMSIM_CB; static whNvmFlashContext g_nvm_flash_ctx; static whNvmFlashConfig g_nvm_flash_cfg = { - .cb = &g_flash_cb, - .context = &g_ramsim_ctx, - .config = &g_ramsim_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; diff --git a/src/wolfhsm_flash_hal.c b/src/wolfhsm_flash_hal.c new file mode 100644 index 0000000000..6270bee76a --- /dev/null +++ b/src/wolfhsm_flash_hal.c @@ -0,0 +1,206 @@ +/* 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 "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" + +/* + * STM32H5 page (= erase) size and program-quad-word size. The dual-bank + * H5 erase granularity is 8 KiB; flash programming happens in 16-byte + * quad-word units. + */ +#define WHFH5_SECTOR_SIZE (8U * 1024U) +#define WHFH5_PROGRAM_UNIT 16U + + +static int _Init(void *context, const void *config) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + + if (ctx == NULL || config == NULL) { + return WH_ERROR_BADARGS; + } + *ctx = *((const whFlashH5Ctx *)config); + + if (ctx->base == 0U || ctx->size == 0U || ctx->partition_size == 0U || + (ctx->base % WHFH5_SECTOR_SIZE) != 0U || + (ctx->size % WHFH5_SECTOR_SIZE) != 0U || + (ctx->partition_size % WHFH5_SECTOR_SIZE) != 0U || + ctx->size < (uint32_t)2 * ctx->partition_size) { + return WH_ERROR_BADARGS; + } + 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; + if (ctx == NULL) { + return 0U; + } + return ctx->partition_size; +} + +/* + * STM32H5 has a single global flash unlock; per-region lock/unlock isn't + * available. Program/Erase wrap the unlock+op+lock cycle themselves, so + * the wh_FlashUnit_Program helper's "WriteUnlock around batch of writes" + * pattern is satisfied without per-call hardware action here. + */ +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; + int rc; + + 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_write programs in H5 quad-word (16 byte) chunks; partial + * quad-words at either end fold the existing flash content so any + * `size` is acceptable here. The H5 ECC rule ("each quad-word may be + * programmed at most once between erases") is satisfied as long as + * wolfHSM's unit writes don't share a quad-word, which holds for the + * 32 KiB-aligned partitions / 8-byte units we use. */ + hal_flash_unlock(); + rc = hal_flash_write(ctx->base + offset, data, (int)size); + hal_flash_lock(); + return (rc == 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 */ From c6f8cf31a12961930bfacd6f494d0853452f0fce Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:54:53 -0700 Subject: [PATCH 07/13] Phase 3: keystore-persist test mirrors PKCS11's first/second-boot pattern --- test-app/app_stm32h5.c | 8 +++-- test-app/wcs/wolfhsm_test.c | 64 +++++++++++++++++++++++++++++++------ test-app/wcs/wolfhsm_test.h | 19 +++++++++++ 3 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 test-app/wcs/wolfhsm_test.h diff --git a/test-app/app_stm32h5.c b/test-app/app_stm32h5.c index a81e98098d..adb89c682b 100644 --- a/test-app/app_stm32h5.c +++ b/test-app/app_stm32h5.c @@ -219,7 +219,7 @@ static int cmd_tpm_quote(const char *args); static int cmd_fwtpm_test(const char *args); #endif #ifdef WOLFCRYPT_TZ_WOLFHSM -extern int cmd_wolfhsm_test(const char *args); +#include "wcs/wolfhsm_test.h" #endif @@ -1511,7 +1511,7 @@ void main(void) #ifdef WOLFCRYPT_TZ_WOLFHSM ret = cmd_wolfhsm_test(NULL); #ifdef WOLFBOOT_TZ_TEST_NO_BKPT - if (ret == 0) { + if (ret == WOLFHSM_TEST_FIRST_BOOT_OK || ret == WOLFHSM_TEST_SECOND_BOOT_OK) { printf("WOLFHSM_TZ_TEST_PASS\r\n"); while (1) { } } else { @@ -1519,7 +1519,9 @@ void main(void) while (1) { } } #else - if (ret == 0) + 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"); diff --git a/test-app/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c index 8469b27e09..1675395a0c 100644 --- a/test-app/wcs/wolfhsm_test.c +++ b/test-app/wcs/wolfhsm_test.c @@ -11,11 +11,14 @@ #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/random.h" @@ -168,10 +171,44 @@ static int wolfhsm_test_aes_cached(whClientContext *client) return rc; } -/* Initializes the wolfHSM client (auto-registers the wolfCrypt cryptocb - * under WH_DEV_ID), runs the CommInit handshake, exercises crypto - * round-trips (RNG, SHA256, AES with cached key) through the - * secure-side server. */ +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; + return 0; + } + + printf("wolfHSM first boot path, committing key to NVM\r\n"); + keyId = WH_MAKE_KEYID(0, WCS_WOLFHSM_CLIENT_ID, 1); + 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; + } + *boot_state = WOLFHSM_TEST_FIRST_BOOT_OK; + return 0; +} + int cmd_wolfhsm_test(const char *args) { static const whTransportNscClientConfig nsc_cfg = { 0 }; @@ -180,6 +217,7 @@ int cmd_wolfhsm_test(const char *args) whClientContext client; uint32_t out_clientid = 0; uint32_t out_serverid = 0; + int boot_state = WOLFHSM_TEST_FAIL; int rc; (void)args; @@ -198,14 +236,14 @@ int cmd_wolfhsm_test(const char *args) rc = wh_Client_Init(&client, &cfg); if (rc != WH_ERROR_OK) { printf("wolfHSM Init failed: %d\r\n", rc); - return 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 rc; + return WOLFHSM_TEST_FAIL; } printf("wolfHSM CommInit ok (client=%u server=%u)\r\n", @@ -214,25 +252,31 @@ int cmd_wolfhsm_test(const char *args) rc = wolfhsm_test_rng(); if (rc != 0) { (void)wh_Client_Cleanup(&client); - return rc; + return WOLFHSM_TEST_FAIL; } rc = wolfhsm_test_sha256(); if (rc != 0) { (void)wh_Client_Cleanup(&client); - return rc; + return WOLFHSM_TEST_FAIL; } rc = wolfhsm_test_aes_cached(&client); if (rc != 0) { (void)wh_Client_Cleanup(&client); - return rc; + 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 0; + 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 */ From 4dbb6ca3e3ccc9b87130d738e6b4ab8570c9d63d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 12:24:53 -0700 Subject: [PATCH 08/13] wolfhsm_flash_hal: model on psa_store sector-cache pattern for H5 ECC e2e real HW tests with first and second boot persistant trip --- include/wolfboot/wolfhsm_flash_hal.h | 19 +++----- src/wolfhsm_flash_hal.c | 65 +++++++++++++++------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/include/wolfboot/wolfhsm_flash_hal.h b/include/wolfboot/wolfhsm_flash_hal.h index b06f84bae7..77720ff5b6 100644 --- a/include/wolfboot/wolfhsm_flash_hal.h +++ b/include/wolfboot/wolfhsm_flash_hal.h @@ -5,12 +5,6 @@ * This file is part of wolfBoot. */ -/* - * Adapter that exposes wolfBoot's hal_flash_*() API as a wolfHSM whFlashCb, - * letting the secure-side wolfHSM server persist its NVM in real flash - * instead of the ramsim used during bring-up. - */ - #ifndef WOLFBOOT_WOLFHSM_FLASH_HAL_H #define WOLFBOOT_WOLFHSM_FLASH_HAL_H @@ -20,14 +14,13 @@ #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; /* Absolute flash address of the wolfHSM NVM - region (must be 8 KiB-aligned) */ - uint32_t size; /* Size of the region in bytes (>= 2 * - partition_size, multiple of 8 KiB) */ - uint32_t partition_size; /* Per-partition size in bytes; wolfHSM uses - two partitions (active + backup) for - journaling. Must be a multiple of 8 KiB. */ + uint32_t base; + uint32_t size; + uint32_t partition_size; } whFlashH5Ctx; extern const whFlashCb whFlashH5_Cb; diff --git a/src/wolfhsm_flash_hal.c b/src/wolfhsm_flash_hal.c index 6270bee76a..8e3590bf45 100644 --- a/src/wolfhsm_flash_hal.c +++ b/src/wolfhsm_flash_hal.c @@ -16,14 +16,15 @@ #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_flash.h" -/* - * STM32H5 page (= erase) size and program-quad-word size. The dual-bank - * H5 erase granularity is 8 KiB; flash programming happens in 16-byte - * quad-word units. - */ -#define WHFH5_SECTOR_SIZE (8U * 1024U) -#define WHFH5_PROGRAM_UNIT 16U +#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) { @@ -55,18 +56,9 @@ static int _Cleanup(void *context) static uint32_t _PartitionSize(void *context) { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; - if (ctx == NULL) { - return 0U; - } - return ctx->partition_size; + return (ctx == NULL) ? 0U : ctx->partition_size; } -/* - * STM32H5 has a single global flash unlock; per-region lock/unlock isn't - * available. Program/Erase wrap the unlock+op+lock cycle themselves, so - * the wh_FlashUnit_Program helper's "WriteUnlock around batch of writes" - * pattern is satisfied without per-call hardware action here. - */ static int _WriteLock(void *context, uint32_t offset, uint32_t size) { (void)context; @@ -103,7 +95,7 @@ static int _Program(void *context, uint32_t offset, uint32_t size, const uint8_t *data) { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; - int rc; + uint32_t written = 0U; if (ctx == NULL || (size != 0U && data == NULL)) { return WH_ERROR_BADARGS; @@ -114,16 +106,29 @@ static int _Program(void *context, uint32_t offset, uint32_t size, if (size == 0U) { return WH_ERROR_OK; } - /* hal_flash_write programs in H5 quad-word (16 byte) chunks; partial - * quad-words at either end fold the existing flash content so any - * `size` is acceptable here. The H5 ECC rule ("each quad-word may be - * programmed at most once between erases") is satisfied as long as - * wolfHSM's unit writes don't share a quad-word, which holds for the - * 32 KiB-aligned partitions / 8-byte units we use. */ - hal_flash_unlock(); - rc = hal_flash_write(ctx->base + offset, data, (int)size); - hal_flash_lock(); - return (rc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; + + while (written < size) { + uint32_t in_sector_off = (offset + written) % WHFH5_SECTOR_SIZE; + uint32_t sector_base = (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_base), + WHFH5_SECTOR_SIZE); + memcpy(cached_sector + in_sector_off, data + written, chunk); + + hal_flash_unlock(); + hal_flash_erase(ctx->base + sector_base, WHFH5_SECTOR_SIZE); + hal_flash_write(ctx->base + sector_base, cached_sector, + WHFH5_SECTOR_SIZE); + hal_flash_lock(); + + written += chunk; + } + return WH_ERROR_OK; } static int _Erase(void *context, uint32_t offset, uint32_t size) @@ -171,9 +176,9 @@ static int _Verify(void *context, uint32_t offset, uint32_t size, static int _BlankCheck(void *context, uint32_t offset, uint32_t size) { - whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; const uint8_t *p; - uint32_t i; + uint32_t i; if (ctx == NULL) { return WH_ERROR_BADARGS; From 86658931fd48594b5e1770521a6a180c4da9c6d5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 12:35:46 -0700 Subject: [PATCH 09/13] Phase 4: docs/wolfHSM.md TZ section + CI lane for stm32h5-tz-wolfhsm - docs/wolfHSM.md: append a STM32H5 TrustZone Engine section alongside the simulator section. Covers build (incl. WOLFBOOT_TZ_TEST_NO_BKPT for hardware), flashing via set-stm32-tz-option-bytes.sh + STM32_Programmer_CLI, expected UART output for both boots, and notes the H5 quad-word ECC handling shared with psa_store / pkcs11_store. Existing client/server content untouched. - .github/workflows/trustzone-emulator-tests.yml: add a wolfHSM step that mirrors the PKCS11 first/second-boot pattern -- one m33mu --persist run with --expect-bkpt 0x7d after the first boot path, committing key to NVM message, then a second --persist run with --expect-bkpt 0x7f after the restored persisted key message. --- .../workflows/trustzone-emulator-tests.yml | 46 +++++++++++++++++ docs/wolfHSM.md | 49 +++++++++++++++++++ 2 files changed, 95 insertions(+) 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/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. From a9ef514055a54ea71329b4f009ced2fda6cd40f1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 13:10:21 -0700 Subject: [PATCH 10/13] Phase 4: add wolfHSM section to STM32-TZ.md Add a wolfHSM section to docs/STM32-TZ.md alongside the PKCS11 and PSA sections, with a cross-reference to the dedicated docs/wolfHSM.md for the full STM32H5 build/flash/test recipe. --- docs/STM32-TZ.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 From b1c5d3341cf70556b0219386f5491cf4dae0f062 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 16:05:36 -0700 Subject: [PATCH 11/13] WOLFCRYPT_TZ_WOLFHSM: Skoll review fixes + flash-adapter unit test - callable: runtime flash config init (drop non-portable static cast), panic on init failures, volatile *rspSz read, clear borrowed NS pointers post-dispatch. - flash_hal: propagate hal_flash_* errors, hoist unlock/lock outside loop, per-iteration cached_sector wipe, validate config before copying into ctx, rename sector_base to sector_offset. - test-app: aes_inited guard, KeyEvict after KeyCommit, ForceZero consistency, drop redundant keyId reassignment. - test-app/Makefile: WOLFBOOT_LIB_WOLFHSM default for standalone test-app builds. - tools/unit-tests/unit-wolfhsm_flash_hal.c: 10-test host unit test for the flash adapter, modeled on unit-psa_store. CMSE pointer-range checks intentionally not applied: m33mu lacks TT/TTAT, and PKCS11/PSA/fwTPM siblings all skip CMSE --- src/wolfhsm_callable.c | 40 +-- src/wolfhsm_flash_hal.c | 48 ++-- test-app/Makefile | 1 + test-app/wcs/wolfhsm_test.c | 15 +- tools/unit-tests/Makefile | 8 +- tools/unit-tests/unit-wolfhsm_flash_hal.c | 312 ++++++++++++++++++++++ 6 files changed, 387 insertions(+), 37 deletions(-) create mode 100644 tools/unit-tests/unit-wolfhsm_flash_hal.c diff --git a/src/wolfhsm_callable.c b/src/wolfhsm_callable.c index ea8722e7f5..85b5804c70 100644 --- a/src/wolfhsm_callable.c +++ b/src/wolfhsm_callable.c @@ -10,6 +10,7 @@ #include #include +#include "loader.h" #include "store_sbrk.h" #include "wolfboot/wcs_wolfhsm.h" #include "wolfboot/wolfhsm_flash_hal.h" @@ -49,11 +50,9 @@ extern uint32_t _flash_keyvault; extern uint32_t _flash_keyvault_size; static whFlashH5Ctx g_flash_ctx; -static const whFlashH5Ctx g_flash_cfg = { - .base = (uint32_t)&_flash_keyvault, - .size = (uint32_t)&_flash_keyvault_size, - .partition_size = WCS_WOLFHSM_PARTITION_SIZE, -}; +/* 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 = { @@ -94,26 +93,29 @@ 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) { - return; + wolfBoot_panic(); } rc = wh_Nvm_Init(&g_nvm_ctx, &g_nvm_cfg); if (rc != WH_ERROR_OK) { - return; + wolfBoot_panic(); } rc = wh_Server_Init(&g_server, &g_server_cfg); if (rc != WH_ERROR_OK) { - return; + wolfBoot_panic(); + } + rc = wh_Server_SetConnected(&g_server, WH_COMM_CONNECTED); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); } - (void)wh_Server_SetConnected(&g_server, WH_COMM_CONNECTED); g_wolfhsm_ready = 1; } -/* Single NSC veneer. Per call: validate the NS pointers/sizes (single-fetch - * defeats TOCTOU on *rspSz), park the buffers in the secure-side transport - * context, run wh_Server_HandleRequestMessage exactly once, write back the - * captured response size. */ int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, uint8_t *rsp, uint32_t *rspSz) { @@ -123,9 +125,8 @@ int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, if (cmd == NULL || rsp == NULL || rspSz == NULL) { return WH_ERROR_BADARGS; } - /* Single fetch of the caller-supplied capacity; subsequent code uses - * only this local copy. The NS caller cannot mutate it under us. */ - rsp_capacity = *rspSz; + /* 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; @@ -151,6 +152,13 @@ int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, } 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; } diff --git a/src/wolfhsm_flash_hal.c b/src/wolfhsm_flash_hal.c index 8e3590bf45..d11e730924 100644 --- a/src/wolfhsm_flash_hal.c +++ b/src/wolfhsm_flash_hal.c @@ -13,6 +13,9 @@ #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" @@ -28,20 +31,22 @@ static uint8_t cached_sector[WHFH5_SECTOR_SIZE]; static int _Init(void *context, const void *config) { - whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + const whFlashH5Ctx *cfg = (const whFlashH5Ctx *)config; + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; - if (ctx == NULL || config == NULL) { + if (ctx == NULL || cfg == NULL) { return WH_ERROR_BADARGS; } - *ctx = *((const whFlashH5Ctx *)config); - if (ctx->base == 0U || ctx->size == 0U || ctx->partition_size == 0U || - (ctx->base % WHFH5_SECTOR_SIZE) != 0U || - (ctx->size % WHFH5_SECTOR_SIZE) != 0U || - (ctx->partition_size % WHFH5_SECTOR_SIZE) != 0U || - ctx->size < (uint32_t)2 * ctx->partition_size) { + 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; } @@ -96,6 +101,7 @@ static int _Program(void *context, uint32_t offset, uint32_t size, { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; uint32_t written = 0U; + int hrc = 0; if (ctx == NULL || (size != 0U && data == NULL)) { return WH_ERROR_BADARGS; @@ -107,28 +113,38 @@ static int _Program(void *context, uint32_t offset, uint32_t size, return WH_ERROR_OK; } + hal_flash_unlock(); while (written < size) { uint32_t in_sector_off = (offset + written) % WHFH5_SECTOR_SIZE; - uint32_t sector_base = (offset + written) - in_sector_off; + 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_base), + (const uint8_t *)(ctx->base + sector_offset), WHFH5_SECTOR_SIZE); memcpy(cached_sector + in_sector_off, data + written, chunk); - hal_flash_unlock(); - hal_flash_erase(ctx->base + sector_base, WHFH5_SECTOR_SIZE); - hal_flash_write(ctx->base + sector_base, cached_sector, - WHFH5_SECTOR_SIZE); - hal_flash_lock(); + 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; } - return WH_ERROR_OK; + hal_flash_lock(); + + return (hrc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; } static int _Erase(void *context, uint32_t offset, uint32_t size) diff --git a/test-app/Makefile b/test-app/Makefile index 6961a0f1db..5614cbaed9 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -5,6 +5,7 @@ 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 diff --git a/test-app/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c index 1675395a0c..d7a4cd4c58 100644 --- a/test-app/wcs/wolfhsm_test.c +++ b/test-app/wcs/wolfhsm_test.c @@ -21,6 +21,7 @@ #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" @@ -124,7 +125,8 @@ static int wolfhsm_test_aes_cached(whClientContext *client) static const uint8_t iv[16] = { 0 }; Aes aes; uint8_t ct[16]; - uint16_t keyId = WH_KEYID_ERASED; + uint16_t keyId = WH_KEYID_ERASED; + int aes_inited = 0; int rc; memset(&aes, 0, sizeof(aes)); @@ -142,6 +144,7 @@ static int wolfhsm_test_aes_cached(whClientContext *client) printf("wolfHSM AesInit failed: %d\r\n", rc); goto out; } + aes_inited = 1; rc = wh_Client_AesSetKeyId(&aes, keyId); if (rc != WH_ERROR_OK) { @@ -166,7 +169,9 @@ static int wolfhsm_test_aes_cached(whClientContext *client) printf("wolfHSM AES ok\r\n"); out: - wc_AesFree(&aes); + if (aes_inited) { + wc_AesFree(&aes); + } (void)wh_Client_KeyEvict(client, keyId); return rc; } @@ -188,11 +193,12 @@ static int wolfhsm_test_persist(whClientContext *client, int *boot_state) 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"); - keyId = WH_MAKE_KEYID(0, WCS_WOLFHSM_CLIENT_ID, 1); rc = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, 0, persist_key, (uint16_t)sizeof(persist_key), &keyId); @@ -205,13 +211,14 @@ static int wolfhsm_test_persist(whClientContext *client, int *boot_state) 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 }; + static const whTransportNscClientConfig nsc_cfg = { { 0 } }; whCommClientConfig comm_cfg; whClientConfig cfg; whClientContext client; diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index ee0b6398e4..47b03404a5 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-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 +374,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; +} From 0c7b55628b9ec5146c586ce4a133071e49934f9d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 11 May 2026 17:37:13 -0700 Subject: [PATCH 12/13] WOLFCRYPT_TZ_WOLFHSM: review fixes and unit-test coverage - options.mk: drop duplicate WOLFHSM_CLIENT_OBJS / WOLFHSM_SERVER_OBJS block; top-of-file definitions are reached before all consumers. - wolfhsm_callable.c: idempotency guard in wcs_wolfhsm_init; defensive memset of g_srv_tx_ctx; move g_flash_cfg to a stack local; clamp rsp_size to rsp_capacity before publishing *rspSz; set *rspSz = 0 on every early-validation error path; use { 0 } initializer; switch to #ifdef WOLF_CRYPTO_CB. - wolfhsm_flash_hal.c: rename _Foo callbacks to whFlashH5_Foo to avoid C-reserved leading-underscore-uppercase identifiers; constant-time compare in Verify since data may be key material; defensive wc_ForceZero(cached_sector) on entry to Program; Erase short-circuits size == 0 before the alignment check for consistency. - test-app/wcs/wolfhsm_test.c: split AesSetIV vs AesCbcEncrypt error diagnostics; KeyEvict the cached key when KeyCommit fails; use { 0 } initializer for nsc_cfg. - tools/unit-tests/Makefile: add WOLFBOOT_LIB_WOLFHSM default and external-libs fallback so unit-wolfhsm_flash_hal finds wolfhsm headers in CI. - .github/workflows/test-external-library-paths.yml: pass WOLFBOOT_LIB_WOLFHSM to the unit-test matrix entry. - unit-wolfhsm_flash_hal.c: cover Cleanup, erase-failure propagation, Read happy path, multi-sector Program, NULL context for Read/PartitionSize/Program/Erase/Verify/BlankCheck, NULL data, and WriteLock/WriteUnlock; use MAP_FIXED_NOREPLACE when available. --- .../workflows/test-external-library-paths.yml | 3 +- options.mk | 38 ----- src/wolfhsm_callable.c | 45 ++++-- src/wolfhsm_flash_hal.c | 97 +++++++----- test-app/wcs/wolfhsm_test.c | 9 +- tools/unit-tests/Makefile | 5 + tools/unit-tests/unit-wolfhsm_flash_hal.c | 145 +++++++++++++++++- 7 files changed, 247 insertions(+), 95 deletions(-) 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/options.mk b/options.mk index e05496381e..552dd6c213 100644 --- a/options.mk +++ b/options.mk @@ -1393,44 +1393,6 @@ ifeq ($(WOLFBOOT_TEST_SIM_CRYPTOCB),1) endif endif -# Shared wolfHSM client/server object lists. Both the legacy WOLFHSM_CLIENT=1 / -# WOLFHSM_SERVER=1 flags and the WOLFCRYPT_TZ_WOLFHSM=1 TZ engine reference -# these to avoid object-list duplication. -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 - # wolfHSM client options ifeq ($(WOLFHSM_CLIENT),1) WOLFCRYPT_OBJS += \ diff --git a/src/wolfhsm_callable.c b/src/wolfhsm_callable.c index 85b5804c70..71ab752351 100644 --- a/src/wolfhsm_callable.c +++ b/src/wolfhsm_callable.c @@ -50,15 +50,12 @@ 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, + /* .config is set at runtime in wcs_wolfhsm_init */ }; static whNvmCb g_nvm_flash_cb = WH_NVM_FLASH_CB; static whNvmContext g_nvm_ctx; @@ -70,7 +67,7 @@ static whNvmConfig g_nvm_cfg = { static whServerCryptoContext g_crypto_ctx; static whTransportNscServerContext g_srv_tx_ctx; -static whTransportNscServerConfig g_srv_tx_cfg = { { 0 } }; +static whTransportNscServerConfig g_srv_tx_cfg = { 0 }; static whCommServerConfig g_comm_cfg = { .transport_context = &g_srv_tx_ctx, .transport_cb = &whTransportNscServer_Cb, @@ -81,7 +78,7 @@ static whServerConfig g_server_cfg = { .comm_config = &g_comm_cfg, .nvm = &g_nvm_ctx, .crypto = &g_crypto_ctx, -#if defined WOLF_CRYPTO_CB +#ifdef WOLF_CRYPTO_CB .devId = INVALID_DEVID, #endif }; @@ -91,13 +88,22 @@ static int g_wolfhsm_ready; void wcs_wolfhsm_init(void) { - int rc; + whFlashH5Ctx flash_cfg; + int rc; + + if (g_wolfhsm_ready) { + return; + } + + memset(&g_srv_tx_ctx, 0, sizeof(g_srv_tx_ctx)); - 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; + 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; - rc = wc_InitRng(g_crypto_ctx.rng); + /* 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(); } @@ -125,16 +131,19 @@ int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, if (cmd == NULL || rsp == NULL || rspSz == NULL) { return WH_ERROR_BADARGS; } - /* volatile read forbids the compiler from re-fetching *rspSz later. */ + /* 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; } @@ -148,8 +157,16 @@ int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, rc = wh_Server_HandleRequestMessage(&g_server); if (rc == WH_ERROR_OK) { - *rspSz = (uint32_t)g_srv_tx_ctx.rsp_size; - } else { + /* 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; } diff --git a/src/wolfhsm_flash_hal.c b/src/wolfhsm_flash_hal.c index d11e730924..13caab7b90 100644 --- a/src/wolfhsm_flash_hal.c +++ b/src/wolfhsm_flash_hal.c @@ -21,15 +21,17 @@ #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. */ +/* 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 _Init(void *context, const void *config) +static int whFlashH5_Init(void *context, const void *config) { const whFlashH5Ctx *cfg = (const whFlashH5Ctx *)config; whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; @@ -50,7 +52,7 @@ static int _Init(void *context, const void *config) return WH_ERROR_OK; } -static int _Cleanup(void *context) +static int whFlashH5_Cleanup(void *context) { if (context == NULL) { return WH_ERROR_BADARGS; @@ -58,13 +60,13 @@ static int _Cleanup(void *context) return WH_ERROR_OK; } -static uint32_t _PartitionSize(void *context) +static uint32_t whFlashH5_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) +static int whFlashH5_WriteLock(void *context, uint32_t offset, uint32_t size) { (void)context; (void)offset; @@ -72,7 +74,7 @@ static int _WriteLock(void *context, uint32_t offset, uint32_t size) return WH_ERROR_OK; } -static int _WriteUnlock(void *context, uint32_t offset, uint32_t size) +static int whFlashH5_WriteUnlock(void *context, uint32_t offset, uint32_t size) { (void)context; (void)offset; @@ -80,7 +82,8 @@ static int _WriteUnlock(void *context, uint32_t offset, uint32_t size) return WH_ERROR_OK; } -static int _Read(void *context, uint32_t offset, uint32_t size, uint8_t *data) +static int whFlashH5_Read(void *context, uint32_t offset, uint32_t size, + uint8_t *data) { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; @@ -96,8 +99,8 @@ static int _Read(void *context, uint32_t offset, uint32_t size, uint8_t *data) return WH_ERROR_OK; } -static int _Program(void *context, uint32_t offset, uint32_t size, - const uint8_t *data) +static int whFlashH5_Program(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; uint32_t written = 0U; @@ -113,6 +116,9 @@ static int _Program(void *context, uint32_t offset, uint32_t size, 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; @@ -147,7 +153,7 @@ static int _Program(void *context, uint32_t offset, uint32_t size, return (hrc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; } -static int _Erase(void *context, uint32_t offset, uint32_t size) +static int whFlashH5_Erase(void *context, uint32_t offset, uint32_t size) { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; int rc; @@ -158,24 +164,40 @@ static int _Erase(void *context, uint32_t offset, uint32_t size) 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; } - if (size == 0U) { - return WH_ERROR_OK; - } + /* 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(); - rc = hal_flash_erase(ctx->base + offset, (int)size); + { + 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 _Verify(void *context, uint32_t offset, uint32_t size, - const uint8_t *data) +static int whFlashH5_Verify(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) { - whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + 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; @@ -183,14 +205,15 @@ static int _Verify(void *context, uint32_t offset, uint32_t size, 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; + /* 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 WH_ERROR_OK; + return (acc == 0U) ? WH_ERROR_OK : WH_ERROR_NOTVERIFIED; } -static int _BlankCheck(void *context, uint32_t offset, uint32_t size) +static int whFlashH5_BlankCheck(void *context, uint32_t offset, uint32_t size) { whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; const uint8_t *p; @@ -212,16 +235,16 @@ static int _BlankCheck(void *context, uint32_t offset, uint32_t size) } const whFlashCb whFlashH5_Cb = { - .Init = _Init, - .Cleanup = _Cleanup, - .PartitionSize = _PartitionSize, - .WriteLock = _WriteLock, - .WriteUnlock = _WriteUnlock, - .Read = _Read, - .Program = _Program, - .Erase = _Erase, - .Verify = _Verify, - .BlankCheck = _BlankCheck, + .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/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c index d7a4cd4c58..22c7d0fee5 100644 --- a/test-app/wcs/wolfhsm_test.c +++ b/test-app/wcs/wolfhsm_test.c @@ -153,9 +153,11 @@ static int wolfhsm_test_aes_cached(whClientContext *client) } rc = wc_AesSetIV(&aes, iv); - if (rc == 0) { - rc = wc_AesCbcEncrypt(&aes, ct, pt, (word32)sizeof(pt)); + 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; @@ -209,6 +211,7 @@ static int wolfhsm_test_persist(whClientContext *client, int *boot_state) 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); @@ -218,7 +221,7 @@ static int wolfhsm_test_persist(whClientContext *client, int *boot_state) int cmd_wolfhsm_test(const char *args) { - static const whTransportNscClientConfig nsc_cfg = { { 0 } }; + static const whTransportNscClientConfig nsc_cfg = { 0 }; whCommClientConfig comm_cfg; whClientConfig cfg; whClientContext client; diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 47b03404a5..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 diff --git a/tools/unit-tests/unit-wolfhsm_flash_hal.c b/tools/unit-tests/unit-wolfhsm_flash_hal.c index e3fa87ee80..6f7b159881 100644 --- a/tools/unit-tests/unit-wolfhsm_flash_hal.c +++ b/tools/unit-tests/unit-wolfhsm_flash_hal.c @@ -60,9 +60,17 @@ int hal_flash_write(haladdr_t addr, const uint8_t *data, int len) 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, - MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + 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; @@ -280,6 +288,132 @@ START_TEST(test_blank_check) } 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"); @@ -295,6 +429,13 @@ Suite *wolfboot_suite(void) 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; } From 2c6827da3245643b0b032b28a7e835ee34e44e3c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 11 May 2026 17:55:42 -0700 Subject: [PATCH 13/13] lib/wolfHSM: pin to wolfHSM PR 348 head for NSC transport --- lib/wolfHSM | 2 +- options.mk | 6 +++--- test-app/Makefile | 6 +++--- test-app/wcs/wolfhsm_stub.c | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) 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 552dd6c213..a912bba163 100644 --- a/options.mk +++ b/options.mk @@ -1150,10 +1150,10 @@ ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) 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_PORT_ARMV8M_TZ_NSC CFLAGS+=-DWOLFHSM_CFG_NO_SYS_TIME CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" - CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz" + 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) @@ -1182,7 +1182,7 @@ ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) endif endif WOLFHSM_OBJS+=$(WOLFHSM_SERVER_OBJS) - WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/stmicro/stm32-tz/wh_transport_nsc.o + WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-tz/wh_transport_nsc.o STACK_USAGE=20000 endif diff --git a/test-app/Makefile b/test-app/Makefile index 5614cbaed9..a46ed929bc 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -368,18 +368,18 @@ ifeq ($(TZEN),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_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/stmicro/stm32-tz" + 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/stmicro/stm32-tz/wh_transport_nsc.o + 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)) diff --git a/test-app/wcs/wolfhsm_stub.c b/test-app/wcs/wolfhsm_stub.c index bff167f0c4..8d8343f274 100644 --- a/test-app/wcs/wolfhsm_stub.c +++ b/test-app/wcs/wolfhsm_stub.c @@ -8,7 +8,7 @@ /* * 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 + * port file (port/armv8m-tz/wh_transport_nsc.c); this stub just * provides the singleton context it operates on. */