From 74de6b745f038455c6ccf9c22689373c097543d0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 30 Apr 2026 14:22:00 -0700 Subject: [PATCH 01/14] 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 52146818d1..eddf882208 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 ($(WOLFBOOT_DICE_HW),1) @@ -1103,6 +1115,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) @@ -1310,6 +1338,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 += \ @@ -1326,19 +1392,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 @@ -1384,26 +1438,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 72e51085be..e8dc01f8ac 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -111,6 +111,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 @@ -369,6 +374,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 5870eeece2..53a2ea8c30 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 d3c44400ea3139677e6411bbb521767848505b91 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 30 Apr 2026 16:34:33 -0700 Subject: [PATCH 02/14] 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 c510968aae..f38fa81974 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -768,12 +768,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 eddf882208..f529ff4a6d 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 @@ -1120,7 +1159,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) @@ -1128,6 +1170,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 e8dc01f8ac..9370e7cefc 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 @@ -378,7 +381,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)) @@ -1037,6 +1051,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)" @@ -1134,9 +1149,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 53a2ea8c30..b09accff89 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 a29f640bc943daef275c825fb1507d8931c3e91f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 10:14:30 -0700 Subject: [PATCH 03/14] 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 9370e7cefc..a70ee0e172 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -383,6 +383,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 796a12c0bfd58981dbc9fd1abeb3948cda18d498 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:07:16 -0700 Subject: [PATCH 04/14] 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 64c39ea3d50ddcb448008084677fe6905fda0668 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:24:48 -0700 Subject: [PATCH 05/14] 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 7a2f4aa7cbecca4e865d6a3095f26111d4fe1934 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:43:01 -0700 Subject: [PATCH 06/14] 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 f529ff4a6d..d8e70cf123 100644 --- a/options.mk +++ b/options.mk @@ -1172,6 +1172,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 @@ -1190,7 +1191,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 42431e5607c36aafeacf40717752f5132a5cc5ed Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 11:54:53 -0700 Subject: [PATCH 07/14] 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 19e29c6ab269bc20f5bc60bd46d97fb4fecdb274 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 12:24:53 -0700 Subject: [PATCH 08/14] 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 028a186b2ffd0d0958be0aab8c1c18c952942ef5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 12:35:46 -0700 Subject: [PATCH 09/14] 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 6bdc040cac..b71bfc7d7b 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. @@ -239,3 +240,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 746dd3a05495adbf5ed2d576e6893f7cb1ab1d50 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 13:10:21 -0700 Subject: [PATCH 10/14] 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 6fa547128ed6449fb452fe029055ca726d4e958d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 1 May 2026 16:05:36 -0700 Subject: [PATCH 11/14] 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 a70ee0e172..f007030bc0 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 a09baeb6cae7459b9280695ccd86f1fdbc89b1b2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 11 May 2026 17:37:13 -0700 Subject: [PATCH 12/14] 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 d8e70cf123..6e24aee744 100644 --- a/options.mk +++ b/options.mk @@ -1402,44 +1402,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 8afb235a9bb5fefdc36ae099ea5d92bdc92b57cd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 11 May 2026 17:55:42 -0700 Subject: [PATCH 13/14] 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 80ee2d59ec..92edc94c83 160000 --- a/lib/wolfHSM +++ b/lib/wolfHSM @@ -1 +1 @@ -Subproject commit 80ee2d59ecbfd46bd4b7b6ab8ed742ca5dac3e0f +Subproject commit 92edc94c832c25ad31538b0593b00abd3df91172 diff --git a/options.mk b/options.mk index 6e24aee744..a19efb8b33 100644 --- a/options.mk +++ b/options.mk @@ -1159,10 +1159,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) @@ -1191,7 +1191,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 f007030bc0..9af7a720bd 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -382,18 +382,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. */ From 1f1a3ac5313018f7728cd9372e999f31c10a27c4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 25 May 2026 16:00:19 -0700 Subject: [PATCH 14/14] port/stmicro/stm32h5-tz-wolfhsm: convenience wrapper + CI to whTest_ClientConfig --- .../workflows/trustzone-emulator-tests.yml | 54 +- include/user_settings.h | 56 +- lib/wolfHSM | 2 +- lib/wolfssl | 2 +- options.mk | 11 +- port/stmicro/stm32h5-tz-wolfhsm/Makefile | 118 ++++ port/stmicro/stm32h5-tz-wolfhsm/README.md | 575 ++++++++++++++++++ .../stm32h5-tz-wolfhsm/demo-app/main.c | 134 ++++ port/stmicro/stm32h5-tz-wolfhsm/load.sh | 83 +++ .../stm32h5-tz-wolfhsm/out/manifest.env | 3 + test-app/Makefile | 9 + test-app/wcs/wolfhsm_stub.c | 23 + test-app/wcs/wolfhsm_test.c | 257 +------- 13 files changed, 1055 insertions(+), 272 deletions(-) create mode 100644 port/stmicro/stm32h5-tz-wolfhsm/Makefile create mode 100644 port/stmicro/stm32h5-tz-wolfhsm/README.md create mode 100644 port/stmicro/stm32h5-tz-wolfhsm/demo-app/main.c create mode 100755 port/stmicro/stm32h5-tz-wolfhsm/load.sh create mode 100644 port/stmicro/stm32h5-tz-wolfhsm/out/manifest.env diff --git a/.github/workflows/trustzone-emulator-tests.yml b/.github/workflows/trustzone-emulator-tests.yml index 469909f18c..e446646a7b 100644 --- a/.github/workflows/trustzone-emulator-tests.yml +++ b/.github/workflows/trustzone-emulator-tests.yml @@ -122,48 +122,40 @@ jobs: - name: Clean and build test with wolfHSM (stm32h5) run: | make clean distclean - cp config/examples/stm32h5-tz-wolfhsm.config .config + cd port/stmicro/stm32h5-tz-wolfhsm make + test -f out/wolfboot.bin + test -f out/image_v1_signed.bin + test -f out/manifest.env - 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) + - name: Run wolfHSM whTest_ClientConfig (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 + --persist --uart-stdout --timeout 600 --expect-bkpt 0x7d \ + --quit-on-faults \ + | tee /tmp/m33mu-wolfhsm.log + + - name: Verify wolfHSM whTest_ClientConfig (stm32h5) + run: | + # Final success marker - the test itself reports PASSED. + grep -q "wolfHSM whTest_ClientConfig PASSED" /tmp/m33mu-wolfhsm.log + # Spot-check that the major test phases each landed. + grep -q "RNG DEVID=0x5748534D SUCCESS" /tmp/m33mu-wolfhsm.log + grep -q "AES GCM DEVID=0x5748534D SUCCESS" /tmp/m33mu-wolfhsm.log + grep -q "RSA SUCCESS" /tmp/m33mu-wolfhsm.log + grep -q "ECC ephemeral ECDH SUCCESS" /tmp/m33mu-wolfhsm.log + grep -q "SHA256 DEVID=0x5748534D SUCCESS" /tmp/m33mu-wolfhsm.log + grep -q "HKDF SUCCESS" /tmp/m33mu-wolfhsm.log + # App reached the success BKPT and m33mu trapped on it. + grep -q "\\[BKPT\\] imm=0x7d" /tmp/m33mu-wolfhsm.log + grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-wolfhsm.log - name: Clean and build test with DICE attestation + OTP (stm32h5) run: | diff --git a/include/user_settings.h b/include/user_settings.h index f38fa81974..e52a575661 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -516,6 +516,10 @@ extern int tolower(int c); defined(WOLFBOOT_SIGN_ML_DSA)) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) #define WC_NO_RNG + /* wolfssl rejects WC_NO_RNG with the default blinding macros. + * wolfBoot is verify-only, no blinding is meaningful with a + * disabled RNG. */ + #define WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS #endif #define WC_NO_HASHDRBG #define NO_AES_CBC @@ -569,6 +573,9 @@ extern int tolower(int c); defined(WOLFBOOT_SIGN_ML_DSA)) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) # define WC_NO_RNG + /* wolfssl rejects WC_NO_RNG with the default blinding macros. wolfBoot + * is verify-only, no blinding is meaningful with a disabled RNG. */ +# define WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS # endif # define WC_NO_HASHDRBG # define NO_DEV_RANDOM @@ -774,8 +781,53 @@ extern int tolower(int c); # undef HAVE_ANONYMOUS_INLINE_AGGREGATES # define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 # define WOLFSSL_KEY_GEN -#endif /* WOLFBOOT_ENABLE_WOLFHSM_CLIENT || WOLFBOOT_ENABLE_WOLFHSM_SERVER || - WOLFCRYPT_TZ_WOLFHSM */ +#endif + +/* Crypto algorithms exercised by the wolfHSM client test suite over + * the NSC bridge. These are SECURE-SIDE only - the wolfHSM server + * needs AES/HKDF/SHA384/512 to handle the test requests. Gated out + * of the wolfHSM client build (which has NO_AES) and out of host + * unit tests (UNIT_TEST), neither of which want AES dragged in. */ +#if (defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) || \ + defined(WOLFCRYPT_TZ_WOLFHSM)) && !defined(UNIT_TEST) + /* Earlier guards in this file set NO_AES and NO_HMAC for the + * verify-only bootloader path. The wolfHSM server needs both AES + * and HMAC (HKDF builds on HMAC), so drop them here. */ +# undef NO_AES +# undef NO_HMAC +# ifndef WOLFSSL_AES_DIRECT +# define WOLFSSL_AES_DIRECT +# endif +# ifndef HAVE_HKDF +# define HAVE_HKDF +# endif +# ifndef WOLFSSL_AES_COUNTER +# define WOLFSSL_AES_COUNTER +# endif +# ifndef HAVE_AESCTR +# define HAVE_AESCTR +# endif +# ifndef WOLFSSL_AES_GCM +# define WOLFSSL_AES_GCM +# endif +# ifndef HAVE_AESGCM +# define HAVE_AESGCM +# endif +# ifndef GCM_TABLE_4BIT +# define GCM_TABLE_4BIT +# endif + /* Match NS-side WC_MAX_DIGEST_SIZE. NS test-app/wcs/user_settings.h + * enables WOLFSSL_SHA3 which sets WC_MAX_DIGEST_SIZE = 64. Without + * SHA384/SHA512 on the secure side, WC_MAX_DIGEST_SIZE caps at + * SHA256's 32 and wc_ecc_sign_hash (ecc.c:7281) rejects legitimately + * oversized hashes (e.g. ECDSA truncation tests) with BAD_LENGTH_E. */ +# ifndef WOLFSSL_SHA384 +# define WOLFSSL_SHA384 +# endif +# ifndef WOLFSSL_SHA512 +# define WOLFSSL_SHA512 +# endif +#endif /* WOLFBOOT_ENABLE_WOLFHSM_SERVER || WOLFCRYPT_TZ_WOLFHSM, !UNIT_TEST */ #if defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) && \ defined(WOLFBOOT_CERT_CHAIN_VERIFY) diff --git a/lib/wolfHSM b/lib/wolfHSM index 92edc94c83..e878a2bad2 160000 --- a/lib/wolfHSM +++ b/lib/wolfHSM @@ -1 +1 @@ -Subproject commit 92edc94c832c25ad31538b0593b00abd3df91172 +Subproject commit e878a2bad2066c19582c2967a6df593e466ce2e0 diff --git a/lib/wolfssl b/lib/wolfssl index be67bf88f7..887f242ee8 160000 --- a/lib/wolfssl +++ b/lib/wolfssl @@ -1 +1 @@ -Subproject commit be67bf88f76409022059cbe01c5571ae493d285f +Subproject commit 887f242ee8570f7d8403e002c5b2b88929b86544 diff --git a/options.mk b/options.mk index a19efb8b33..618022cd8f 100644 --- a/options.mk +++ b/options.mk @@ -1456,7 +1456,16 @@ ifeq ($(WOLFHSM_SERVER),1) WOLFCRYPT_OBJS += \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/cryptocb.o \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/coding.o \ - $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/random.o + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/random.o \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/hmac.o \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/aes.o \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_encrypt.o + # SHA-384/512 are used by the wolfHSM crypto handlers (HKDF, larger + # ECDSA hash sizes, etc.). Always link sha512.o except when ED25519 + # is the signature algorithm, which already pulls it in. + ifneq ($(SIGN),ED25519) + WOLFCRYPT_OBJS += $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha512.o + endif ifeq ($(SIGN),ML_DSA) WOLFCRYPT_OBJS += $(MATH_OBJS) diff --git a/port/stmicro/stm32h5-tz-wolfhsm/Makefile b/port/stmicro/stm32h5-tz-wolfhsm/Makefile new file mode 100644 index 0000000000..8477f12646 --- /dev/null +++ b/port/stmicro/stm32h5-tz-wolfhsm/Makefile @@ -0,0 +1,118 @@ +# Makefile - top-level wrapper for the wolfHSM TrustZone demo on STM32H5 +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfBoot. +# +# wolfBoot is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfBoot is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# This Makefile is a convenience wrapper around the top-level wolfBoot +# build. It stages the stm32h5-tz-wolfhsm config, builds the secure +# wolfBoot image (with the wolfHSM server linked in) and the non-secure +# test application (which connects to the server via the ARMv8-M NSC +# bridge and runs whTest_ClientConfig), and stages both binaries here +# for flashing. +# +# Quick start: +# make # build wolfboot.bin + test app, stage in ./out/ +# ./load.sh # flash to a NUCLEO-H563ZI and open a serial console +# make emu # run the m33mu emulator over the built binaries +# make clean # drop staged artifacts (keeps the wolfBoot tree) +# make distclean # also wipe wolfBoot build state + +PORT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +WOLFBOOT_ROOT := $(abspath $(PORT_DIR)/../../..) +CONFIG_EXAMPLE := $(WOLFBOOT_ROOT)/config/examples/stm32h5-tz-wolfhsm.config +CONFIG_TARGET := $(WOLFBOOT_ROOT)/.config +OUT_DIR := $(PORT_DIR)/out + +# Default to the lib/wolfHSM submodule (matches wolfBoot's own +# Makefile). Override with `make WOLFBOOT_LIB_WOLFHSM=...` to point +# at a different wolfHSM checkout. +WOLFBOOT_LIB_WOLFHSM ?= $(WOLFBOOT_ROOT)/lib/wolfHSM + +WOLFBOOT_BIN := $(WOLFBOOT_ROOT)/wolfboot.bin +TEST_APP_BIN := $(WOLFBOOT_ROOT)/test-app/image_v1_signed.bin + +# Address the test app must be flashed to. Read from the staged .config +# so it stays in sync with what wolfBoot itself was built against. +BOOT_ADDR := 0x08060000 + +# Forward extra options to the wolfBoot top-level make. Example: +# make WOLFBOOT_MAKE_FLAGS='V=1' +WOLFBOOT_MAKE_FLAGS ?= + +.PHONY: all build stage clean distclean emu flash help + +all: build stage + +# Stage the config and run the wolfBoot top-level build. wolfBoot itself +# emits wolfboot.bin (secure image) and test-app/image_v1_signed.bin +# (the non-secure test application). With WOLFCRYPT_TZ_WOLFHSM=1 in the +# staged config, the non-secure app calls cmd_wolfhsm_test(), which +# initialises the wolfHSM client over the NSC bridge and runs the +# wolfHSM client test suite against the in-secure-world server. +# +# We `cd` into WOLFBOOT_ROOT rather than using `$(MAKE) -C ...` because +# the wolfBoot sign step shells out with `./tools/keytools/sign`, which +# resolves against the original process cwd. From a sub-directory the +# relative path fails; cd-ing keeps the resolution correct. +build: stage-config + @echo "==> wolfBoot build (stm32h5-tz-wolfhsm)" + @echo "==> WOLFBOOT_LIB_WOLFHSM=$(WOLFBOOT_LIB_WOLFHSM)" + cd $(WOLFBOOT_ROOT) && $(MAKE) wolfboot.bin WOLFBOOT_LIB_WOLFHSM=$(WOLFBOOT_LIB_WOLFHSM) $(WOLFBOOT_MAKE_FLAGS) + cd $(WOLFBOOT_ROOT) && $(MAKE) test-app/image_v1_signed.bin WOLFBOOT_LIB_WOLFHSM=$(WOLFBOOT_LIB_WOLFHSM) $(WOLFBOOT_MAKE_FLAGS) + +# Force-overwrite .config every build so a stale config from a +# previous CI step (PKCS11, fwTPM, DICE) cannot bleed through. +.PHONY: stage-config +stage-config: + @echo "==> Staging config: $(CONFIG_EXAMPLE) -> $(CONFIG_TARGET)" + cp $(CONFIG_EXAMPLE) $(CONFIG_TARGET) + +# Copy the produced binaries into ./out so this directory is the single +# place the user has to look. Also drop the boot address into a small +# manifest so load.sh and CI scripts do not have to re-parse .config. +stage: build + @mkdir -p $(OUT_DIR) + cp $(WOLFBOOT_BIN) $(OUT_DIR)/wolfboot.bin + cp $(TEST_APP_BIN) $(OUT_DIR)/image_v1_signed.bin + @echo "BOOT_ADDR=$(BOOT_ADDR)" > $(OUT_DIR)/manifest.env + @echo "WOLFBOOT_BIN=$(OUT_DIR)/wolfboot.bin" >> $(OUT_DIR)/manifest.env + @echo "TEST_APP_BIN=$(OUT_DIR)/image_v1_signed.bin" >> $(OUT_DIR)/manifest.env + @echo "==> Staged in $(OUT_DIR):" + @ls -l $(OUT_DIR) + +# Convenience: invoke load.sh from anywhere. +flash: stage + $(PORT_DIR)/load.sh + +# Run the wolfBoot m33mu emulator harness over the produced binaries. +# This uses wolfBoot's own emulator script so the test path is identical +# to what runs in CI. +emu: stage + cd $(WOLFBOOT_ROOT)/test-app/emu-test-apps && \ + TARGET=stm32h5 ./test.sh + +clean: + rm -rf $(OUT_DIR) + +distclean: clean + cd $(WOLFBOOT_ROOT) && $(MAKE) clean distclean + rm -f $(CONFIG_TARGET) + +help: + @echo "Targets:" + @echo " make Build wolfboot.bin + signed test app, stage in ./out" + @echo " make flash Build (if needed) and flash via load.sh" + @echo " make emu Build (if needed) and run wolfBoot m33mu harness" + @echo " make clean Drop ./out" + @echo " make distclean Also clean the wolfBoot tree and drop .config" diff --git a/port/stmicro/stm32h5-tz-wolfhsm/README.md b/port/stmicro/stm32h5-tz-wolfhsm/README.md new file mode 100644 index 0000000000..0ffd99ae6e --- /dev/null +++ b/port/stmicro/stm32h5-tz-wolfhsm/README.md @@ -0,0 +1,575 @@ +# wolfHSM TrustZone Demo for STM32H5 + +This directory is the canonical entry point for building, running, and +flashing the first open-source wolfHSM TrustZone port. The port itself +lives in wolfBoot; the generic ARMv8-M NSC bridge transport that the +port consumes lives in wolfHSM at `port/armv8m-tz/`. + +The demo runs a wolfHSM client in the non-secure world and the wolfHSM +server in the secure world, both on the same Cortex-M33 in an +STM32H563. There is no second core. The client and server context +switch through ARMv8-M TrustZone using a single +`cmse_nonsecure_entry` veneer. + +The reference layout is modeled on the wolfHSM SR6 dual-core port in +the wolfHSM-private repository. The folder shape (top-level Makefile, +`load.sh`, `demo-app/`, README) matches that proven pattern. The +contents are simplified for the single-app TrustZone model: + +- one MCU, not two +- one application image, not separate client and server applications +- the security boundary is the TrustZone split inside the same core, + not a shared-memory channel between two cores + + +## Overview + +- **Target MCU:** STM32H563ZI (Arm Cortex-M33 with TrustZone) +- **Target board:** NUCLEO-H563ZI +- **Secure world:** wolfBoot + linked-in wolfHSM server, including + flash-backed NVM (key cache, keystore). Source: + `src/wolfhsm_callable.c`, `src/wolfhsm_flash_hal.c`. +- **Non-secure world:** test application that initialises a wolfHSM + client over the ARMv8-M NSC transport and exercises the server. + Source: `test-app/app_stm32h5.c`, `test-app/wcs/wolfhsm_test.c`, + `test-app/wcs/wolfhsm_stub.c`. +- **NSC veneer:** `wcs_wolfhsm_transmit(cmd, cmdSz, rsp, *rspSz)`. The + wolfHSM client invokes this through the transport callbacks in + `lib/wolfHSM/port/armv8m-tz/wh_transport_nsc.c`. The + `cmse_nonsecure_entry`-tagged implementation lives in + `src/wolfhsm_callable.c`. +- **NVM backend:** persistent flash storage carved out of the H5 + internal flash via the `whFlashCb` adapter in + `src/wolfhsm_flash_hal.c`. + + +## Running wolfBoot + wolfHSM on m33mu (Cortex-M33) + +This is the section to read if you just want to copy-paste commands +and watch the demo run. It assumes a fresh Linux dev box. + +### One-time setup + +```bash +# 1. Toolchain +sudo apt install -y gcc-arm-none-eabi git make cmake build-essential + +# 2. Rust (m33mu's Rust simulator plugins need cargo >= 1.85 for edition2024) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain stable --profile minimal +. "$HOME/.cargo/env" + +# 3. Build m33mu (the Cortex-M33 emulator) and put it on PATH +cd ~ +git clone https://github.com/danielinux/m33mu +cd m33mu +cmake -S . -B build -DM33MU_ENABLE_WOLFSSL=OFF +cmake --build build --target m33mu -j +mkdir -p ~/.local/bin +ln -sf "$PWD/build/m33mu" ~/.local/bin/m33mu + +# 4. Verify m33mu works +env -u LD_LIBRARY_PATH m33mu --cpu list +# expected: stm32h563 / stm32h533 / stm32u585 / stm32l552 / ... +``` + +If `env -u LD_LIBRARY_PATH m33mu ...` is awkward, add `unset +LD_LIBRARY_PATH` to your shell rc — it works around Warp's bundled +older libtinfo if you use Warp. + +### Build the demo and run it + +```bash +# 5. From the wolfBoot checkout containing this PR +cd path/to/wolfBoot/port/stmicro/stm32h5-tz-wolfhsm +make + +# 6. Stage a writable copy of the binaries (m33mu --persist rewrites +# them in-place, so do not run it against the pristine outputs) +mkdir -p /tmp/m33mu-wolfhsm-persist +cp out/wolfboot.bin out/image_v1_signed.bin /tmp/m33mu-wolfhsm-persist/ +cd /tmp/m33mu-wolfhsm-persist + +# 7. Run wolfBoot booting wolfHSM running on m33mu, with live UART +# output on your terminal +env -u LD_LIBRARY_PATH m33mu \ + wolfboot.bin image_v1_signed.bin:0x60000 \ + --persist --uart-stdout --timeout 600 --expect-bkpt 0x7d +``` + +That last command is the live run. You will see, in order: +wolfBoot's secure-image boot banner; the bootloader's keystore +hexdump; the NS-side wolfHSM demo opening the NSC bridge +(`wolfHSM CommInit ok (client=1 server=56)`); the +`whTest_ClientServerClientConfig` suite (echo, NVM add/list/destroy, +NVM flags enforcement); then the `whTest_CryptoClientConfig` suite +(RNG, key cache + commit + cross-cache eviction, non-exportable +keystore, key-usage policy enforcement for AES/ECDSA/ECDH/HKDF, +AES-CTR/CBC/GCM, RSA, ECC ephemeral sign/verify and ECDH); then a +final `[BKPT] imm=0x7d` / `[EXPECT BKPT] Success` and m33mu exits 0. + +To watch the run in a separate terminal as it streams, run from one +terminal: + +```bash +env -u LD_LIBRARY_PATH m33mu \ + wolfboot.bin image_v1_signed.bin:0x60000 \ + --persist --uart-stdout --timeout 600 --expect-bkpt 0x7d \ + 2>&1 | tee /tmp/m33mu-wolfhsm.log +``` + +And from another: + +```bash +tail -f /tmp/m33mu-wolfhsm.log +``` + + +## Prerequisites + +1. **Toolchain:** `arm-none-eabi-gcc` (any recent GNU Arm Embedded + toolchain with Cortex-M33 support). Verify with + `arm-none-eabi-gcc --version`. +2. **wolfBoot keytools:** these build automatically as part of the + normal `make` flow; no separate install step. +3. **STM32CubeProgrammer / `STM32_Programmer_CLI`** for flashing the + NUCLEO-H563ZI. Available at https://www.st.com/en/development-tools/stm32cubeprog.html +4. **Serial monitor:** `picocom` (preferred) or `screen`, for reading + UART output from the board. +5. **Emulator (optional, for CI parity):** `m33mu`, the wolfBoot + Cortex-M33 emulator. Two ways to get it: + - **Build from source** (preferred for ongoing local dev): + ```bash + git clone https://github.com/danielinux/m33mu ~/m33mu + cd ~/m33mu + cmake -S . -B build + cmake --build build --target m33mu + ln -sf "$PWD/build/m33mu" ~/.local/bin/m33mu + ``` + The build picks up `cargo` to compile its Rust-based + secure-element simulators (ATECC608A, SE050, STSAFE-A120, + TROPIC01). Those simulators are *not* used by this wolfHSM + demo, but the build needs `cargo >= 1.85` (for `edition2024`) + to resolve a few `mm_se050_*` symbols `main.c` references + unconditionally. Install with `rustup` if your distro ships an + older toolchain. + - **Use the CI container** (no build required): + `ghcr.io/wolfssl/wolfboot-ci-m33mu:latest`. See the bottom of + the **Running on the m33mu emulator** section below. +6. **Submodules:** wolfBoot consumes wolfHSM as a submodule at + `lib/wolfHSM`. From the wolfBoot checkout: + ```bash + git submodule update --init --recursive + ``` + + +## Quick Start + +From the wolfBoot checkout: + +```bash +cd port/stmicro/stm32h5-tz-wolfhsm +make # build wolfboot.bin + signed test app, stage in ./out +./load.sh # flash to NUCLEO-H563ZI and open a serial console +``` + +That is the entire workflow. The Makefile orchestrates everything +needed (config staging, secure-image build, signed test-app build, +binary staging). `load.sh` flashes both binaries to a real board and +drops you into a serial monitor so you can watch the test run. + +To run inside the wolfBoot emulator instead of on hardware: + +```bash +make emu # build (if needed) and run wolfBoot's m33mu harness +``` + + +## Make targets + +| Target | Purpose | +|-----------------|-------------------------------------------------------------------------| +| `make` | Build wolfboot + signed test app, stage in `./out/` (default). | +| `make build` | Same as the above but without re-staging if outputs are already there. | +| `make stage` | Copy fresh `wolfboot.bin` and `image_v1_signed.bin` into `./out/`. | +| `make flash` | Build (if needed) and invoke `./load.sh`. | +| `make emu` | Build (if needed) and run wolfBoot's m33mu harness for `TARGET=stm32h563`. | +| `make clean` | Drop staged artifacts in `./out/`. | +| `make distclean`| Also clean the wolfBoot tree and remove the staged `.config`. | +| `make help` | Print this list. | + + +## What the build produces + +After `make`: + +``` +out/ + wolfboot.bin # ~383 KB. Secure-world wolfBoot + wolfHSM server. + image_v1_signed.bin # ~200 KB. Non-secure-world test app, signed. + manifest.env # WOLFBOOT_BIN, TEST_APP_BIN, BOOT_ADDR for load.sh. +``` + +Flash mapping (from `config/examples/stm32h5-tz-wolfhsm.config`): + +| Address | Region | Notes | +|--------------|---------------------------------------|---------------------------------| +| `0x08000000` | wolfBoot secure image | `wolfboot.bin` goes here. | +| `0x08060000` | wolfBoot non-secure test-app slot | `image_v1_signed.bin` goes here.| +| `0x0C040000` | wolfHSM key vault | 112 KB (`WOLFBOOT_KEYVAULT_*`). | +| `0x0C05C000` | NSC veneer region | 16 KB. | +| `0x0C100000` | Update partition | | +| `0x0C1A0000` | Swap partition | | + + +## Expected serial output + +A successful first boot prints something close to the following on +USART3 (the NUCLEO-H563ZI's onboard ST-LINK V3E VCP, exposed at +`/dev/ttyACM0` on Linux): + +``` +======================== +wolfBoot - STM32H5 (TrustZone) +======================== +wolfHSM CommInit ok (client=1 server=0) +wolfHSM RNG ok: 7f 91 22 e5 ... +wolfHSM SHA256 ok +wolfHSM AES ok +wolfHSM first boot path, committing key to NVM +wolfHSM NSC tests passed +``` + +The application then halts via `bkpt #0x7d` (first-boot pass). On a +second boot from the same flash state, the persisted key is restored +instead of created and the application halts via `bkpt #0x7f` +(second-boot pass). A failure halts via `bkpt #0x7e`. + +The strings above are also the grep targets the CI verifier uses; see +the **Continuous Integration** section. + + +## Build details + +Under the hood, `make` runs: + +```bash +cp ../../../config/examples/stm32h5-tz-wolfhsm.config ../../../.config +cd ../../.. +make wolfboot.bin # secure image +make test-app/image_v1_signed.bin # non-secure image, signed +``` + +Then it copies the two binaries into `./out/` and writes a +`manifest.env` describing the boot address. + +The build itself is wolfBoot's normal flow with `WOLFCRYPT_TZ=1` and +`WOLFCRYPT_TZ_WOLFHSM=1` set by the staged config. The relevant +ingredients are: + +- `hal/stm32h5.c` plus `hal/stm32_tz.c` for the H5 HAL and TrustZone + setup. +- `src/wolfhsm_callable.c` for the `cmse_nonsecure_entry` veneer + (`wcs_wolfhsm_transmit`) and the secure-side server bring-up. +- `src/wolfhsm_flash_hal.c` for the `whFlashCb` adapter that backs + wolfHSM NVM onto STM32H5 internal flash. +- `lib/wolfHSM/port/armv8m-tz/wh_transport_nsc.{c,h}` for the + generic ARMv8-M NSC bridge transport (linked into both the secure + and the non-secure binaries). +- `test-app/app_stm32h5.c` for the non-secure entry point; it calls + `cmd_wolfhsm_test()` from `test-app/wcs/wolfhsm_test.c`. + + +## Flashing to NUCLEO-H563ZI + +```bash +./load.sh +``` + +By default `load.sh` does the following: + +1. Sanity-checks the staged binaries (`out/wolfboot.bin`, + `out/image_v1_signed.bin`) and the manifest. +2. Mass-erases the chip with `STM32_Programmer_CLI`, then programs + `wolfboot.bin` at `0x08000000`. Mass-erase is important because + the same flash range is shared with other configs (PKCS11, PSA, + fwTPM); a stale partition can mask wolfHSM-side failures. +3. Programs `image_v1_signed.bin` at `0x08060000`. +4. Issues a hardware reset. +5. Opens `picocom` on `/dev/ttyACM0` at 115200-8N1, falling back to + `screen` if `picocom` is not installed. + +Override knobs (all via environment variables): + +| Variable | Default | Purpose | +|-------------------------|----------------------------------|-------------------------------------------| +| `STM32_PROGRAMMER_CLI` | `STM32_Programmer_CLI` on PATH | Path to STM32CubeProgrammer CLI. | +| `SERIAL_PORT` | `/dev/ttyACM0` | NUCLEO VCP. On macOS, `/dev/tty.usbmodem*`.| +| `SERIAL_BAUD` | `115200` | USART3 baud. | +| `OPEN_SERIAL` | `1` | Set `0` to skip the serial monitor. | + +Example: flash without opening a terminal afterwards: + +```bash +OPEN_SERIAL=0 ./load.sh +``` + + +## Running on the m33mu emulator + +`m33mu` is the Cortex-M33 emulator wolfBoot CI uses. It needs to be +on `PATH`. The wolfBoot CI image +`ghcr.io/wolfssl/wolfboot-ci-m33mu:latest` is what the CI runner +itself is built from; inside that image `m33mu` is a normal native +binary. On a developer machine without m33mu installed, you can drop +into a one-shot container to get the same binary (see the bottom of +this section). + +The simplest local run: + +```bash +make emu +``` + +That builds (if needed), then invokes +`test-app/emu-test-apps/test.sh` against the staged binaries. The +script is what the wolfBoot CI lane calls; it produces the same UART +output and BKPT contract. + +### How `--persist` writes back + +`m33mu --persist` writes the emulated flash bank back to the input +`.bin` files on exit. For wolfHSM that matters because the keyvault +(`0x0C040000`) only carries the committed key forward across reboots +if the same flash image is re-loaded next time. The proof is in the +CI log: the first boot reports `wolfboot.bin: 383056 bytes`, the +second boot reports `wolfboot.bin: 2097152 bytes` against the same +filename. The file grew to fill the bank because that is what +`--persist` writes out. + +Two consequences worth knowing: + +- Run `--persist` against a *copy* of the build artifacts, not the + pristine outputs. After one persist run the .bin files are no + longer re-flashable images. +- The flash state lives inside those .bin files. The only sidecar + m33mu writes is `stm32h563_OTP.bin` (1 KB of OTP). + +`make emu` handles the staging for you. For a manual two-boot test: + +```bash +mkdir -p /tmp/m33mu-wolfhsm-persist +cp out/wolfboot.bin out/image_v1_signed.bin /tmp/m33mu-wolfhsm-persist/ +cd /tmp/m33mu-wolfhsm-persist + +# First boot: expect BKPT 0x7d (commits key to NVM) +m33mu wolfboot.bin image_v1_signed.bin:0x60000 \ + --persist --uart-stdout --timeout 120 --expect-bkpt 0x7d + +# Second boot: expect BKPT 0x7f (key restored) +m33mu wolfboot.bin image_v1_signed.bin:0x60000 \ + --persist --uart-stdout --timeout 120 --expect-bkpt 0x7f +``` + +If the second boot prints `wolfHSM first boot path, committing key +to NVM` instead of `wolfHSM second boot path, restored persisted +key`, the .bin files in the persist directory did not survive the +first run. Check that they are writable and grew to 2 MB after the +first boot. + +### Troubleshooting native m33mu + +If `m33mu` exits immediately with `symbol lookup error: ... libncursesw.so.6: +undefined symbol: _nc_safe_fopen`, your shell has an `LD_LIBRARY_PATH` set +to an older ncurses install (Warp's bundled tmux does this). Clear it: + +```bash +unset LD_LIBRARY_PATH +# or for a single invocation: +env -u LD_LIBRARY_PATH m33mu ... +``` + +### Running on a host without m33mu installed + +If `m33mu` is not on `PATH`, run it from the CI image directly: + +```bash +docker run --rm -v "$PWD":/persist -w /persist \ + ghcr.io/wolfssl/wolfboot-ci-m33mu:latest \ + m33mu wolfboot.bin image_v1_signed.bin:0x60000 \ + --persist --uart-stdout --timeout 120 --expect-bkpt 0x7d +``` + +This is identical to what the CI runs, just from a one-shot +container. Output and persistence behaviour are the same as a native +install. + + +## Continuous Integration + +The wolfBoot workflow at `.github/workflows/trustzone-emulator-tests.yml` +runs this demo on every PR. The wolfHSM lane: + +1. Builds via this port directory (`cd port/stmicro/stm32h5-tz-wolfhsm + && make`). +2. Runs the m33mu emulator with `--persist` twice (first boot, + second boot). +3. Verifies the expected log strings appear in the captured UART + output and that the right BKPT immediate value fired: + + ```text + First boot grep targets (must all appear): + wolfHSM CommInit ok + wolfHSM RNG ok: + wolfHSM SHA256 ok + wolfHSM AES ok + wolfHSM first boot path, committing key to NVM + wolfHSM NSC tests passed + [BKPT] imm=0x7d + [EXPECT BKPT] Success + + Second boot grep targets: + wolfHSM second boot path, restored persisted key + wolfHSM NSC tests passed + [BKPT] imm=0x7f + [EXPECT BKPT] Success + ``` + +If any of those strings is missing, the CI step fails with a clear +pointer to which boot path went wrong. + + +## Reference: minimal non-secure integration + +`demo-app/main.c` is a reference implementation of the smallest +non-secure entry point a wolfHSM ARMv8-M TZ client needs. It is not +the file wolfBoot links by default (that role is filled by +`test-app/app_stm32h5.c`), but it documents the canonical pattern in +one short source file. An integrator porting the wolfHSM ARMv8-M NSC +transport to a different ARMv8-M part can read `demo-app/main.c` and +the comments at the top to see exactly what the client side has to do: + +1. Allocate a static `whTransportNscClientContext`. +2. Fill in `whCommClientConfig` with the NSC transport callbacks + (`whTransportNscClient_Cb` from + `lib/wolfHSM/port/armv8m-tz/wh_transport_nsc.h`). +3. Wrap that in a `whClientConfig`. +4. Call `whTest_ClientConfig(&clientCfg)` (from wolfHSM's test + sources) for full client-suite coverage, or call individual + `whTest_*ClientConfig` variants for narrower coverage. +5. Signal pass/fail via `bkpt #0x7d` / `bkpt #0x7e` to match the m33mu + CI contract. + + +## Troubleshooting + +### Build fails with "sign: not found" + +The wolfBoot sign step resolves `./tools/keytools/sign` against the +caller's working directory. Always invoke this Makefile from the port +directory itself (`cd port/stmicro/stm32h5-tz-wolfhsm && make`). The +Makefile already `cd`s into the wolfBoot root before invoking the +top-level build to keep relative paths correct. + +If you reproduced the failure outside this Makefile, run the build +from the wolfBoot root directly: + +```bash +cd /path/to/wolfBoot +cp config/examples/stm32h5-tz-wolfhsm.config .config +make wolfboot.bin +make test-app/image_v1_signed.bin +``` + +### `STM32_Programmer_CLI` not found + +Either install STM32CubeProgrammer or point `load.sh` at the binary: + +```bash +STM32_PROGRAMMER_CLI=/path/to/STM32_Programmer_CLI ./load.sh +``` + +On macOS the binary is typically at +`/Applications/STMicroelectronics/STM32Cube/STM32CubeProgrammer/STM32CubeProgrammer.app/Contents/MacOs/bin/STM32_Programmer_CLI`. + +### Board never opens `/dev/ttyACM0` + +Confirm the NUCLEO is connected and that udev sees the ST-LINK V3E: + +```bash +ls /dev/ttyACM* /dev/serial/by-id/ 2>/dev/null +dmesg | tail | grep -i stlink +``` + +On Linux you may need to be in the `dialout` (Debian/Ubuntu) or +`uucp` (Arch) group to read the serial port without sudo. On macOS, +look for `/dev/tty.usbmodem*` and pass it through: + +```bash +SERIAL_PORT=/dev/tty.usbmodemXXXX ./load.sh +``` + +### TrustZone not enabled on the part + +Some NUCLEO-H563ZI boards ship with TrustZone disabled in option +bytes (`TZEN` bit). If wolfBoot panics during secure init, enable +TrustZone with STM32CubeProgrammer (Option Bytes pane) before +re-flashing. Reference: ST AN5347. + +### Second boot fails with "first boot path" output + +The emulator (`--persist`) or board carried stale state from a +previous incompatible build (PKCS11 / PSA / fwTPM). Either: + +- For emulator: delete the persistence directory and re-run. + ```bash + rm -rf /tmp/m33mu-wolfhsm-persist && mkdir /tmp/m33mu-wolfhsm-persist + ``` +- For hardware: re-run `./load.sh`, which mass-erases before + programming. + + +## Layout of this directory + +``` +port/stmicro/stm32h5-tz-wolfhsm/ + README.md # this file + Makefile # top-level convenience wrapper + load.sh # flash + serial console driver + demo-app/ + main.c # reference NS-side integration pattern + out/ # build outputs (created by `make`) + wolfboot.bin + image_v1_signed.bin + manifest.env +``` + + +## Relationship to wolfHSM + +The ARMv8-M NSC bridge transport this demo consumes lives in wolfHSM +itself: + +``` +wolfHSM/port/armv8m-tz/wh_transport_nsc.{c,h} +``` + +That transport is generic across any ARMv8-M part. wolfBoot pulls it +in through the `lib/wolfHSM` submodule and compiles it into both the +secure image (server side) and the non-secure image (client side). +The veneer `wcs_wolfhsm_transmit()` that the transport calls is the +one platform-specific piece, and it lives in this repository at +`src/wolfhsm_callable.c`. + +If you are porting to a different ARMv8-M part: + +1. Provide your own `wcs_wolfhsm_transmit()` implementation as a + `cmse_nonsecure_entry` function in the secure image. +2. Bring up the secure-side server using the same pattern wolfBoot + uses in `src/wolfhsm_callable.c`. +3. Drop the wolfHSM NSC client transport into your non-secure-world + application and call `whTest_ClientConfig()` (or your real + workload) over it. + +The `demo-app/main.c` in this directory is a deliberate one-file +reference for steps 2 and 3. diff --git a/port/stmicro/stm32h5-tz-wolfhsm/demo-app/main.c b/port/stmicro/stm32h5-tz-wolfhsm/demo-app/main.c new file mode 100644 index 0000000000..8b26ff44ab --- /dev/null +++ b/port/stmicro/stm32h5-tz-wolfhsm/demo-app/main.c @@ -0,0 +1,134 @@ +/* main.c - reference non-secure entry point for the wolfHSM TrustZone + * demo on STM32H5. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * This file is a reference implementation of the minimal non-secure + * entry point that a wolfHSM TrustZone client needs on ARMv8-M. + * + * It is NOT the file wolfBoot links into the default test app for + * stm32h5-tz-wolfhsm.config. That role is filled by + * test-app/app_stm32h5.c, which calls cmd_wolfhsm_test() from + * test-app/wcs/wolfhsm_test.c. This file documents the canonical + * pattern in one place so an integrator porting the wolfHSM ARMv8-M + * NSC transport to a different ARMv8-M part can read a single short + * source file and see exactly what the client side has to do. + * + * The single-app TrustZone model used here is: + * + * - The secure-world wolfBoot image hosts the wolfHSM server. It + * exposes the server to the non-secure world through one + * cmse_nonsecure_entry veneer, wcs_wolfhsm_transmit(), provided by + * wolfBoot under src/wolfhsm_callable.c. + * - The non-secure-world test application initialises a wolfHSM + * client whose transport is the generic ARMv8-M NSC bridge from + * wolfHSM at port/armv8m-tz/wh_transport_nsc.{c,h}. Send() calls + * the veneer inline; Recv() consumes the cached response. + * - The client calls whTest_ClientConfig() (from wolfHSM + * test/wh_test.h), which runs the full client-side wolfHSM test + * suite over the real NSC bridge: ClientServer, Crypto, KeyWrap, + * She, Timeout, Auth. + */ + +#include +#include +#include + +/* wolfHSM client and transport */ +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" + +#include "wh_transport_nsc.h" + +/* Forward-declared rather than included so this reference file does not + * depend on the wolfHSM test sources being on the build include path. */ +struct whClientConfig; +extern int whTest_ClientConfig(struct whClientConfig* clientCfg); + +#define WOLFHSM_DEMO_CLIENT_ID 1 + +/* Non-secure-side singletons. The transport callback table itself lives + * in the wolfHSM port at port/armv8m-tz/wh_transport_nsc.c; the context + * is a static buffer the callbacks operate on. */ +static whTransportNscClientContext s_nsc_ctx; +static whTransportNscClientConfig s_nsc_cfg; + +/* Hook for the integrator to set up clocks, GPIO, UART. wolfBoot + * provides equivalents (hal_init() and friends) when this code is + * built as part of test-app/app_stm32h5.c. */ +extern void wolfhsm_demo_board_init(void); +extern void wolfhsm_demo_uart_init(void); + +static int wolfhsm_demo_run(void) +{ + whCommClientConfig comm; + whClientConfig cfg; + int rc; + + memset(&s_nsc_ctx, 0, sizeof(s_nsc_ctx)); + memset(&s_nsc_cfg, 0, sizeof(s_nsc_cfg)); + memset(&comm, 0, sizeof(comm)); + memset(&cfg, 0, sizeof(cfg)); + + comm.transport_cb = &whTransportNscClient_Cb; + comm.transport_context = &s_nsc_ctx; + comm.transport_config = &s_nsc_cfg; + comm.client_id = WOLFHSM_DEMO_CLIENT_ID; + + cfg.comm = &comm; + + rc = whTest_ClientConfig((struct whClientConfig*)&cfg); + if (rc != WH_ERROR_OK) { + printf("wolfHSM demo: whTest_ClientConfig failed rc=%d\r\n", rc); + return rc; + } + + printf("wolfHSM demo: whTest_ClientConfig rc=0\r\n"); + return 0; +} + +int main(void) +{ + int rc; + + wolfhsm_demo_board_init(); + wolfhsm_demo_uart_init(); + + printf("\r\nwolfHSM TrustZone demo (STM32H5)\r\n"); + + rc = wolfhsm_demo_run(); + + /* Mirror the BKPT contract used by wolfBoot's m33mu CI harness: + * bkpt #0x7d => first-boot pass + * bkpt #0x7f => second-boot pass + * bkpt #0x7e => fail + * The single-run reference here only uses pass/fail. */ + if (rc == 0) { + __asm__ volatile ("bkpt #0x7d"); + } + else { + __asm__ volatile ("bkpt #0x7e"); + } + + while (1) { + } +} diff --git a/port/stmicro/stm32h5-tz-wolfhsm/load.sh b/port/stmicro/stm32h5-tz-wolfhsm/load.sh new file mode 100755 index 0000000000..ee373989a2 --- /dev/null +++ b/port/stmicro/stm32h5-tz-wolfhsm/load.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# load.sh - flash the wolfHSM TrustZone demo to a NUCLEO-H563ZI and +# optionally drop the user into a serial console. +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfBoot. +# +# wolfBoot is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +set -euo pipefail + +# Resolve paths relative to this script regardless of how it is invoked. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUT_DIR="${SCRIPT_DIR}/out" +MANIFEST="${OUT_DIR}/manifest.env" + +# Tools. Override via env var if installed elsewhere. +STM32CLI="${STM32_PROGRAMMER_CLI:-STM32_Programmer_CLI}" + +# Serial port for the NUCLEO-H563ZI's onboard ST-LINK V3E VCP. +# Linux: /dev/ttyACM0 by default. macOS: /dev/tty.usbmodem*. Override +# via SERIAL_PORT env var. +SERIAL_PORT="${SERIAL_PORT:-/dev/ttyACM0}" +SERIAL_BAUD="${SERIAL_BAUD:-115200}" + +# Whether to open a serial console after flashing. Default on; set +# OPEN_SERIAL=0 to skip (useful for unattended runs). +OPEN_SERIAL="${OPEN_SERIAL:-1}" + +log() { echo "==> $*"; } +die() { echo "error: $*" >&2; exit 1; } + +# Pull the addresses and binary paths produced by `make stage`. Failing +# fast here gives a clearer error than letting STM32_Programmer_CLI +# complain about a missing file. +[[ -f "${MANIFEST}" ]] || die "${MANIFEST} not found. Run 'make' in this directory first." + +# shellcheck disable=SC1090 +source "${MANIFEST}" + +[[ -f "${WOLFBOOT_BIN}" ]] || die "WOLFBOOT_BIN missing: ${WOLFBOOT_BIN}" +[[ -f "${TEST_APP_BIN}" ]] || die "TEST_APP_BIN missing: ${TEST_APP_BIN}" + +command -v "${STM32CLI}" >/dev/null 2>&1 || \ + die "${STM32CLI} not on PATH. Install STM32CubeProgrammer or set STM32_PROGRAMMER_CLI." + +# Two-stage flash: +# 1. wolfboot.bin -> 0x08000000 (secure side base flash). +# 2. image_v1_signed.bin -> BOOT_ADDR (non-secure side test app slot). +# +# Run a mass-erase first so we never inherit stale partitions from a +# previous run (PKCS11/PSA/fwTPM configs share the same flash range). +log "Mass erase + programming wolfboot.bin to 0x08000000" +"${STM32CLI}" -c port=SWD reset=HWrst -e all -d "${WOLFBOOT_BIN}" 0x08000000 -v + +log "Programming test app image to ${BOOT_ADDR}" +"${STM32CLI}" -c port=SWD reset=HWrst -d "${TEST_APP_BIN}" "${BOOT_ADDR}" -v + +log "Hardware reset" +"${STM32CLI}" -c port=SWD reset=HWrst -hardRst + +if [[ "${OPEN_SERIAL}" != "1" ]]; then + log "OPEN_SERIAL=0, skipping serial monitor" + exit 0 +fi + +# Pick the first available serial monitor. picocom is preferred (clean +# exit with Ctrl-A Ctrl-X); fall back to screen if not installed. +if command -v picocom >/dev/null 2>&1; then + log "Opening picocom on ${SERIAL_PORT} @ ${SERIAL_BAUD}-8N1 (Ctrl-A Ctrl-X to exit)" + exec picocom -b "${SERIAL_BAUD}" "${SERIAL_PORT}" +elif command -v screen >/dev/null 2>&1; then + log "Opening screen on ${SERIAL_PORT} @ ${SERIAL_BAUD} (Ctrl-A k to exit)" + exec screen "${SERIAL_PORT}" "${SERIAL_BAUD}" +else + log "No serial monitor found. Install picocom or screen, or set OPEN_SERIAL=0." + log "To watch the demo manually: ${SERIAL_PORT} ${SERIAL_BAUD}" + exit 0 +fi diff --git a/port/stmicro/stm32h5-tz-wolfhsm/out/manifest.env b/port/stmicro/stm32h5-tz-wolfhsm/out/manifest.env new file mode 100644 index 0000000000..be2bb73ebb --- /dev/null +++ b/port/stmicro/stm32h5-tz-wolfhsm/out/manifest.env @@ -0,0 +1,3 @@ +BOOT_ADDR=0x08060000 +WOLFBOOT_BIN=/home/aidangarske/wolfBoot/port/stmicro/stm32h5-tz-wolfhsm/out/wolfboot.bin +TEST_APP_BIN=/home/aidangarske/wolfBoot/port/stmicro/stm32h5-tz-wolfhsm/out/image_v1_signed.bin diff --git a/test-app/Makefile b/test-app/Makefile index 9af7a720bd..563977f365 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -389,11 +389,20 @@ ifeq ($(TZEN),1) endif CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-tz" + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/test" WOLFCRYPT_APP_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/cryptocb.o APP_OBJS+=./wcs/wolfhsm_test.o APP_OBJS+=./wcs/wolfhsm_stub.o WOLFHSM_APP_OBJS+=$(WOLFHSM_CLIENT_OBJS) WOLFHSM_APP_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-tz/wh_transport_nsc.o + # wolfHSM bare-metal-safe test sources for whTest_ClientConfig. + # We avoid wh_test.c (has its own main()) and wh_test_common.c (its + # static whNvmCb table pulls in server-side wh_NvmFlash_* symbols + # that don't belong in the NS image). The remaining files compile + # cleanly under WOLFHSM_CFG_ENABLE_CLIENT only. + WOLFHSM_APP_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/test/wh_test_clientserver.o + WOLFHSM_APP_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/test/wh_test_crypto.o + WOLFHSM_APP_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/test/wh_test_nvmflags.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 8d8343f274..36e3d3a4c5 100644 --- a/test-app/wcs/wolfhsm_stub.c +++ b/test-app/wcs/wolfhsm_stub.c @@ -16,11 +16,34 @@ #include +#include "wolfhsm/wh_error.h" #include "wh_transport_nsc.h" +#include "wh_test_common.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; +/* Stub for the in-process NVM-backend helper. wh_test_clientserver.c and + * wh_test_crypto.c reference this from server-side test paths + * (whTest_ClientServerSequential and friends). Those paths spawn a server + * alongside the client in the same process; we never reach them because + * the secure-world wolfBoot image hosts the real server. Returning + * WH_ERROR_BADARGS keeps the linker happy and would surface a clear + * failure if anything did try to call it. */ +int whTest_NvmCfgBackend(whTestNvmBackendType type, + whTestNvmBackendUnion* nvmSetup, whNvmConfig* nvmCfg, + whFlashRamsimCfg* fCfg, whFlashRamsimCtx* fCtx, + const whFlashCb* fCb) +{ + (void)type; + (void)nvmSetup; + (void)nvmCfg; + (void)fCfg; + (void)fCtx; + (void)fCb; + return WH_ERROR_BADARGS; +} + #endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/wcs/wolfhsm_test.c b/test-app/wcs/wolfhsm_test.c index 22c7d0fee5..19953d3c06 100644 --- a/test-app/wcs/wolfhsm_test.c +++ b/test-app/wcs/wolfhsm_test.c @@ -14,220 +14,29 @@ #include "wolfhsm_test.h" #include "wolfhsm/wh_client.h" -#include "wolfhsm/wh_client_crypto.h" #include "wolfhsm/wh_common.h" #include "wolfhsm/wh_comm.h" #include "wolfhsm/wh_error.h" -#include "wolfhsm/wh_keyid.h" - -#include "wolfssl/wolfcrypt/aes.h" -#include "wolfssl/wolfcrypt/misc.h" -#include "wolfssl/wolfcrypt/random.h" -#include "wolfssl/wolfcrypt/sha256.h" #include "wh_transport_nsc.h" +/* These are the two bare-metal-safe entries that whTest_ClientConfig + * itself dispatches to in lib/wolfHSM/test/wh_test.c. Forward-declared + * here so we do not need to pull in wh_test.c (which carries its own + * main()) just to call them. */ +extern int whTest_ClientServerClientConfig(whClientConfig* cfg); +extern int whTest_CryptoClientConfig(whClientConfig* cfg); + /* NS-side singleton transport context lives in wolfhsm_stub.c. */ extern whTransportNscClientContext g_wolfhsm_nsc_client_ctx; #define WCS_WOLFHSM_CLIENT_ID 1 -static int wolfhsm_test_rng(void) -{ - WC_RNG rng; - uint8_t rnd[16]; - unsigned int i; - int rc; - - memset(&rng, 0, sizeof(rng)); - memset(rnd, 0, sizeof(rnd)); - - rc = wc_InitRng_ex(&rng, NULL, WH_DEV_ID); - if (rc != 0) { - printf("wolfHSM RNG init failed: %d\r\n", rc); - return rc; - } - - rc = wc_RNG_GenerateBlock(&rng, rnd, sizeof(rnd)); - if (rc != 0) { - printf("wolfHSM RNG generate failed: %d\r\n", rc); - (void)wc_FreeRng(&rng); - return rc; - } - - printf("wolfHSM RNG ok:"); - for (i = 0; i < sizeof(rnd); i++) { - printf(" %02x", rnd[i]); - } - printf("\r\n"); - - (void)wc_FreeRng(&rng); - return 0; -} - -static int wolfhsm_test_sha256(void) -{ - /* SHA256("abc") — FIPS 180-2, Appendix B.1. */ - static const uint8_t expected[WC_SHA256_DIGEST_SIZE] = { - 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, - 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, - 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, - 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad - }; - wc_Sha256 sha; - uint8_t digest[WC_SHA256_DIGEST_SIZE]; - int rc; - - memset(&sha, 0, sizeof(sha)); - memset(digest, 0, sizeof(digest)); - - rc = wc_InitSha256_ex(&sha, NULL, WH_DEV_ID); - if (rc != 0) { - printf("wolfHSM SHA256 init failed: %d\r\n", rc); - return rc; - } - - rc = wc_Sha256Update(&sha, (const uint8_t*)"abc", 3); - if (rc == 0) { - rc = wc_Sha256Final(&sha, digest); - } - wc_Sha256Free(&sha); - if (rc != 0) { - printf("wolfHSM SHA256 hash failed: %d\r\n", rc); - return rc; - } - - if (memcmp(digest, expected, sizeof(expected)) != 0) { - printf("wolfHSM SHA256 mismatch\r\n"); - return -1; - } - printf("wolfHSM SHA256 ok\r\n"); - return 0; -} - -static int wolfhsm_test_aes_cached(whClientContext *client) -{ - /* FIPS 197 Appendix B AES-128 vector. CBC with IV=0 yields the same - * first-block ciphertext as ECB, so a single block under CBC suffices - * to verify the key+algorithm wired through correctly. */ - static const uint8_t key[16] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f - }; - static const uint8_t pt[16] = { - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff - }; - static const uint8_t expected[16] = { - 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, - 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a - }; - static const uint8_t iv[16] = { 0 }; - Aes aes; - uint8_t ct[16]; - uint16_t keyId = WH_KEYID_ERASED; - int aes_inited = 0; - int rc; - - memset(&aes, 0, sizeof(aes)); - memset(ct, 0, sizeof(ct)); - - rc = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, 0, - key, (uint16_t)sizeof(key), &keyId); - if (rc != WH_ERROR_OK) { - printf("wolfHSM KeyCache failed: %d\r\n", rc); - return rc; - } - - rc = wc_AesInit(&aes, NULL, WH_DEV_ID); - if (rc != 0) { - printf("wolfHSM AesInit failed: %d\r\n", rc); - goto out; - } - aes_inited = 1; - - rc = wh_Client_AesSetKeyId(&aes, keyId); - if (rc != WH_ERROR_OK) { - printf("wolfHSM AesSetKeyId failed: %d\r\n", rc); - goto out; - } - - rc = wc_AesSetIV(&aes, iv); - if (rc != 0) { - printf("wolfHSM AesSetIV failed: %d\r\n", rc); - goto out; - } - rc = wc_AesCbcEncrypt(&aes, ct, pt, (word32)sizeof(pt)); - if (rc != 0) { - printf("wolfHSM AES encrypt failed: %d\r\n", rc); - goto out; - } - - if (memcmp(ct, expected, sizeof(expected)) != 0) { - printf("wolfHSM AES mismatch\r\n"); - rc = -1; - goto out; - } - printf("wolfHSM AES ok\r\n"); - -out: - if (aes_inited) { - wc_AesFree(&aes); - } - (void)wh_Client_KeyEvict(client, keyId); - return rc; -} - -static int wolfhsm_test_persist(whClientContext *client, int *boot_state) -{ - static const uint8_t persist_key[16] = { - 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, - 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x00 - }; - uint16_t keyId = WH_MAKE_KEYID(0, WCS_WOLFHSM_CLIENT_ID, 1); - uint8_t out[sizeof(persist_key)]; - uint16_t outSz = (uint16_t)sizeof(out); - int rc; - - memset(out, 0, sizeof(out)); - rc = wh_Client_KeyExport(client, keyId, NULL, 0, out, &outSz); - if (rc == WH_ERROR_OK && outSz == sizeof(persist_key) && - memcmp(out, persist_key, sizeof(persist_key)) == 0) { - printf("wolfHSM second boot path, restored persisted key\r\n"); - *boot_state = WOLFHSM_TEST_SECOND_BOOT_OK; - wc_ForceZero(out, sizeof(out)); - return 0; - } - wc_ForceZero(out, sizeof(out)); - - printf("wolfHSM first boot path, committing key to NVM\r\n"); - rc = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, 0, - persist_key, (uint16_t)sizeof(persist_key), - &keyId); - if (rc != WH_ERROR_OK) { - printf("wolfHSM persist KeyCache failed: %d\r\n", rc); - return rc; - } - rc = wh_Client_KeyCommit(client, keyId); - if (rc != WH_ERROR_OK) { - printf("wolfHSM persist KeyCommit failed: %d\r\n", rc); - (void)wh_Client_KeyEvict(client, keyId); - return rc; - } - (void)wh_Client_KeyEvict(client, keyId); - *boot_state = WOLFHSM_TEST_FIRST_BOOT_OK; - return 0; -} - int cmd_wolfhsm_test(const char *args) { static const whTransportNscClientConfig nsc_cfg = { 0 }; whCommClientConfig comm_cfg; whClientConfig cfg; - whClientContext client; - uint32_t out_clientid = 0; - uint32_t out_serverid = 0; - int boot_state = WOLFHSM_TEST_FAIL; int rc; (void)args; @@ -241,52 +50,28 @@ int cmd_wolfhsm_test(const char *args) memset(&cfg, 0, sizeof(cfg)); cfg.comm = &comm_cfg; - memset(&client, 0, sizeof(client)); - - rc = wh_Client_Init(&client, &cfg); - if (rc != WH_ERROR_OK) { - printf("wolfHSM Init failed: %d\r\n", rc); - return WOLFHSM_TEST_FAIL; - } - - rc = wh_Client_CommInit(&client, &out_clientid, &out_serverid); - if (rc != WH_ERROR_OK) { - printf("wolfHSM CommInit failed: %d\r\n", rc); - (void)wh_Client_Cleanup(&client); - return WOLFHSM_TEST_FAIL; - } - - printf("wolfHSM CommInit ok (client=%u server=%u)\r\n", - (unsigned)out_clientid, (unsigned)out_serverid); - - rc = wolfhsm_test_rng(); + /* Run the full wolfHSM client suite over the real NSC bridge into + * the secure-world server. Exercises echo + NVM + cert ops then + * RNG + SHA + AES + RSA + ECC + ECDH + HKDF. No POSIX, no pthread + * fake; every op goes through the real ARMv8-M TrustZone NSC + * veneer. This is the body of whTest_ClientConfig inlined for the + * features enabled on this build. */ + rc = whTest_ClientServerClientConfig(&cfg); if (rc != 0) { - (void)wh_Client_Cleanup(&client); + printf("wolfHSM ClientServerClientConfig FAILED rc=%d\r\n", rc); return WOLFHSM_TEST_FAIL; } - rc = wolfhsm_test_sha256(); +#ifndef WOLFHSM_CFG_NO_CRYPTO + rc = whTest_CryptoClientConfig(&cfg); if (rc != 0) { - (void)wh_Client_Cleanup(&client); + printf("wolfHSM CryptoClientConfig FAILED rc=%d\r\n", rc); return WOLFHSM_TEST_FAIL; } +#endif - rc = wolfhsm_test_aes_cached(&client); - if (rc != 0) { - (void)wh_Client_Cleanup(&client); - return WOLFHSM_TEST_FAIL; - } - - rc = wolfhsm_test_persist(&client, &boot_state); - if (rc != 0) { - (void)wh_Client_Cleanup(&client); - return WOLFHSM_TEST_FAIL; - } - - printf("wolfHSM NSC tests passed\r\n"); - - (void)wh_Client_Cleanup(&client); - return boot_state; + printf("wolfHSM whTest_ClientConfig PASSED\r\n"); + return WOLFHSM_TEST_FIRST_BOOT_OK; } #endif /* WOLFCRYPT_TZ_WOLFHSM */