diff --git a/.github/workflows/test-external-library-paths.yml b/.github/workflows/test-external-library-paths.yml index 4d437547cb..f34a6c3351 100644 --- a/.github/workflows/test-external-library-paths.yml +++ b/.github/workflows/test-external-library-paths.yml @@ -86,4 +86,5 @@ jobs: echo "=== Building unit tests with external paths ===" make -C tools/unit-tests \ WOLFBOOT_LIB_WOLFSSL="$(realpath ../external-libs/wolfssl)" \ - WOLFBOOT_LIB_WOLFPKCS11="$(realpath ../external-libs/wolfPKCS11)" + WOLFBOOT_LIB_WOLFPKCS11="$(realpath ../external-libs/wolfPKCS11)" \ + WOLFBOOT_LIB_WOLFHSM="$(realpath ../external-libs/wolfHSM)" diff --git a/.github/workflows/trustzone-emulator-tests.yml b/.github/workflows/trustzone-emulator-tests.yml index 432421dc0a..e446646a7b 100644 --- a/.github/workflows/trustzone-emulator-tests.yml +++ b/.github/workflows/trustzone-emulator-tests.yml @@ -119,6 +119,44 @@ 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 + 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 + + - 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 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: | make clean distclean diff --git a/config/examples/stm32h5-tz-wolfhsm.config b/config/examples/stm32h5-tz-wolfhsm.config new file mode 100644 index 0000000000..92184707ef --- /dev/null +++ b/config/examples/stm32h5-tz-wolfhsm.config @@ -0,0 +1,35 @@ +ARCH?=ARM +TZEN?=1 +TARGET?=stm32h5 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +CORTEX_M33?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +WOLFBOOT_PARTITION_SIZE?=0xA0000 +WOLFBOOT_SECTOR_SIZE?=0x2000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x0C040000 +WOLFBOOT_KEYVAULT_SIZE?=0x1C000 +WOLFBOOT_NSC_ADDRESS?=0x0C05C000 +WOLFBOOT_NSC_SIZE?=0x4000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08060000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x0C100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x0C1A0000 +FLAGS_HOME=0 +DISABLE_BACKUP=0 +WOLFCRYPT_TZ=1 +WOLFCRYPT_TZ_WOLFHSM=1 +IMAGE_HEADER_SIZE?=1024 +ARMORED=1 diff --git a/docs/STM32-TZ.md b/docs/STM32-TZ.md index a20e0986d3..982546d3df 100644 --- a/docs/STM32-TZ.md +++ b/docs/STM32-TZ.md @@ -33,6 +33,19 @@ The `WOLFCRYPT_TZ_PSA` option provides a standard PSA Crypto interface using wolfPSA in the secure domain. The key storage uses the same secure flash keystore backend as PKCS11, exposed through the wolfPSA store API. +### wolfHSM API in non-secure world + +The `WOLFCRYPT_TZ_WOLFHSM` option hosts a wolfHSM server inside the secure +domain and exposes it to non-secure applications through a single non-secure +callable veneer. Non-secure code uses the standard wolfCrypt API with the +wolfHSM client cryptocb registered under `WH_DEV_ID`; key material, the +keystore, and crypto operations stay in the secure domain. Persistent keys +live in the same secure flash keystore region used by PKCS11 and PSA, with +two-partition journaling for power-fail safety. + +See [wolfHSM](wolfHSM.md) for the full configuration, build, flash, and +test recipe on STM32H5. + ### PSA Initial Attestation (DICE) When `WOLFCRYPT_TZ_PSA=1` is enabled, wolfBoot exposes the PSA Initial diff --git a/docs/wolfHSM.md b/docs/wolfHSM.md index b6fafbcb94..39827473e8 100644 --- a/docs/wolfHSM.md +++ b/docs/wolfHSM.md @@ -21,6 +21,7 @@ wolfBoot supports using wolfHSM on the following platforms: - wolfBoot simulator (using wolfHSM POSIX TCP transport) - AURIX TC3xx (shared memory transport) +- STM32H5 TrustZone (the secure-side wolfBoot hosts a wolfHSM server and exposes it to the non-secure application through a single NSC veneer; see [STM32H5 TrustZone Engine](#stm32h5-trustzone-engine) below) Details on configuring wolfBoot to use wolfHSM on each of these platforms can be found in the wolfBoot (and wolfHSM) documentation specific to that target, with the exception of the simulator, which is documented here. The remainder of this document focuses on the generic wolfHSM-related configuration options. @@ -238,3 +239,51 @@ When using wolfHSM server mode, no external server is required. wolfBoot include ``` The embedded wolfHSM server will automatically handle all cryptographic operations and key management using the file-based NVM storage(`wolfBoot_wolfHSM_NVM.bin`) that was generated above. + +## STM32H5 TrustZone Engine + +On STM32H5, wolfBoot can host a wolfHSM server in the secure TrustZone image and expose it to the non-secure application through a single non-secure-callable veneer (`wcs_wolfhsm_transmit`). The non-secure side runs the standard wolfHSM client API, which auto-registers a wolfCrypt cryptocb under `WH_DEV_ID`, so application-level wolfCrypt calls that pass that device ID transparently round-trip to the secure server. + +This is a separate deployment shape from the wolfHSM client/server modes documented above; it does not use `WOLFBOOT_ENABLE_WOLFHSM_CLIENT/SERVER` or the `hsmClientCtx`/`hsmServerCtx` HAL hooks, and is mutually exclusive with the other STM32H5 TrustZone engines (`WOLFCRYPT_TZ_PKCS11`, `WOLFCRYPT_TZ_PSA`, `WOLFCRYPT_TZ_FWTPM`). + +### Build + +```sh +cp config/examples/stm32h5-tz-wolfhsm.config .config +make +``` + +For on-board hardware testing, add `WOLFBOOT_TZ_TEST_NO_BKPT=1` so the auto-test prints a UART pass/fail line and idles in `while (1)` instead of issuing `bkpt #0x7f` (which HardFaults on real silicon without a debugger): + +```sh +make WOLFBOOT_TZ_TEST_NO_BKPT=1 +``` + +### Flash + +The wolfBoot helper programs the option bytes the secure boot path requires (`TZEN`, `SECBOOTADD`, `SECWM1`/`SECWM2`); see [STM32-TZ.md](STM32-TZ.md) for the option-byte details: + +```sh +./tools/scripts/set-stm32-tz-option-bytes.sh +STM32_Programmer_CLI -c port=swd -d wolfboot.bin 0x0C000000 +STM32_Programmer_CLI -c port=swd -d test-app/image_v1_signed.bin 0x08060000 +``` + +### Test + +The non-secure test application runs the wolfHSM auto-test at startup. A successful first boot ends with: + +```text +wolfHSM CommInit ok (client=1 server=...) +wolfHSM RNG ok: <16 random bytes> +wolfHSM SHA256 ok +wolfHSM AES ok +wolfHSM first boot path, committing key to NVM +wolfHSM NSC tests passed +``` + +The default build raises `bkpt #0x7d` on first-boot success and `bkpt #0x7f` on second-boot success (after the persisted key is reloaded from flash on reset). The `WOLFBOOT_TZ_TEST_NO_BKPT=1` build prints a final `WOLFHSM_TZ_TEST_PASS` UART line instead. Reset the board (no re-flash) to verify persistence; the second boot prints `wolfHSM second boot path, restored persisted key`. + +### Notes + +The wolfHSM NVM lives in the existing `FLASH_KEYVAULT` region (112 KiB at `0x0C040000`) shared with the other STM32H5 TrustZone engines. The flash adapter (`src/wolfhsm_flash_hal.c`) caches the affected sector, modifies it, and rewrites the whole 8 KiB sector in one erase + program cycle, mirroring `psa_store.c` / `pkcs11_store.c`. This satisfies the H5 quad-word ECC rule that each 16-byte unit may be programmed exactly once between erases, which wolfHSM's 8-byte-unit writes would otherwise violate. diff --git a/include/user_settings.h b/include/user_settings.h index c510968aae..d36c45f585 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -768,12 +768,51 @@ 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 + +/* 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) +# 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/include/wolfboot/wcs_wolfhsm.h b/include/wolfboot/wcs_wolfhsm.h new file mode 100644 index 0000000000..da65c33dc3 --- /dev/null +++ b/include/wolfboot/wcs_wolfhsm.h @@ -0,0 +1,23 @@ +/* wcs_wolfhsm.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifndef WOLFBOOT_WCS_WOLFHSM_H +#define WOLFBOOT_WCS_WOLFHSM_H + +#include +#include "wolfboot/wc_secure.h" + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, + uint8_t *rsp, uint32_t *rspSz); + +void wcs_wolfhsm_init(void); + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ + +#endif /* WOLFBOOT_WCS_WOLFHSM_H */ diff --git a/include/wolfboot/wolfhsm_flash_hal.h b/include/wolfboot/wolfhsm_flash_hal.h new file mode 100644 index 0000000000..77720ff5b6 --- /dev/null +++ b/include/wolfboot/wolfhsm_flash_hal.h @@ -0,0 +1,30 @@ +/* wolfhsm_flash_hal.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifndef WOLFBOOT_WOLFHSM_FLASH_HAL_H +#define WOLFBOOT_WOLFHSM_FLASH_HAL_H + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include + +#include "wolfhsm/wh_flash.h" + +/* Per-call config / context for the adapter. base/size/partition_size are + * the only client-visible fields; the cache lives inside the static + * implementation in wolfhsm_flash_hal.c (mirroring psa_store.c). */ +typedef struct { + uint32_t base; + uint32_t size; + uint32_t partition_size; +} whFlashH5Ctx; + +extern const whFlashCb whFlashH5_Cb; + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ + +#endif /* WOLFBOOT_WOLFHSM_FLASH_HAL_H */ diff --git a/lib/wolfHSM b/lib/wolfHSM index 977bf187e7..c8bce36424 160000 --- a/lib/wolfHSM +++ b/lib/wolfHSM @@ -1 +1 @@ -Subproject commit 977bf187e7a57a184493dcd216eb9a328f381865 +Subproject commit c8bce364249d74e7166c527aaa4229b57bc94ca6 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 6062e77193..bb8cb56f80 100644 --- a/options.mk +++ b/options.mk @@ -1,4 +1,43 @@ WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/asn.o + +# Shared wolfHSM client/server object lists. Defined here at the top so any +# downstream block (legacy WOLFHSM_CLIENT/SERVER, or WOLFCRYPT_TZ_WOLFHSM TZ +# engine) can reference them by variable name without ordering hazards. +WOLFHSM_CLIENT_OBJS := \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_cryptocb.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_client_dma.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_dma.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_utils.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_comm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_comm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_customcb.o + +WOLFHSM_SERVER_OBJS := \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_utils.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_comm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_nvm_flash.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_keyid.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_flash_unit.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_counter.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_keystore.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_customcb.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_customcb.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_keystore.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_crypto.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_counter.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_nvm.o \ + $(WOLFBOOT_LIB_WOLFHSM)/src/wh_message_comm.o + USE_CLANG?=0 ifeq ($(USE_CLANG),1) USE_GCC?=0 @@ -935,12 +974,24 @@ ifeq ($(WOLFCRYPT_TZ_PKCS11),1) ifeq ($(WOLFCRYPT_TZ_FWTPM),1) $(error WOLFCRYPT_TZ_PKCS11 and WOLFCRYPT_TZ_FWTPM are mutually exclusive) endif + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + $(error WOLFCRYPT_TZ_PKCS11 and WOLFCRYPT_TZ_WOLFHSM are mutually exclusive) + endif endif ifeq ($(WOLFCRYPT_TZ_PSA),1) ifeq ($(WOLFCRYPT_TZ_FWTPM),1) $(error WOLFCRYPT_TZ_PSA and WOLFCRYPT_TZ_FWTPM are mutually exclusive) endif + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + $(error WOLFCRYPT_TZ_PSA and WOLFCRYPT_TZ_WOLFHSM are mutually exclusive) + endif +endif + +ifeq ($(WOLFCRYPT_TZ_FWTPM),1) + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + $(error WOLFCRYPT_TZ_FWTPM and WOLFCRYPT_TZ_WOLFHSM are mutually exclusive) + endif endif ifeq ($(WOLFBOOT_DICE_HW),1) @@ -1103,6 +1154,47 @@ ifeq ($(WOLFCRYPT_TZ_FWTPM),1) STACK_USAGE=20000 endif +ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + CFLAGS+=-DWOLFCRYPT_TZ_WOLFHSM + CFLAGS+=-DWOLFCRYPT_SECURE_MODE + CFLAGS+=-DWOLFHSM_CFG_ENABLE_SERVER + CFLAGS+=-DWOLFHSM_CFG_COMM_DATA_LEN=1280 + CFLAGS+=-DWOLFHSM_CFG_PORT_ARMV8M_TZ_NSC + CFLAGS+=-DWOLFHSM_CFG_NO_SYS_TIME + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-tz" + ifeq ($(USE_CLANG),1) + CLANG_MULTILIB_FLAGS:=$(filter -mthumb -mlittle-endian,$(LDFLAGS)) $(filter -mcpu=%,$(CFLAGS)) + LIBS+=$(shell $(CLANG_GCC_NAME) $(CLANG_MULTILIB_FLAGS) -print-file-name=libc.a) + LIBS+=$(shell $(CLANG_GCC_NAME) $(CLANG_MULTILIB_FLAGS) -print-libgcc-file-name) + else + LDFLAGS+=--specs=nano.specs + endif + WOLFCRYPT_OBJS+=src/store_sbrk.o + WOLFCRYPT_OBJS+=src/wolfhsm_callable.o + WOLFCRYPT_OBJS+=src/wolfhsm_flash_hal.o + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/cryptocb.o + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/coding.o + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/hmac.o + ifneq ($(SIGN),ED25519) + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha512.o + endif + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_encrypt.o + ifeq ($(ENCRYPT_WITH_AES128)$(ENCRYPT_WITH_AES256),) + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/aes.o + endif + WOLFCRYPT_OBJS+=$(RSA_OBJS) + ifeq ($(findstring ECC,$(SIGN)),) + ifeq ($(findstring ECC,$(SIGN_SECONDARY)),) + WOLFCRYPT_OBJS+=$(ECC_OBJS) + WOLFCRYPT_OBJS+=$(MATH_OBJS) + endif + endif + WOLFHSM_OBJS+=$(WOLFHSM_SERVER_OBJS) + WOLFHSM_OBJS+=$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-tz/wh_transport_nsc.o + STACK_USAGE=20000 +endif + OBJS+=$(PUBLIC_KEY_OBJS) ifneq ($(STAGE1),1) OBJS+=$(UPDATE_OBJS) @@ -1326,19 +1418,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 +1464,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/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/src/wc_callable.c b/src/wc_callable.c index 1dd9761232..706c1c692a 100644 --- a/src/wc_callable.c +++ b/src/wc_callable.c @@ -34,6 +34,10 @@ #include "wolfboot/wcs_fwtpm.h" #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM +#include "wolfboot/wcs_wolfhsm.h" +#endif + static WC_RNG wcs_rng; int CSME_NSE_API wcs_get_random(uint8_t *rand, uint32_t size) @@ -48,6 +52,9 @@ void wcs_Init(void) #ifdef WOLFBOOT_TZ_FWTPM wcs_fwtpm_init(); #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM + wcs_wolfhsm_init(); +#endif } #endif /* WOLFCRYPT_SECURE_MODE */ diff --git a/src/wolfhsm_callable.c b/src/wolfhsm_callable.c new file mode 100644 index 0000000000..71ab752351 --- /dev/null +++ b/src/wolfhsm_callable.c @@ -0,0 +1,182 @@ +/* wolfhsm_callable.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include + +#include "loader.h" +#include "store_sbrk.h" +#include "wolfboot/wcs_wolfhsm.h" +#include "wolfboot/wolfhsm_flash_hal.h" + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/random.h" + +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_server.h" + +#include "wh_transport_nsc.h" + +extern unsigned int _start_heap; +extern unsigned int _heap_size; + +void *_sbrk(unsigned int incr) +{ + static uint8_t *heap; + return wolfboot_store_sbrk(incr, &heap, (uint8_t *)&_start_heap, + (uint32_t)(&_heap_size)); +} + +#define WCS_WOLFHSM_SERVER_ID 56U + +/* Two 32 KiB partitions in the wolfBoot keyvault region: 64 KiB used out of + * 112 KiB, leaving headroom. Per-partition layout = 24 B state header + + * 32 directory entries * 56 B (= ~1.8 KiB) + ~30 KiB usable payload. */ +#define WCS_WOLFHSM_PARTITION_SIZE (32U * 1024U) + +/* Linker-provided symbols for the FLASH_KEYVAULT region defined in + * hal/stm32h5.ld; matches the PSA / PKCS11 stores' pattern. */ +extern uint32_t _flash_keyvault; +extern uint32_t _flash_keyvault_size; + +static whFlashH5Ctx g_flash_ctx; + +static whNvmFlashContext g_nvm_flash_ctx; +static whNvmFlashConfig g_nvm_flash_cfg = { + .cb = &whFlashH5_Cb, + .context = &g_flash_ctx, + /* .config is set at runtime in wcs_wolfhsm_init */ +}; +static whNvmCb g_nvm_flash_cb = WH_NVM_FLASH_CB; +static whNvmContext g_nvm_ctx; +static whNvmConfig g_nvm_cfg = { + .cb = &g_nvm_flash_cb, + .context = &g_nvm_flash_ctx, + .config = &g_nvm_flash_cfg, +}; + +static whServerCryptoContext g_crypto_ctx; +static whTransportNscServerContext g_srv_tx_ctx; +static whTransportNscServerConfig g_srv_tx_cfg = { 0 }; +static whCommServerConfig g_comm_cfg = { + .transport_context = &g_srv_tx_ctx, + .transport_cb = &whTransportNscServer_Cb, + .transport_config = &g_srv_tx_cfg, + .server_id = WCS_WOLFHSM_SERVER_ID, +}; +static whServerConfig g_server_cfg = { + .comm_config = &g_comm_cfg, + .nvm = &g_nvm_ctx, + .crypto = &g_crypto_ctx, +#ifdef WOLF_CRYPTO_CB + .devId = INVALID_DEVID, +#endif +}; + +static whServerContext g_server; +static int g_wolfhsm_ready; + +void wcs_wolfhsm_init(void) +{ + whFlashH5Ctx flash_cfg; + int rc; + + if (g_wolfhsm_ready) { + return; + } + + memset(&g_srv_tx_ctx, 0, sizeof(g_srv_tx_ctx)); + + flash_cfg.base = (uint32_t)&_flash_keyvault; + flash_cfg.size = (uint32_t)&_flash_keyvault_size; + flash_cfg.partition_size = WCS_WOLFHSM_PARTITION_SIZE; + g_nvm_flash_cfg.config = &flash_cfg; + + /* g_crypto_ctx.rng is an embedded WC_RNG[1] (see wh_server.h) */ + rc = wc_InitRng(&g_crypto_ctx.rng[0]); + if (rc != 0) { + wolfBoot_panic(); + } + rc = wh_Nvm_Init(&g_nvm_ctx, &g_nvm_cfg); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); + } + rc = wh_Server_Init(&g_server, &g_server_cfg); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); + } + rc = wh_Server_SetConnected(&g_server, WH_COMM_CONNECTED); + if (rc != WH_ERROR_OK) { + wolfBoot_panic(); + } + g_wolfhsm_ready = 1; +} + +int CSME_NSE_API wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz, + uint8_t *rsp, uint32_t *rspSz) +{ + uint32_t rsp_capacity; + int rc; + + if (cmd == NULL || rsp == NULL || rspSz == NULL) { + return WH_ERROR_BADARGS; + } + /* single-fetch *rspSz so it cannot be re-read after validation */ + rsp_capacity = *(volatile const uint32_t *)rspSz; + + if (cmdSz == 0U || cmdSz > WH_COMM_MTU) { + *rspSz = 0; + return WH_ERROR_BADARGS; + } + if (rsp_capacity == 0U || rsp_capacity > WH_COMM_MTU) { + *rspSz = 0; + return WH_ERROR_BADARGS; + } + if (!g_wolfhsm_ready) { + *rspSz = 0; + return WH_ERROR_NOTREADY; + } + + g_srv_tx_ctx.req_buf = cmd; + g_srv_tx_ctx.req_size = (uint16_t)cmdSz; + g_srv_tx_ctx.rsp_buf = rsp; + g_srv_tx_ctx.rsp_capacity = (uint16_t)rsp_capacity; + g_srv_tx_ctx.rsp_size = 0; + g_srv_tx_ctx.request_pending = 1; + + rc = wh_Server_HandleRequestMessage(&g_server); + + if (rc == WH_ERROR_OK) { + /* clamp: dispatcher must honor rsp_capacity; defend against regression */ + if ((uint32_t)g_srv_tx_ctx.rsp_size > rsp_capacity) { + *rspSz = 0; + rc = WH_ERROR_ABORTED; + } + else { + *rspSz = (uint32_t)g_srv_tx_ctx.rsp_size; + } + } + else { + *rspSz = 0; + } + + g_srv_tx_ctx.req_buf = NULL; + g_srv_tx_ctx.req_size = 0; + g_srv_tx_ctx.rsp_buf = NULL; + g_srv_tx_ctx.rsp_capacity = 0; + g_srv_tx_ctx.request_pending = 0; + + return rc; +} + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/src/wolfhsm_flash_hal.c b/src/wolfhsm_flash_hal.c new file mode 100644 index 0000000000..13caab7b90 --- /dev/null +++ b/src/wolfhsm_flash_hal.c @@ -0,0 +1,250 @@ +/* wolfhsm_flash_hal.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include + +#include "hal.h" +#include "wolfboot/wolfhsm_flash_hal.h" + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/misc.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" + +#define WHFH5_SECTOR_SIZE (8U * 1024U) + +/* Sector-cached RMW, mirrors psa_store.c. H5 flash programs as 16-byte + * qwords ECC; one program per erase. wolfHSM 8-byte writes would clobber + * neighbours, so Program loads the sector, modifies, erases, rewrites. + * + * Single static cache: non-reentrant by design (one secure-side server, + * synchronous per-NSC dispatch, no threads or interrupts on this path). + * Residual plaintext may remain in cache for one programming cycle if a + * synchronous fault aborts hal_flash_write; disable debug in production. */ +static uint8_t cached_sector[WHFH5_SECTOR_SIZE]; + +static int whFlashH5_Init(void *context, const void *config) +{ + const whFlashH5Ctx *cfg = (const whFlashH5Ctx *)config; + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + + if (ctx == NULL || cfg == NULL) { + return WH_ERROR_BADARGS; + } + + if (cfg->base == 0U || cfg->size == 0U || cfg->partition_size == 0U || + (cfg->base % WHFH5_SECTOR_SIZE) != 0U || + (cfg->size % WHFH5_SECTOR_SIZE) != 0U || + (cfg->partition_size % WHFH5_SECTOR_SIZE) != 0U || + cfg->partition_size > cfg->size / 2U) { + return WH_ERROR_BADARGS; + } + + *ctx = *cfg; + return WH_ERROR_OK; +} + +static int whFlashH5_Cleanup(void *context) +{ + if (context == NULL) { + return WH_ERROR_BADARGS; + } + return WH_ERROR_OK; +} + +static uint32_t whFlashH5_PartitionSize(void *context) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + return (ctx == NULL) ? 0U : ctx->partition_size; +} + +static int whFlashH5_WriteLock(void *context, uint32_t offset, uint32_t size) +{ + (void)context; + (void)offset; + (void)size; + return WH_ERROR_OK; +} + +static int whFlashH5_WriteUnlock(void *context, uint32_t offset, uint32_t size) +{ + (void)context; + (void)offset; + (void)size; + return WH_ERROR_OK; +} + +static int whFlashH5_Read(void *context, uint32_t offset, uint32_t size, + uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if (size > 0U) { + memcpy(data, (const uint8_t *)(ctx->base + offset), size); + } + return WH_ERROR_OK; +} + +static int whFlashH5_Program(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + uint32_t written = 0U; + int hrc = 0; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if (size == 0U) { + return WH_ERROR_OK; + } + + /* defensive wipe in case a prior call faulted before the per-iter wipe */ + wc_ForceZero(cached_sector, sizeof(cached_sector)); + + hal_flash_unlock(); + while (written < size) { + uint32_t in_sector_off = (offset + written) % WHFH5_SECTOR_SIZE; + uint32_t sector_offset = (offset + written) - in_sector_off; + uint32_t chunk = WHFH5_SECTOR_SIZE - in_sector_off; + if (chunk > size - written) { + chunk = size - written; + } + + memcpy(cached_sector, + (const uint8_t *)(ctx->base + sector_offset), + WHFH5_SECTOR_SIZE); + memcpy(cached_sector + in_sector_off, data + written, chunk); + + hrc = hal_flash_erase(ctx->base + sector_offset, WHFH5_SECTOR_SIZE); + if (hrc == 0) { + hrc = hal_flash_write(ctx->base + sector_offset, cached_sector, + WHFH5_SECTOR_SIZE); + } + + /* Per-iteration wipe so a fault between sectors doesn't strand + * plaintext keystore bytes in the static cache. */ + wc_ForceZero(cached_sector, sizeof(cached_sector)); + + if (hrc != 0) { + break; + } + written += chunk; + } + hal_flash_lock(); + + return (hrc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} + +static int whFlashH5_Erase(void *context, uint32_t offset, uint32_t size) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + int rc; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + if (size == 0U) { + return WH_ERROR_OK; + } + if ((offset % WHFH5_SECTOR_SIZE) != 0U || + (size % WHFH5_SECTOR_SIZE) != 0U) { + return WH_ERROR_BADARGS; + } + + /* hal_flash_erase takes int; loop over sector-sized chunks so the cast + * stays well-defined regardless of how large size grows. */ + hal_flash_unlock(); + { + uint32_t erased = 0U; + rc = 0; + while (erased < size) { + rc = hal_flash_erase(ctx->base + offset + erased, + (int)WHFH5_SECTOR_SIZE); + if (rc != 0) { + break; + } + erased += WHFH5_SECTOR_SIZE; + } + } + hal_flash_lock(); + return (rc == 0) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} + +static int whFlashH5_Verify(void *context, uint32_t offset, uint32_t size, + const uint8_t *data) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + const uint8_t *p; + uint8_t acc = 0; + uint32_t i; + + if (ctx == NULL || (size != 0U && data == NULL)) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + /* constant-time compare; verified data may be key material */ + p = (const uint8_t *)(ctx->base + offset); + for (i = 0U; i < size; i++) { + acc |= (uint8_t)(p[i] ^ data[i]); + } + return (acc == 0U) ? WH_ERROR_OK : WH_ERROR_NOTVERIFIED; +} + +static int whFlashH5_BlankCheck(void *context, uint32_t offset, uint32_t size) +{ + whFlashH5Ctx *ctx = (whFlashH5Ctx *)context; + const uint8_t *p; + uint32_t i; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + if (offset > ctx->size || size > ctx->size - offset) { + return WH_ERROR_BADARGS; + } + p = (const uint8_t *)(ctx->base + offset); + for (i = 0U; i < size; i++) { + if (p[i] != 0xFFU) { + return WH_ERROR_NOTBLANK; + } + } + return WH_ERROR_OK; +} + +const whFlashCb whFlashH5_Cb = { + .Init = whFlashH5_Init, + .Cleanup = whFlashH5_Cleanup, + .PartitionSize = whFlashH5_PartitionSize, + .WriteLock = whFlashH5_WriteLock, + .WriteUnlock = whFlashH5_WriteUnlock, + .Read = whFlashH5_Read, + .Program = whFlashH5_Program, + .Erase = whFlashH5_Erase, + .Verify = whFlashH5_Verify, + .BlankCheck = whFlashH5_BlankCheck, +}; + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/Makefile b/test-app/Makefile index cddbb99a66..5a4a2feab6 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -5,12 +5,16 @@ WOLFBOOT_LIB_WOLFSSL?=../lib/wolfssl WOLFBOOT_LIB_WOLFTPM?=../lib/wolfTPM +WOLFBOOT_LIB_WOLFHSM?=../lib/wolfHSM WOLFSSL_LOCAL_OBJDIR?=wolfssl_obj WOLFTPM_LOCAL_OBJDIR?=wolftpm_obj +WOLFHSM_LOCAL_OBJDIR?=wolfhsm_obj vpath %.c $(WOLFBOOT_LIB_WOLFSSL) vpath %.S $(WOLFBOOT_LIB_WOLFSSL) vpath %.c $(WOLFBOOT_LIB_WOLFTPM) vpath %.S $(WOLFBOOT_LIB_WOLFTPM) +vpath %.c $(WOLFBOOT_LIB_WOLFHSM) +vpath %.S $(WOLFBOOT_LIB_WOLFHSM) TARGET?=none ARCH?=ARM @@ -109,6 +113,11 @@ ifeq ($(WOLFCRYPT_TZ_FWTPM),1) WOLFCRYPT_TZ_FWTPM=1 endif +ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + WOLFCRYPT_TZ=1 + WOLFCRYPT_TZ_WOLFHSM=1 +endif + # Setup default linker flags LDFLAGS+=-T $(LSCRIPT) -Wl,-gc-sections -Wl,-Map=image.map -nostartfiles @@ -367,6 +376,35 @@ ifeq ($(TZEN),1) $(WOLFTPM_LOCAL_OBJDIR)/%, $(WOLFTPM_APP_OBJS)) APP_OBJS+=$(sort $(WOLFTPM_APP_OBJS)) endif + ifeq ($(WOLFCRYPT_TZ_WOLFHSM),1) + CFLAGS+=-DWOLFCRYPT_TZ_WOLFHSM + CFLAGS+=-DWOLFHSM_CFG_ENABLE_CLIENT + CFLAGS+=-DWOLFHSM_CFG_COMM_DATA_LEN=1280 + CFLAGS+=-DWOLFHSM_CFG_PORT_ARMV8M_TZ_NSC + CFLAGS+=-DWOLFHSM_CFG_NO_SYS_TIME + ifeq ($(WOLFBOOT_TZ_TEST_NO_BKPT),1) + CFLAGS+=-DWOLFBOOT_TZ_TEST_NO_BKPT + endif + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)" + CFLAGS+=-I"$(WOLFBOOT_LIB_WOLFHSM)/port/armv8m-tz" + 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)) + endif WOLFCRYPT_APP_OBJS := $(patsubst $(WOLFBOOT_LIB_WOLFSSL)/%, \ $(WOLFSSL_LOCAL_OBJDIR)/%, $(WOLFCRYPT_APP_OBJS)) ifneq ($(WOLFCRYPT_TZ_PKCS11),1) @@ -1024,6 +1062,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)" @@ -1121,9 +1160,20 @@ $(WOLFTPM_LOCAL_OBJDIR)/%.o: %.S $(Q)mkdir -p $(dir $@) $(Q)$(CC) $(WOLFTPM_CFLAGS) -c $(OUTPUT_FLAG) $@ $< +$(WOLFHSM_LOCAL_OBJDIR)/%.o: %.c + @echo "\t[CC-$(ARCH)] $@" + $(Q)mkdir -p $(dir $@) + $(Q)$(CC) $(WOLFHSM_CFLAGS) -c $(OUTPUT_FLAG) $@ $< + +$(WOLFHSM_LOCAL_OBJDIR)/%.o: %.S + @echo "\t[AS-$(ARCH)] $@" + $(Q)mkdir -p $(dir $@) + $(Q)$(CC) $(WOLFHSM_CFLAGS) -c $(OUTPUT_FLAG) $@ $< + clean: $(Q)rm -f *.bin *.elf tags *.o $(LSCRIPT) $(APP_OBJS) wcs/*.o - $(Q)rm -rf $(WOLFSSL_LOCAL_OBJDIR) $(WOLFTPM_LOCAL_OBJDIR) + $(Q)rm -rf $(WOLFSSL_LOCAL_OBJDIR) $(WOLFTPM_LOCAL_OBJDIR) \ + $(WOLFHSM_LOCAL_OBJDIR) $(LSCRIPT): $(LSCRIPT_TEMPLATE) FORCE $(Q)printf "%d" $(WOLFBOOT_PARTITION_BOOT_ADDRESS) > .wolfboot-offset diff --git a/test-app/app_stm32h5.c b/test-app/app_stm32h5.c index 660718aa4a..adb89c682b 100644 --- a/test-app/app_stm32h5.c +++ b/test-app/app_stm32h5.c @@ -218,6 +218,9 @@ static int cmd_tpm_quote(const char *args); #ifdef WOLFBOOT_TZ_FWTPM static int cmd_fwtpm_test(const char *args); #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM +#include "wcs/wolfhsm_test.h" +#endif #define CMD_BUFFER_SIZE 256 @@ -1505,6 +1508,26 @@ void main(void) asm volatile ("bkpt #0x7e"); #endif +#ifdef WOLFCRYPT_TZ_WOLFHSM + ret = cmd_wolfhsm_test(NULL); +#ifdef WOLFBOOT_TZ_TEST_NO_BKPT + if (ret == WOLFHSM_TEST_FIRST_BOOT_OK || ret == WOLFHSM_TEST_SECOND_BOOT_OK) { + printf("WOLFHSM_TZ_TEST_PASS\r\n"); + while (1) { } + } else { + printf("WOLFHSM_TZ_TEST_FAIL\r\n"); + while (1) { } + } +#else + if (ret == WOLFHSM_TEST_FIRST_BOOT_OK) + asm volatile ("bkpt #0x7d"); + else if (ret == WOLFHSM_TEST_SECOND_BOOT_OK) + asm volatile ("bkpt #0x7f"); + else + asm volatile ("bkpt #0x7e"); +#endif +#endif + #if defined(WOLFBOOT_ATTESTATION_TEST) && defined(WOLFCRYPT_TZ_PSA) (void)run_attestation_test(); #endif diff --git a/test-app/wcs/user_settings.h b/test-app/wcs/user_settings.h index 1f71ff86a8..711b9052c6 100644 --- a/test-app/wcs/user_settings.h +++ b/test-app/wcs/user_settings.h @@ -54,6 +54,14 @@ extern int tolower(int c); #define MAX_CRYPTO_DEVID_CALLBACKS 2 #endif +/* wolfHSM (TZ engine, NS client side) */ +#ifdef WOLFCRYPT_TZ_WOLFHSM + #define WOLF_CRYPTO_CB + #undef HAVE_ANONYMOUS_INLINE_AGGREGATES + #define HAVE_ANONYMOUS_INLINE_AGGREGATES 1 + #define WOLFSSL_KEY_GEN +#endif + /* ECC */ @@ -156,7 +164,8 @@ extern int tolower(int c); #define HAVE_PKCS8 #define HAVE_PKCS12 -#if defined(SECURE_PKCS11) || defined(WOLFBOOT_TZ_FWTPM) +#if defined(SECURE_PKCS11) || defined(WOLFBOOT_TZ_FWTPM) || \ + defined(WOLFCRYPT_TZ_WOLFHSM) static inline int wcs_cmse_get_random(unsigned char* output, int sz) { diff --git a/test-app/wcs/wolfhsm_stub.c b/test-app/wcs/wolfhsm_stub.c new file mode 100644 index 0000000000..36e3d3a4c5 --- /dev/null +++ b/test-app/wcs/wolfhsm_stub.c @@ -0,0 +1,49 @@ +/* wolfhsm_stub.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +/* + * Non-secure side static buffers + transport context for the wolfHSM TZ + * NSC bridge. The transport callback table itself lives in the wolfHSM + * port file (port/armv8m-tz/wh_transport_nsc.c); this stub just + * provides the singleton context it operates on. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include + +#include "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 new file mode 100644 index 0000000000..19953d3c06 --- /dev/null +++ b/test-app/wcs/wolfhsm_test.c @@ -0,0 +1,77 @@ +/* wolfhsm_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifdef WOLFCRYPT_TZ_WOLFHSM + +#include +#include +#include + +#include "wolfhsm_test.h" + +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_error.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 + +int cmd_wolfhsm_test(const char *args) +{ + static const whTransportNscClientConfig nsc_cfg = { 0 }; + whCommClientConfig comm_cfg; + whClientConfig cfg; + 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; + + /* 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) { + printf("wolfHSM ClientServerClientConfig FAILED rc=%d\r\n", rc); + return WOLFHSM_TEST_FAIL; + } + +#ifndef WOLFHSM_CFG_NO_CRYPTO + rc = whTest_CryptoClientConfig(&cfg); + if (rc != 0) { + printf("wolfHSM CryptoClientConfig FAILED rc=%d\r\n", rc); + return WOLFHSM_TEST_FAIL; + } +#endif + + printf("wolfHSM whTest_ClientConfig PASSED\r\n"); + return WOLFHSM_TEST_FIRST_BOOT_OK; +} + +#endif /* WOLFCRYPT_TZ_WOLFHSM */ diff --git a/test-app/wcs/wolfhsm_test.h b/test-app/wcs/wolfhsm_test.h new file mode 100644 index 0000000000..f84b15da8f --- /dev/null +++ b/test-app/wcs/wolfhsm_test.h @@ -0,0 +1,19 @@ +/* wolfhsm_test.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + */ + +#ifndef WOLFBOOT_TEST_WOLFHSM_H +#define WOLFBOOT_TEST_WOLFHSM_H + +enum wolfhsm_test_result { + WOLFHSM_TEST_FAIL = -1, + WOLFHSM_TEST_FIRST_BOOT_OK = 1, + WOLFHSM_TEST_SECOND_BOOT_OK = 2 +}; + +int cmd_wolfhsm_test(const char *args); + +#endif /* WOLFBOOT_TEST_WOLFHSM_H */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index ee0b6398e4..b29c12d574 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -10,6 +10,7 @@ WOLFBOOT_LIB_WOLFSSL?=../../lib/wolfssl WOLFBOOT_LIB_WOLFPKCS11?=../../lib/wolfPKCS11 WOLFBOOT_LIB_WOLFPSA?=../../lib/wolfPSA WOLFBOOT_LIB_WOLFTPM?=../../lib/wolfTPM +WOLFBOOT_LIB_WOLFHSM?=../../lib/wolfHSM ifeq ($(wildcard $(WOLFBOOT_LIB_WOLFPSA)),) WOLFBOOT_LIB_WOLFPSA=../../../external-libs/wolfPSA @@ -17,12 +18,16 @@ endif ifeq ($(wildcard $(WOLFBOOT_LIB_WOLFTPM)),) WOLFBOOT_LIB_WOLFTPM=../../../external-libs/wolfTPM endif +ifeq ($(wildcard $(WOLFBOOT_LIB_WOLFHSM)),) +WOLFBOOT_LIB_WOLFHSM=../../../external-libs/wolfHSM +endif # Convert to absolute paths for standalone usage WOLFBOOT_LIB_WOLFSSL:=$(abspath $(WOLFBOOT_LIB_WOLFSSL)) WOLFBOOT_LIB_WOLFPKCS11:=$(abspath $(WOLFBOOT_LIB_WOLFPKCS11)) WOLFBOOT_LIB_WOLFPSA:=$(abspath $(WOLFBOOT_LIB_WOLFPSA)) WOLFBOOT_LIB_WOLFTPM:=$(abspath $(WOLFBOOT_LIB_WOLFTPM)) +WOLFBOOT_LIB_WOLFHSM:=$(abspath $(WOLFBOOT_LIB_WOLFHSM)) CFLAGS=-I. -I../../src -I../../include -I$(WOLFBOOT_LIB_WOLFSSL) CFLAGS+=-g -ggdb @@ -51,7 +56,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-enc-nvm-flagshome unit-delta unit-gzip unit-update-flash unit-update-flash-delta \ unit-update-flash-hook \ unit-update-flash-self-update \ - unit-update-flash-enc unit-update-ram unit-update-ram-nofixed unit-pkcs11_store unit-psa_store unit-disk \ + unit-update-flash-enc unit-update-ram unit-update-ram-nofixed unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \ unit-update-disk unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \ unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \ @@ -374,6 +379,12 @@ unit-pkcs11_store: ../../include/target.h unit-pkcs11_store.c unit-psa_store: ../../include/target.h unit-psa_store.c gcc -o $@ $(WOLFCRYPT_SRC) unit-psa_store.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) +unit-wolfhsm_flash_hal:CFLAGS+=-I$(WOLFBOOT_LIB_WOLFHSM) -DWOLFCRYPT_TZ_WOLFHSM -DWOLFHSM_CFG_NO_SYS_TIME -DMOCK_PARTITIONS +unit-wolfhsm_flash_hal:WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/misc.c +unit-wolfhsm_flash_hal: ../../include/target.h unit-wolfhsm_flash_hal.c + gcc -o $@ $(WOLFCRYPT_SRC) unit-wolfhsm_flash_hal.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) + gpt-sfdisk-test.h: truncate -s 131072 .gpt-tmp.img printf 'label: gpt\nfirst-lba: 34\nstart=34, size=67, name="boot"\nstart=101, size=100, name="rootfs"\n' \ diff --git a/tools/unit-tests/unit-wolfhsm_flash_hal.c b/tools/unit-tests/unit-wolfhsm_flash_hal.c new file mode 100644 index 0000000000..6f7b159881 --- /dev/null +++ b/tools/unit-tests/unit-wolfhsm_flash_hal.c @@ -0,0 +1,453 @@ +/* unit-wolfhsm_flash_hal.c + * + * Unit tests for the wolfHSM whFlashCb adapter (src/wolfhsm_flash_hal.c). + */ + +#include +#include +#include +#include +#include + +#include "user_settings.h" +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/misc.h" + +#include "hal.h" +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_flash.h" +#include "wolfboot/wolfhsm_flash_hal.h" + +#define MOCK_FLASH_BASE 0xCF000000U +#define MOCK_FLASH_SECTOR (8U * 1024U) +#define MOCK_FLASH_SECTORS 14U +#define MOCK_FLASH_SIZE (MOCK_FLASH_SECTORS * MOCK_FLASH_SECTOR) + +static int g_flash_locked = 1; +static int g_flash_write_fail = 0; +static int g_flash_erase_fail = 0; + +void hal_flash_unlock(void) { g_flash_locked = 0; } +void hal_flash_lock(void) { g_flash_locked = 1; } + +int hal_flash_erase(haladdr_t addr, int len) +{ + if (g_flash_locked || g_flash_erase_fail) { + return -1; + } + if (addr < MOCK_FLASH_BASE || + addr + (uintptr_t)len > MOCK_FLASH_BASE + MOCK_FLASH_SIZE) { + return -1; + } + memset((void *)addr, 0xFF, (size_t)len); + return 0; +} + +int hal_flash_write(haladdr_t addr, const uint8_t *data, int len) +{ + if (g_flash_locked || g_flash_write_fail) { + return -1; + } + if (addr < MOCK_FLASH_BASE || + addr + (uintptr_t)len > MOCK_FLASH_BASE + MOCK_FLASH_SIZE) { + return -1; + } + memcpy((void *)addr, data, (size_t)len); + return 0; +} + +#include "../../src/wolfhsm_flash_hal.c" + +static void mock_flash_init(void) +{ + /* Prefer MAP_FIXED_NOREPLACE so the kernel refuses to clobber an + * existing mapping at MOCK_FLASH_BASE (Linux >= 4.17). Older kernels + * fall back to plain MAP_FIXED. */ + int flags = MAP_PRIVATE | MAP_ANONYMOUS; +#ifdef MAP_FIXED_NOREPLACE + flags |= MAP_FIXED_NOREPLACE; +#else + flags |= MAP_FIXED; +#endif + void *p = mmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE, + PROT_READ | PROT_WRITE, flags, -1, 0); + ck_assert_ptr_eq(p, (void *)(uintptr_t)MOCK_FLASH_BASE); + memset((void *)(uintptr_t)MOCK_FLASH_BASE, 0xFF, MOCK_FLASH_SIZE); + g_flash_locked = 1; + g_flash_write_fail = 0; + g_flash_erase_fail = 0; +} + +static void mock_flash_fini(void) +{ + munmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE); +} + +START_TEST(test_init_rejects_null) +{ + whFlashH5Ctx ctx; + whFlashH5Ctx cfg; + + cfg.base = MOCK_FLASH_BASE; + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Init(NULL, &cfg), WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, NULL), WH_ERROR_BADARGS); +} +END_TEST + +START_TEST(test_init_rejects_bad_config) +{ + whFlashH5Ctx ctx; + whFlashH5Ctx cfg; + + cfg.base = 0; + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.base = MOCK_FLASH_BASE; + cfg.size = 0; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = 0; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.base = MOCK_FLASH_BASE + 4U; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.base = MOCK_FLASH_BASE; + cfg.size = MOCK_FLASH_SIZE + 4U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = 100U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); + + cfg.partition_size = MOCK_FLASH_SIZE; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS); +} +END_TEST + +START_TEST(test_init_accepts_valid) +{ + whFlashH5Ctx ctx; + whFlashH5Ctx cfg; + + cfg.base = MOCK_FLASH_BASE; + cfg.size = MOCK_FLASH_SIZE; + cfg.partition_size = MOCK_FLASH_SIZE / 2U; + ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_OK); + ck_assert_uint_eq(whFlashH5_Cb.PartitionSize(&ctx), MOCK_FLASH_SIZE / 2U); +} +END_TEST + +START_TEST(test_read_bounds) +{ + whFlashH5Ctx ctx; + uint8_t buf[16]; + + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, MOCK_FLASH_SIZE + 1U, 0U, buf), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, MOCK_FLASH_SIZE + 1U, buf), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, 0U, NULL), WH_ERROR_OK); +} +END_TEST + +START_TEST(test_program_single_partial_preserves_neighbours) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t readback[8]; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + memcpy(readback, (void *)(uintptr_t)MOCK_FLASH_BASE, sizeof(readback)); + ck_assert_mem_eq(readback, data, sizeof(data)); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[8], 0xFFU); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[MOCK_FLASH_SECTOR - 1U], + 0xFFU); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_program_crosses_sector_boundary) +{ + whFlashH5Ctx ctx; + const uint32_t off = MOCK_FLASH_SECTOR - 8U; + uint8_t data[16]; + uint8_t readback[16]; + int i; + + for (i = 0; i < (int)sizeof(data); i++) { + data[i] = (uint8_t)(0x10 + i); + } + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, off, sizeof(data), data), + WH_ERROR_OK); + memcpy(readback, (void *)(uintptr_t)(MOCK_FLASH_BASE + off), + sizeof(readback)); + ck_assert_mem_eq(readback, data, sizeof(data)); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off - 1U], 0xFFU); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off + 16U], 0xFFU); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_program_propagates_write_failure) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + g_flash_write_fail = 1; + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_ABORTED); + g_flash_write_fail = 0; + mock_flash_fini(); +} +END_TEST + +START_TEST(test_erase_alignment) +{ + whFlashH5Ctx ctx; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 100U, MOCK_FLASH_SECTOR), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 0U, 100U), WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_OK); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_verify) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t bad[8] = { 0 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, sizeof(bad), bad), + WH_ERROR_NOTVERIFIED); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_blank_check) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.BlankCheck(&ctx, 0U, MOCK_FLASH_SIZE), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.BlankCheck(&ctx, 0U, sizeof(data)), + WH_ERROR_NOTBLANK); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_cleanup) +{ + whFlashH5Ctx ctx; + + ck_assert_int_eq(whFlashH5_Cb.Cleanup(NULL), WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Cleanup(&ctx), WH_ERROR_OK); +} +END_TEST + +START_TEST(test_program_propagates_erase_failure) +{ + whFlashH5Ctx ctx; + uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + g_flash_erase_fail = 1; + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_ABORTED); + g_flash_erase_fail = 0; + mock_flash_fini(); +} +END_TEST + +START_TEST(test_read_returns_flash_contents) +{ + whFlashH5Ctx ctx; + uint8_t data[16]; + uint8_t buf[16]; + int i; + + for (i = 0; i < (int)sizeof(data); i++) { + data[i] = (uint8_t)(0xA0 + i); + } + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data), + WH_ERROR_OK); + memset(buf, 0, sizeof(buf)); + ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, sizeof(buf), buf), + WH_ERROR_OK); + ck_assert_mem_eq(buf, data, sizeof(data)); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_callbacks_reject_null_context) +{ + uint8_t data[8] = { 0 }; + + ck_assert_int_eq(whFlashH5_Cb.Read(NULL, 0U, sizeof(data), data), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Program(NULL, 0U, sizeof(data), data), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Erase(NULL, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Verify(NULL, 0U, sizeof(data), data), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.BlankCheck(NULL, 0U, sizeof(data)), + WH_ERROR_BADARGS); + ck_assert_uint_eq(whFlashH5_Cb.PartitionSize(NULL), 0U); +} +END_TEST + +START_TEST(test_callbacks_reject_null_data) +{ + whFlashH5Ctx ctx; + + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, 8U, NULL), + WH_ERROR_BADARGS); + ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, 8U, NULL), + WH_ERROR_BADARGS); +} +END_TEST + +START_TEST(test_program_spans_three_sectors) +{ + whFlashH5Ctx ctx; + const uint32_t off = MOCK_FLASH_SECTOR - 4U; + const uint32_t sz = (2U * MOCK_FLASH_SECTOR) + 8U; + uint8_t data[(2U * MOCK_FLASH_SECTOR) + 8U]; + uint8_t readback[sizeof(data)]; + uint32_t i; + + for (i = 0U; i < sizeof(data); i++) { + data[i] = (uint8_t)(i & 0xFFU); + } + mock_flash_init(); + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, off, sz, data), WH_ERROR_OK); + memcpy(readback, (void *)(uintptr_t)(MOCK_FLASH_BASE + off), sz); + ck_assert_mem_eq(readback, data, sz); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off - 1U], 0xFFU); + ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off + sz], 0xFFU); + mock_flash_fini(); +} +END_TEST + +START_TEST(test_writelock_writeunlock_noop) +{ + whFlashH5Ctx ctx; + + ctx.base = MOCK_FLASH_BASE; + ctx.size = MOCK_FLASH_SIZE; + ctx.partition_size = MOCK_FLASH_SIZE / 2U; + + ck_assert_int_eq(whFlashH5_Cb.WriteLock(&ctx, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_OK); + ck_assert_int_eq(whFlashH5_Cb.WriteUnlock(&ctx, 0U, MOCK_FLASH_SECTOR), + WH_ERROR_OK); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("wolfHSM-flash-hal"); + TCase *tc = tcase_create("flash-hal"); + + tcase_add_test(tc, test_init_rejects_null); + tcase_add_test(tc, test_init_rejects_bad_config); + tcase_add_test(tc, test_init_accepts_valid); + tcase_add_test(tc, test_read_bounds); + tcase_add_test(tc, test_program_single_partial_preserves_neighbours); + tcase_add_test(tc, test_program_crosses_sector_boundary); + tcase_add_test(tc, test_program_propagates_write_failure); + tcase_add_test(tc, test_erase_alignment); + tcase_add_test(tc, test_verify); + tcase_add_test(tc, test_blank_check); + tcase_add_test(tc, test_cleanup); + tcase_add_test(tc, test_program_propagates_erase_failure); + tcase_add_test(tc, test_read_returns_flash_contents); + tcase_add_test(tc, test_callbacks_reject_null_context); + tcase_add_test(tc, test_callbacks_reject_null_data); + tcase_add_test(tc, test_program_spans_three_sectors); + tcase_add_test(tc, test_writelock_writeunlock_noop); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = wolfboot_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +}