Skip to content

Commit ae382bf

Browse files
committed
port/armv8m-tz: NSC bridge transport for ARMv8-M TrustZone
1 parent df426ad commit ae382bf

6 files changed

Lines changed: 476 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Build wolfBoot against this wolfHSM checkout and run the STM32H5
2+
# TrustZone demo under the m33mu emulator. Guards against transport
3+
# changes here breaking the wolfBoot integration.
4+
5+
name: wolfBoot TrustZone integration (m33mu)
6+
7+
on:
8+
pull_request:
9+
paths:
10+
- 'port/armv8m-tz/**'
11+
- '.github/workflows/wolfboot-tz-integration.yml'
12+
workflow_dispatch:
13+
14+
jobs:
15+
wolfboot-stm32h5-tz-wolfhsm:
16+
runs-on: ubuntu-latest
17+
# Marked non-required while m33mu upstream fixes are pending.
18+
continue-on-error: true
19+
container:
20+
image: ghcr.io/wolfssl/wolfboot-ci-m33mu:latest
21+
22+
steps:
23+
- name: Checkout this wolfHSM PR
24+
uses: actions/checkout@v4
25+
with:
26+
path: wolfHSM
27+
28+
- name: Checkout wolfBoot PR #769 head
29+
# Transitional: until wolfBoot PR #769 lands in master, the
30+
# port wrapper and the wolfHSM CI lane live only on that PR.
31+
# Switch back to `ref: master` after PR #769 merges.
32+
uses: actions/checkout@v4
33+
with:
34+
repository: wolfSSL/wolfBoot
35+
ref: refs/pull/769/head
36+
path: wolfBoot
37+
# Skip lib/wolfHSM submodule init - we replace it with this
38+
# PR's checkout. Other submodules still need to come down.
39+
submodules: false
40+
41+
- name: Initialise wolfBoot submodules (except lib/wolfHSM)
42+
working-directory: wolfBoot
43+
run: |
44+
git config -f .gitmodules submodule.lib/wolfHSM.update none
45+
git submodule update --init --recursive
46+
47+
- name: Point lib/wolfHSM at this PR
48+
working-directory: wolfBoot
49+
run: |
50+
rm -rf lib/wolfHSM
51+
ln -s "$GITHUB_WORKSPACE/wolfHSM" lib/wolfHSM
52+
ls -l lib/wolfHSM
53+
54+
- name: Build wolfBoot STM32H5 TZ demo
55+
working-directory: wolfBoot/port/stmicro/stm32h5-tz-wolfhsm
56+
run: make
57+
58+
- name: Run wolfHSM whTest_ClientConfig under m33mu
59+
working-directory: wolfBoot/port/stmicro/stm32h5-tz-wolfhsm
60+
run: |
61+
set -o pipefail
62+
mkdir -p /tmp/m33mu-wolfhsm-persist
63+
cp out/wolfboot.bin out/image_v1_signed.bin /tmp/m33mu-wolfhsm-persist/
64+
cd /tmp/m33mu-wolfhsm-persist
65+
# Long timeout: software RSA and ML-DSA (and their async
66+
# paths) are slow under the emulator; real silicon is faster.
67+
m33mu wolfboot.bin image_v1_signed.bin:0x60000 \
68+
--persist --uart-stdout --timeout 1800 \
69+
--expect-bkpt 0x7d --quit-on-faults \
70+
2>&1 | tee /tmp/m33mu-wolfhsm.log
71+
72+
- name: Verify wolfHSM whTest_ClientConfig
73+
run: |
74+
# Final success marker - the test itself reports PASSED.
75+
grep -q "wolfHSM whTest_ClientConfig PASSED" /tmp/m33mu-wolfhsm.log
76+
# Spot-check that the major test phases each landed.
77+
grep -q "RNG DEVID=0x5748534D SUCCESS" /tmp/m33mu-wolfhsm.log
78+
grep -q "AES GCM DEVID=0x5748534D SUCCESS" /tmp/m33mu-wolfhsm.log
79+
grep -q "RSA SUCCESS" /tmp/m33mu-wolfhsm.log
80+
grep -q "ECC ephemeral ECDH SUCCESS" /tmp/m33mu-wolfhsm.log
81+
grep -q "SHA256 DEVID=0x5748534D SUCCESS" /tmp/m33mu-wolfhsm.log
82+
grep -q "HKDF SUCCESS" /tmp/m33mu-wolfhsm.log
83+
# App reached the success BKPT and m33mu trapped on it.
84+
grep -q "\\[BKPT\\] imm=0x7d" /tmp/m33mu-wolfhsm.log
85+
grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-wolfhsm.log
86+
87+
- name: Archive emulator log on failure
88+
if: failure()
89+
uses: actions/upload-artifact@v4
90+
with:
91+
name: m33mu-wolfhsm.log
92+
path: /tmp/m33mu-wolfhsm.log

docs/src/5-Features.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ wolfHSM ships with several reference transports that between them cover the most
469469
- **POSIX TCP transport** (`port/posix/posix_transport_tcp.h`): carries the packet exchange over a length-prefixed TCP stream. The server listens on a configured port and the client connects to it. This is the default transport for the POSIX server example and the recommended development transport, because the client and server can live in separate host processes — or on separate machines — without any additional platform integration.
470470
- **POSIX shared memory transport** (`port/posix/posix_transport_shm.h`): hosts the same request/response buffer layout used by the memory buffer transport in a named POSIX shared memory object, with optional space for DMA-style buffers alongside it. This lets the client and server run as independent processes on the same host while exercising the shared-memory code paths a multi-core SoC would use in production.
471471
- **TLS-over-TCP transport** (`port/posix/posix_transport_tls.h`): wraps the POSIX TCP transport in a wolfSSL-secured channel, with support for certificate-based authentication and PSK. It is intended for deployments where the client and server are physically separated and the link between them cannot be trusted. The packet framing above the TLS session is identical to the plain TCP transport, so higher-level code does not change between the two.
472+
- **ARMv8-M TrustZone NSC bridge transport** (`port/armv8m-tz/wh_transport_nsc.h`): a synchronous transport for ARMv8-M Cortex-M parts where the server runs in the secure world and clients run in the non-secure world. The non-secure client `Send` calls a single `cmse_nonsecure_entry` veneer (`wcs_wolfhsm_transmit`) the integrator provides; that veneer hands the request to the secure-side server inline and returns the response in the same call, which `Recv` then yields. There is no polling or shared-memory ring. The transport is target-agnostic across ARMv8-M parts; the veneer, flash/NVM adapter, and server init are supplied by the secure-side integration. The reference integration is the wolfBoot STM32H5 port.
472473
473474
Beyond the reference transports, platform ports for embedded targets typically supply hardware-specific transports — silicon mailboxes, interrupt-driven inter-core channels, vendor IPC blocks — by implementing the same callback interface. The comm-layer contract is purely "deliver one packet, in order," so a transport need only marshal bytes between the two sides.
474475

port/armv8m-tz/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# ARMv8-M TrustZone NSC bridge transport
2+
3+
A synchronous wolfHSM transport for ARMv8-M TrustZone targets that
4+
bridges a non-secure client to a secure-world server through a single
5+
Non-Secure Callable (NSC) veneer. See `wh_transport_nsc.h` for the C
6+
API and `docs/src/chapter08.md` for the narrative description.
7+
8+
## How it works
9+
10+
Client and server share a single secure-callable function. The
11+
non-secure side packs a wolfHSM request into a buffer, calls the
12+
veneer, and waits for the veneer to return with the response written
13+
into a second buffer. The secure side runs the wolfHSM server in the
14+
same process; there is no separate task, no IRQ, and no shared-memory
15+
ring — just a function call across the security boundary.
16+
17+
## Host-side veneer contract
18+
19+
The integrator provides one function with this exact shape:
20+
21+
```c
22+
int wcs_wolfhsm_transmit(const uint8_t *cmd, uint32_t cmdSz,
23+
uint8_t *rsp, uint32_t *rspSz);
24+
```
25+
26+
declared `cmse_nonsecure_entry` from the secure side. On entry,
27+
`cmd[0..cmdSz)` holds the request and `*rspSz` is the maximum response
28+
size the client can accept; on return the function writes the
29+
response into `rsp[0..*rspSz)` and updates `*rspSz` to the actual
30+
size. Return value follows wolfHSM's `WH_ERROR_*` convention.
31+
32+
The veneer must not block on anything outside the secure server; the
33+
non-secure side treats it as a synchronous call.
34+
35+
## Known integrations
36+
37+
- **wolfBoot STM32H5 demo app** at
38+
[`port/stmicro/stm32h5-tz-wolfhsm/`](https://github.com/wolfSSL/wolfBoot/tree/main/port/stmicro/stm32h5-tz-wolfhsm)
39+
in wolfBoot. Reference integration on a NUCLEO-H563ZI;
40+
verified end-to-end on real silicon and on the m33mu emulator.
41+
42+
To add a new integration, copy the veneer skeleton from wolfBoot's
43+
demo, wire `wh_transport_nsc.h` into the non-secure client init, and
44+
write a `whServerCb` table for the secure server.

port/armv8m-tz/wh_transport_nsc.c

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* port/armv8m-tz/wh_transport_nsc.c
3+
*
4+
* Copyright (C) 2026 wolfSSL Inc.
5+
*
6+
* This file is part of wolfHSM.
7+
*
8+
* wolfHSM is free software; you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation; either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* wolfHSM is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with wolfHSM. If not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
22+
#include "wolfhsm/wh_settings.h"
23+
24+
#ifdef WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC
25+
26+
#include <stdint.h>
27+
#include <string.h>
28+
29+
#include "wolfhsm/wh_comm.h"
30+
#include "wolfhsm/wh_error.h"
31+
#include "wh_transport_nsc.h"
32+
33+
/*
34+
* Resolved on the non-secure side via the wolfBoot --cmse-implib import
35+
* library; on the secure side the same symbol is provided by the host's
36+
* NSC veneer (wolfBoot's src/wolfhsm_callable.c). The server callbacks
37+
* below never call this; --gc-sections strips client-side code from the
38+
* secure image.
39+
*/
40+
extern int wcs_wolfhsm_transmit(const uint8_t* cmd, uint32_t cmdSz,
41+
uint8_t* rsp, uint32_t* rspSz);
42+
43+
44+
/* ============================================================
45+
* Non-secure (client) callbacks
46+
* ============================================================ */
47+
48+
static int _NscClientInit(void* context, const void* config,
49+
whCommSetConnectedCb connectcb, void* connectcb_arg)
50+
{
51+
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;
52+
53+
(void)config;
54+
55+
if (ctx == NULL) {
56+
return WH_ERROR_BADARGS;
57+
}
58+
59+
memset(ctx, 0, sizeof(*ctx));
60+
ctx->initialized = 1;
61+
62+
/* Synchronous bridge: the secure side is always reachable once linked. */
63+
if (connectcb != NULL) {
64+
connectcb(connectcb_arg, WH_COMM_CONNECTED);
65+
}
66+
return WH_ERROR_OK;
67+
}
68+
69+
static int _NscClientSend(void* context, uint16_t size, const void* data)
70+
{
71+
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;
72+
uint32_t rspSz;
73+
int rc;
74+
75+
if (ctx == NULL || data == NULL || ctx->initialized == 0U) {
76+
return WH_ERROR_BADARGS;
77+
}
78+
if (size == 0U || size > WH_TRANSPORT_NSC_BUFFER_SIZE) {
79+
return WH_ERROR_BADARGS;
80+
}
81+
/* prior response must be consumed before next Send */
82+
if (ctx->last_rsp_size != 0U) {
83+
return WH_ERROR_NOTREADY;
84+
}
85+
86+
rspSz = (uint32_t)WH_TRANSPORT_NSC_BUFFER_SIZE;
87+
rc = wcs_wolfhsm_transmit((const uint8_t*)data, (uint32_t)size,
88+
ctx->rsp_buf, &rspSz);
89+
if (rc != 0) {
90+
ctx->last_rsp_size = 0;
91+
/* propagate known wolfHSM error codes, collapse unknowns */
92+
if (rc == WH_ERROR_BADARGS || rc == WH_ERROR_NOTREADY ||
93+
rc == WH_ERROR_ABORTED) {
94+
return rc;
95+
}
96+
return WH_ERROR_ABORTED;
97+
}
98+
if (rspSz == 0U || rspSz > (uint32_t)WH_TRANSPORT_NSC_BUFFER_SIZE) {
99+
ctx->last_rsp_size = 0;
100+
return WH_ERROR_ABORTED;
101+
}
102+
103+
ctx->last_rsp_size = (uint16_t)rspSz;
104+
return WH_ERROR_OK;
105+
}
106+
107+
static int _NscClientRecv(void* context, uint16_t* out_size, void* data)
108+
{
109+
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;
110+
111+
if (ctx == NULL || out_size == NULL || data == NULL ||
112+
ctx->initialized == 0U) {
113+
return WH_ERROR_BADARGS;
114+
}
115+
if (ctx->last_rsp_size == 0U) {
116+
return WH_ERROR_NOTREADY;
117+
}
118+
/* out_size is in/out capacity; reject truncation, keep cached response */
119+
if (*out_size < ctx->last_rsp_size) {
120+
return WH_ERROR_BADARGS;
121+
}
122+
123+
memcpy(data, ctx->rsp_buf, ctx->last_rsp_size);
124+
*out_size = ctx->last_rsp_size;
125+
ctx->last_rsp_size = 0;
126+
return WH_ERROR_OK;
127+
}
128+
129+
static int _NscClientCleanup(void* context)
130+
{
131+
whTransportNscClientContext* ctx = (whTransportNscClientContext*)context;
132+
if (ctx == NULL) {
133+
return WH_ERROR_BADARGS;
134+
}
135+
ctx->initialized = 0;
136+
return WH_ERROR_OK;
137+
}
138+
139+
const whTransportClientCb whTransportNscClient_Cb = {
140+
.Init = _NscClientInit,
141+
.Send = _NscClientSend,
142+
.Recv = _NscClientRecv,
143+
.Cleanup = _NscClientCleanup,
144+
};
145+
146+
147+
/* ============================================================
148+
* Secure-side (server) callbacks
149+
*
150+
* The host's NSC veneer populates req_buf/req_size/rsp_buf/rsp_capacity
151+
* and sets request_pending = 1 before calling wh_Server_HandleRequestMessage.
152+
* Recv hands the request to the dispatcher; Send writes the response back
153+
* into rsp_buf and stores its size for the veneer to read.
154+
* ============================================================ */
155+
156+
static int _NscServerInit(void* context, const void* config,
157+
whCommSetConnectedCb connectcb, void* connectcb_arg)
158+
{
159+
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;
160+
161+
(void)config;
162+
163+
if (ctx == NULL) {
164+
return WH_ERROR_BADARGS;
165+
}
166+
167+
memset(ctx, 0, sizeof(*ctx));
168+
169+
if (connectcb != NULL) {
170+
connectcb(connectcb_arg, WH_COMM_CONNECTED);
171+
}
172+
return WH_ERROR_OK;
173+
}
174+
175+
static int _NscServerRecv(void* context, uint16_t* inout_size, void* data)
176+
{
177+
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;
178+
179+
if (ctx == NULL || inout_size == NULL || data == NULL) {
180+
return WH_ERROR_BADARGS;
181+
}
182+
if (!ctx->request_pending || ctx->req_buf == NULL || ctx->req_size == 0U) {
183+
return WH_ERROR_NOTREADY;
184+
}
185+
/* clear stale rsp_size up-front so every exit path leaves a clean state */
186+
ctx->rsp_size = 0;
187+
188+
if (ctx->req_size > *inout_size) {
189+
ctx->request_pending = 0;
190+
return WH_ERROR_ABORTED;
191+
}
192+
193+
memcpy(data, ctx->req_buf, ctx->req_size);
194+
*inout_size = ctx->req_size;
195+
ctx->request_pending = 0;
196+
return WH_ERROR_OK;
197+
}
198+
199+
static int _NscServerSend(void* context, uint16_t size, const void* data)
200+
{
201+
/* veneer is responsible for Recv/Send pairing; Send does not enforce it */
202+
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;
203+
204+
if (ctx == NULL || data == NULL) {
205+
return WH_ERROR_BADARGS;
206+
}
207+
if (size == 0U || size > ctx->rsp_capacity) {
208+
return WH_ERROR_BADARGS;
209+
}
210+
if (ctx->rsp_buf == NULL) {
211+
return WH_ERROR_ABORTED;
212+
}
213+
214+
memcpy(ctx->rsp_buf, data, size);
215+
ctx->rsp_size = size;
216+
return WH_ERROR_OK;
217+
}
218+
219+
static int _NscServerCleanup(void* context)
220+
{
221+
whTransportNscServerContext* ctx = (whTransportNscServerContext*)context;
222+
if (ctx == NULL) {
223+
return WH_ERROR_BADARGS;
224+
}
225+
/* clear stale NS pointers so they cannot survive reinit */
226+
memset(ctx, 0, sizeof(*ctx));
227+
return WH_ERROR_OK;
228+
}
229+
230+
const whTransportServerCb whTransportNscServer_Cb = {
231+
.Init = _NscServerInit,
232+
.Recv = _NscServerRecv,
233+
.Send = _NscServerSend,
234+
.Cleanup = _NscServerCleanup,
235+
};
236+
237+
#endif /* WOLFHSM_CFG_PORT_ARMV8M_TZ_NSC */

0 commit comments

Comments
 (0)