Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ jobs:
- name: Build and test
run: cd test && make clean && make -j WOLFSSL_DIR=../wolfssl && make run

# Build and test ARMv8-M TrustZone NSC bridge transport (port/armv8m-tz)
- name: Build and test ARMV8M_TZ_NSC ASAN
run: cd test && make clean && make -j ARMV8M_TZ_NSC=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Build and test standard build, with DMA and ASAN enabled
- name: Build and test DMA ASAN
run: cd test && make clean && make -j DMA=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run
Expand Down
10 changes: 10 additions & 0 deletions docs/src/chapter08.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ The distribution of this port is restricted by the vendor. Please contact suppo
- 1x 100MHz e200z0 PowerPC HSM core with NVM
- Crypto offload: TRNG, AES128

### ARMv8-M TrustZone (NSC bridge)

The `port/armv8m-tz` port provides a synchronous TrustZone non-secure-callable bridge transport for any ARMv8-M Cortex-M target (Cortex-M23 / M33 / M35P / M55 / M85). It is designed for deployments in which a secure-side image hosts a wolfHSM server and exposes it to the non-secure application through a single `cmse_nonsecure_entry` veneer (`wcs_wolfhsm_transmit`). The first integration is wolfBoot on STM32H5; see `wolfBoot/docs/wolfHSM.md` for the build, flash, and test recipe.

The port provides:
- Single-call NSC transport (no polling, no shared-memory ring): client `Send` invokes the host-supplied veneer inline and caches the response; client `Recv` consumes the cached response on the first call (subsequent calls return `WH_ERROR_NOTREADY` until the next `Send`).
- Server-side callbacks that consume the request the host's veneer parked in a static context and write the response back to the non-secure caller's buffer.

The transport is target-agnostic. Bringing it up on a new ARMv8-M part is a porting exercise on the host side only: provide the `cmse_nonsecure_entry` veneer that fronts `wcs_wolfhsm_transmit`, plus whatever flash/NVM adapter and server init the deployment needs.

### POSIX

The POSIX port provides multiple and fully functional implementations of different wolfHSM abstractions that can be used to better understand the exact functionality expected for different hardware abstractions.
Expand Down
237 changes: 237 additions & 0 deletions port/armv8m-tz/wh_transport_nsc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* port/armv8m-tz/wh_transport_nsc.c
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfHSM.
*
* wolfHSM 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.
*
* wolfHSM 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 wolfHSM. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wolfhsm/wh_settings.h"

#ifdef WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC

#include <stdint.h>
#include <string.h>

#include "wolfhsm/wh_comm.h"
#include "wolfhsm/wh_error.h"
#include "wh_transport_nsc.h"

/*
* Resolved on the non-secure side via the wolfBoot --cmse-implib import
* library; on the secure side the same symbol is provided by the host's
* NSC veneer (wolfBoot's src/wolfhsm_callable.c). The server callbacks
* below never call this; --gc-sections strips client-side code from the
* secure image.
*/
extern int wcs_wolfhsm_transmit(const uint8_t* cmd, uint32_t cmdSz,
uint8_t* rsp, uint32_t* rspSz);


/* ============================================================
* Non-secure (client) callbacks
* ============================================================ */

static int _NscClientInit(void* context, const void* config,
whCommSetConnectedCb connectcb, void* connectcb_arg)
{
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;

(void)config;

if (ctx == NULL) {
return WH_ERROR_BADARGS;
}

memset(ctx, 0, sizeof(*ctx));
ctx->initialized = 1;

/* Synchronous bridge: the secure side is always reachable once linked. */
if (connectcb != NULL) {
connectcb(connectcb_arg, WH_COMM_CONNECTED);
}
return WH_ERROR_OK;
}

static int _NscClientSend(void* context, uint16_t size, const void* data)
{
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;
uint32_t rspSz;
int rc;

if (ctx == NULL || data == NULL || ctx->initialized == 0U) {
return WH_ERROR_BADARGS;
}
if (size == 0U || size > WH_TRANSPORT_NSC_BUFFER_SIZE) {
return WH_ERROR_BADARGS;
}
/* prior response must be consumed before next Send */
if (ctx->last_rsp_size != 0U) {
return WH_ERROR_NOTREADY;
}

rspSz = (uint32_t)WH_TRANSPORT_NSC_BUFFER_SIZE;
rc = wcs_wolfhsm_transmit((const uint8_t*)data, (uint32_t)size,
ctx->rsp_buf, &rspSz);
if (rc != 0) {
ctx->last_rsp_size = 0;
/* propagate known wolfHSM error codes, collapse unknowns */
if (rc == WH_ERROR_BADARGS || rc == WH_ERROR_NOTREADY ||
rc == WH_ERROR_ABORTED) {
return rc;
}
return WH_ERROR_ABORTED;
Comment thread
aidangarske marked this conversation as resolved.
}
if (rspSz == 0U || rspSz > (uint32_t)WH_TRANSPORT_NSC_BUFFER_SIZE) {
ctx->last_rsp_size = 0;
return WH_ERROR_ABORTED;
}

ctx->last_rsp_size = (uint16_t)rspSz;
return WH_ERROR_OK;
}

static int _NscClientRecv(void* context, uint16_t* out_size, void* data)
{
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;

if (ctx == NULL || out_size == NULL || data == NULL ||
ctx->initialized == 0U) {
return WH_ERROR_BADARGS;
}
if (ctx->last_rsp_size == 0U) {
return WH_ERROR_NOTREADY;
}
/* out_size is in/out capacity; reject truncation, keep cached response */
if (*out_size < ctx->last_rsp_size) {
return WH_ERROR_BADARGS;
}

memcpy(data, ctx->rsp_buf, ctx->last_rsp_size);
*out_size = ctx->last_rsp_size;
ctx->last_rsp_size = 0;
return WH_ERROR_OK;
}
Comment thread
aidangarske marked this conversation as resolved.

static int _NscClientCleanup(void* context)
{
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;
if (ctx == NULL) {
return WH_ERROR_BADARGS;
}
ctx->initialized = 0;
return WH_ERROR_OK;
}

const whTransportClientCb whTransportNscClient_Cb = {
.Init = _NscClientInit,
.Send = _NscClientSend,
.Recv = _NscClientRecv,
.Cleanup = _NscClientCleanup,
};


/* ============================================================
* Secure-side (server) callbacks
*
* The host's NSC veneer populates req_buf/req_size/rsp_buf/rsp_capacity
* and sets request_pending = 1 before calling wh_Server_HandleRequestMessage.
* Recv hands the request to the dispatcher; Send writes the response back
* into rsp_buf and stores its size for the veneer to read.
* ============================================================ */

static int _NscServerInit(void* context, const void* config,
whCommSetConnectedCb connectcb, void* connectcb_arg)
{
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;

(void)config;

if (ctx == NULL) {
return WH_ERROR_BADARGS;
}

memset(ctx, 0, sizeof(*ctx));

if (connectcb != NULL) {
connectcb(connectcb_arg, WH_COMM_CONNECTED);
}
return WH_ERROR_OK;
}

static int _NscServerRecv(void* context, uint16_t* inout_size, void* data)
{
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;

if (ctx == NULL || inout_size == NULL || data == NULL) {
return WH_ERROR_BADARGS;
}
if (!ctx->request_pending || ctx->req_buf == NULL || ctx->req_size == 0U) {
return WH_ERROR_NOTREADY;
}
/* clear stale rsp_size up-front so every exit path leaves a clean state */
ctx->rsp_size = 0;

if (ctx->req_size > *inout_size) {
ctx->request_pending = 0;
return WH_ERROR_ABORTED;
}

memcpy(data, ctx->req_buf, ctx->req_size);
*inout_size = ctx->req_size;
ctx->request_pending = 0;
return WH_ERROR_OK;
}

static int _NscServerSend(void* context, uint16_t size, const void* data)
{
/* veneer is responsible for Recv/Send pairing; Send does not enforce it */
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;

if (ctx == NULL || data == NULL) {
return WH_ERROR_BADARGS;
}
if (size == 0U || size > ctx->rsp_capacity) {
return WH_ERROR_BADARGS;
}
if (ctx->rsp_buf == NULL) {
return WH_ERROR_ABORTED;
}

memcpy(ctx->rsp_buf, data, size);
ctx->rsp_size = size;
return WH_ERROR_OK;
}

static int _NscServerCleanup(void* context)
{
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;
if (ctx == NULL) {
return WH_ERROR_BADARGS;
}
/* clear stale NS pointers so they cannot survive reinit */
memset(ctx, 0, sizeof(*ctx));
return WH_ERROR_OK;
}

const whTransportServerCb whTransportNscServer_Cb = {
.Init = _NscServerInit,
.Recv = _NscServerRecv,
.Send = _NscServerSend,
.Cleanup = _NscServerCleanup,
};

#endif /* WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC */
91 changes: 91 additions & 0 deletions port/armv8m-tz/wh_transport_nsc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* port/armv8m-tz/wh_transport_nsc.h
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfHSM.
*
* wolfHSM 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.
*
* wolfHSM 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 wolfHSM. If not, see <http://www.gnu.org/licenses/>.
*/

/*
* Synchronous TrustZone NSC bridge transport for wolfHSM.
*
* The non-secure (client) side calls a single ARMv8-M Cortex-M
* cmse_nonsecure_entry veneer (`wcs_wolfhsm_transmit`) provided by the
* secure-side host. The veneer hands the request to the secure-side
* server context, runs `wh_Server_HandleRequestMessage` once inline,
* and returns the response in the same call. There is no polling,
* notify counter, or async producer/consumer — Send delivers the
* response, Recv just hands it back.
*
* The transport is target-agnostic across ARMv8-M TrustZone parts;
* the target-specific NSC veneer is provided by the host.
*/

#ifndef WH_TRANSPORT_NSC_H_
#define WH_TRANSPORT_NSC_H_

#include "wolfhsm/wh_settings.h"

#ifdef WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC

#include <stdint.h>
#include "wolfhsm/wh_comm.h"

#define WH_TRANSPORT_NSC_BUFFER_SIZE WH_COMM_MTU

/*
* Non-secure (client) context. Owns the response buffer in NS .bss.
* Not internally thread-safe.
*/
typedef struct {
uint8_t rsp_buf[WH_TRANSPORT_NSC_BUFFER_SIZE];
uint16_t last_rsp_size;
uint8_t initialized;
uint8_t WH_PAD[5]; /* trailing slack */
} whTransportNscClientContext;

/* Empty config; Init accepts NULL since there is nothing to read. */
typedef struct {
uint8_t WH_PAD[1];
} whTransportNscClientConfig;

/*
* Secure-side server context. Populated by the NSC veneer per call:
* before invoking `wh_Server_HandleRequestMessage` the host sets
* req_buf/req_size/rsp_buf/rsp_capacity; after the dispatcher returns,
* the host reads rsp_size to pass back to the non-secure caller.
*/
typedef struct {
const uint8_t* req_buf;
uint8_t* rsp_buf;
uint16_t req_size;
uint16_t rsp_capacity;
uint16_t rsp_size; /* set by Send, read by veneer */
uint8_t request_pending; /* set by veneer, cleared by Recv */
uint8_t WH_PAD[1];
} whTransportNscServerContext;

typedef struct {
uint8_t WH_PAD[1];
} whTransportNscServerConfig;

/* Pre-populated tables; callbacks are file-local in wh_transport_nsc.c */
extern const whTransportClientCb whTransportNscClient_Cb;
extern const whTransportServerCb whTransportNscServer_Cb;

#endif /* WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC */

#endif /* WH_TRANSPORT_NSC_H_ */
8 changes: 8 additions & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ ifeq ($(AUTH),1)
DEF += -DWOLFHSM_CFG_ENABLE_AUTHENTICATION
endif

# Build the ARMv8-M TrustZone NSC bridge transport plus its host unit test
ifeq ($(ARMV8M_TZ_NSC),1)
DEF += -DWOLFHSM_CFG_PORT_ARMV8M_TZ_NSC
WOLFHSM_ARMV8M_TZ_DIR := $(WOLFHSM_DIR)/port/armv8m-tz
INC += -I$(WOLFHSM_ARMV8M_TZ_DIR)
SRC_C += $(wildcard $(WOLFHSM_ARMV8M_TZ_DIR)/*.c)
endif

## Project defines
# Option to build wolfcrypt tests
ifeq ($(TESTWOLFCRYPT),1)
Expand Down
7 changes: 7 additions & 0 deletions test/wh_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
#include "wh_test_timeout.h"
#include "wh_test_dma.h"
#include "wh_test_keystore_reqsize.h"
#ifdef WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC
#include "wh_test_transport_nsc.h"
#endif
#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
#include "wh_test_auth.h"
#endif /* WOLFHSM_CFG_ENABLE_AUTHENTICATION */
Expand Down Expand Up @@ -105,6 +108,10 @@ int whTest_Unit(void)
WH_TEST_ASSERT(0 == whTest_Comm());
WH_TEST_ASSERT(0 == whTest_ClientServer());

#ifdef WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC
WH_TEST_ASSERT(0 == whTest_TransportNsc());
#endif

#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
/* Auth tests */
WH_TEST_ASSERT(0 == whTest_AuthMEM());
Expand Down
Loading
Loading