-
Notifications
You must be signed in to change notification settings - Fork 2
Crypto Engines
wolfTrust provides two Secure crypto engines behind the same Platform Security Architecture (PSA) Firmware Framework for M (FF-M) service boundary. The native crypto engine is the default. The wolfHSM engine is an opt-in add-on for deployments that need the wolfHSM client/server key model or its external-HSM integration path.
The engine choice does not change the actual Non-secure-to-Secure boundary. Both builds use the same five CMSE veneers, generated manifest, service IDs, SPM-owned caller identity, copied IOVEC rules, isolation bands, storage services, attestation service, firmware-update service, and Secure Partition recovery path. The STM32H563 manifest requests isolation profile 3 in both builds. That value is wolfTrust's validated policy profile, not proof of independent TF-M Level 3 code and data isolation.
| Native crypto engine | wolfHSM engine | |
|---|---|---|
| Selector |
WT_ENGINE=native (default) |
WT_ENGINE=hsm |
| Guest PSA Crypto | wolfPSA and wolfCrypt execute in the Non-secure guest; DRBG seed requests cross into the Secure vault | wolfPSA routes supported operations through the wolfHSM client and SERVICE_HSM to a per-guest Secure wolfHSM server |
| Secure key service | Native request format dispatches wolfCrypt directly; explicitly vault-backed keys are NVM objects | wolfHSM request format dispatches the wolfHSM server and its keystore |
| Guest volatile PSA keys | Held in the guest's Non-secure memory | Keys for supported offloaded operations are held by the Secure wolfHSM server |
| Persistent Secure keys | Native vault objects marked SENSITIVE and NONEXPORTABLE
|
wolfHSM server-keystore objects with wolfHSM key policy |
| Attestation IAK | Vault-backed P-256 key used directly by wolfCrypt | Committed wolfHSM server-keystore key |
| External HSM path | Not provided by this engine | Available through the wolfHSM server model when a deployment configures a backend; the reference build uses software wolfCrypt |
| Secure-image footprint | Lower | Adds the wolfHSM protocol, server, per-guest contexts, and per-guest server stacks |
The native engine is not a mode in which every guest PSA key automatically moves into the Secure vault. In the reference guests, ordinary wolfPSA operations and volatile keys remain local to the Non-secure guest. The native wire is a separate explicit interface for vault-backed key operations. Secure services such as attestation use that vault backend directly.
SERVICE_HSM keeps its existing name and SID in both builds. The service
copies one request into Secure memory, obtains the caller identity stamped by
the SPM, and passes the opaque packet to the engine selected at build time:
Non-secure guest
|
| psa_connect / psa_call / psa_close
v
five WolfTrust_FFM_* CMSE veneers
|
v
SPM: caller identity + manifest policy + copied IOVECs
|
v
SERVICE_HSM protocol-opaque relay
|
+-- WT_ENGINE=native --> native request --> wolfCrypt + vault key backend
|
`-- WT_ENGINE=hsm ----> wolfHSM packet --> per-guest wolfHSM server
The binding is the wt_hsm_relay_set_submit() call in
src/spm_partitions.c. It selects wt_native_submit() or
wt_hsm_relay_submit(). src/services/hsm_relay_service.c does not interpret
either protocol. It enforces the copied request and response bounds and passes
the SPM-stamped client ID to the selected backend.
The native engine links wolfCrypt and the shared flash-backed NVM object store, but not the wolfHSM server, communication layer, or message layer.
The reference guest configuration behaves as follows:
- wolfPSA and wolfCrypt execute locally in each Non-secure guest;
- wolfCrypt DRBG seed material comes from the Secure vault RNG through one
SERVICE_HSMcall; - ITS, Protected Storage, attestation, and firmware update continue to use their normal Secure services; and
- clients that need a vault-backed key can use the native request format explicitly.
Guest-created vault-backed P-256 and AES-256 key objects are indexed by the
SERVICE_HSM partition identity, the SPM-stamped client identity, and a
64-bit UID. They are stored with the wolfHSM NVM library's SENSITIVE and
NONEXPORTABLE flags. The storage face refuses key-flagged objects, checked
NVM reads reject non-exportable objects, and the native request format has no
private-key export operation. Private-key computations run in the Secure
key-vault domain and temporary plaintext key buffers are zeroized after use.
The attestation IAK is a separate fixed vault object used only by the
attestation path.
One request is a 24-byte wt_crypto_wire_req_t followed by an optional
payload. One response is a 32-bit PSA status followed by an optional payload.
Each complete request and response is bounded by the 384-byte
WT_HSM_RELAY_MSG_MAX copied buffer.
| Request field | Type | Meaning |
|---|---|---|
uid |
uint64_t |
Key UID for key operations |
op |
uint32_t |
Operation number |
usage |
uint32_t |
Key-usage bits, or requested length for RANDOM
|
key_type |
uint32_t |
Native P-256 or AES-256 key encoding |
reserved |
uint32_t |
Reserved; must be zero. The Secure parser rejects a nonzero value with PSA_ERROR_INVALID_ARGUMENT
|
| payload | bytes | Imported key, digest, signature, plaintext, ciphertext, or hash input as required by the operation |
The defined operations are key generate, import, public export, sign, verify,
encrypt, decrypt, and destroy, plus random generation and SHA-256 hashing.
Random responses are limited to 256 bytes per request. P-256 signatures use a
fixed 64-byte r || s form and public keys use the 65-byte uncompressed X9.63
form. AES-256 encrypt and decrypt use AES-GCM and return or consume
nonce || ciphertext || tag.
The structures are copied directly between the reference Cortex-M client and
Secure image. This is a target-local ABI, not a versioned network protocol.
The header and operation values are declared in
include/wolftrust/services/crypto_native.h.
wolfHSM is not removed in the native engine. Its self-contained flash-backed object store is linked in both engines and owns every persistent object: the vault, Internal Trusted Storage, Protected Storage, the firmware-update staging metadata, the anti-rollback version floors, and the native engine's vault key objects. Only wolfHSM's server, communication, and message layers are dropped in native. The store's on-flash format is identical in both engines, so persistent storage objects survive a switch between engines.
The Initial Attestation Key is the exception. The hsm engine keeps the IAK in the wolfHSM server keystore; the native engine keeps it as a vault key object. Neither engine migrates the other's, so switching engines on an already-provisioned device does not carry the attestation identity: a provisioning lifecycle mints a fresh IAK, and a SECURED device with no IAK for the running engine fails closed rather than adopting a new one. Choose the crypto engine at provisioning time and keep it fixed for the device's life.
| Source | Role |
|---|---|
wh_nvm.c |
Object-store API: add, read, metadata, destroy, and the access-policy checks (WRITE_ONCE, SENSITIVE, NONEXPORTABLE) |
wh_nvm_flash.c |
Log-structured object store implemented over a flash callback |
wh_flash_unit.c |
Program-unit-aligned read, program, erase, and blank-check helpers under the store |
wh_lock.c |
Serialization lock so the shared store is safe across the confined keystore partitions |
wh_utils.c |
Endian, constant-time compare, and force-zero helpers the store depends on |
wh_keyid.c |
Key-id namespace translation between client and server key identifiers |
These files carry no server, communication, message, or wolfCrypt dependency, so linking them costs only the store itself. Reusing the proven store rather than reimplementing it keeps the on-flash format stable and avoids re-testing a storage rewrite; the flash-backed object store is not where either engine's size difference lives.
Guest initialization completes wolfHSM's COMM INIT handshake before enabling
crypto requests. A failed handshake releases the client transport. Transient
connection failures use the existing initialization retry. When that retry is
active, a failed handshake restores its callback so the next crypto request
can retry without an explicit guest initialization call.
The wolfHSM engine links the wolfHSM client/server protocol and creates one
Secure server context for each configured guest. Guest wolfPSA calls use
wolfCrypt's crypto-callback path, the wolfHSM client serializes the request,
and the request crosses the same SERVICE_HSM FF-M door used by the native
engine.
The relay derives guest N from SPM client ID -(N + 1) and forces wolfHSM
server client ID N + 1. It rejects guest-facing wolfHSM NVM message groups,
so a guest cannot use the crypto door to read vault, rollback, storage-counter,
or attestation objects. The server keystore provides wolfHSM's key lifecycle,
namespace, and non-exportable-key behavior.
The reference engine runs wolfCrypt in the Secure image. Choosing
WT_ENGINE=hsm does not by itself select an external device; it retains the
wolfHSM server integration point for a deployment that supplies one.
Use the native crypto engine when:
- Secure flash or SRAM is constrained;
- guest-local wolfPSA and wolfCrypt execution is acceptable;
- the application only needs Secure entropy, the explicit vault-key interface, and the other wolfTrust Secure services; or
- the deployment does not need the wolfHSM client/server protocol.
Choose the wolfHSM engine (WT_ENGINE=hsm) over the native engine for one of
three reasons, in rough order of how often they apply:
- External hardware-HSM or secure-element offload. This is the main reason to enable it: the wolfHSM server can front an external device, so crypto and keys are delegated off-core rather than run by on-chip wolfCrypt. The native engine has no such path.
- The full wolfHSM server-keystore key-management model, when a deployment's tooling or provisioning flow already expects wolfHSM key lifecycle, namespaces, and non-exportable-key semantics as the server presents them.
-
Backward compatibility with existing Non-secure guest code built against
the wolfHSM client wire (
wh_Client_CryptoCb), where reworking the guest to the native request format is not worth it.
If none of those apply, prefer the native engine: it is smaller, keeps keys non-exportable in the vault, and needs no wolfHSM server. The wolfHSM engine adds this key-management and offload model on top of the same isolation boundary. The native engine is not a weaker FF-M gateway or a reduced-isolation build.
Build the Secure image with a fresh output directory for each engine:
make secure-image WT_ENGINE=native BUILD_DIR=build-native
make secure-image WT_ENGINE=hsm BUILD_DIR=build-hsmWT_ENGINE=native is the default, so an unset selector builds the native
engine. The legacy selector remains accepted:
| Legacy setting | Equivalent selector |
|---|---|
WT_ENGINE_HSM=0 |
WT_ENGINE=native |
WT_ENGINE_HSM=1 |
WT_ENGINE=hsm |
The Secure image and both guest images must use the same engine. See Building for the Zephyr and FreeRTOS guest settings.
These Secure-image measurements were reproduced on 2026-09-18 from the source
tree containing this page. The pinned dependency revisions and versions are
listed in
TF-M Compatibility. The builds ran on
wolf-prec5560 with arm-none-eabi-gcc 13.2.1, -Os, and the repository
defaults other than the engine and output directory:
make BUILD_DIR=build_size_native WT_ENGINE=native secure-image
make BUILD_DIR=build_size_hsm WT_ENGINE=hsm secure-image
arm-none-eabi-size build_size_native/wolftrust.elf \
build_size_hsm/wolftrust.elfFlash is text + data; static RAM is data + bss. Both images use the
STM32H563 reference manifest with isolation_profile set to 3 and include two
guests, ITS, Protected Storage, firmware update, vault services, and COSE
attestation. The wolfHSM image also contains two per-guest wolfHSM tasklet
stacks configured at 10 KiB each. wolfBoot and Non-secure guest images are not
included.
| Engine | text |
data |
bss |
Flash | Static RAM |
|---|---|---|---|---|---|
| Native | 86,140 bytes | 708 bytes | 26,545 bytes | 86,848 bytes | 27,253 bytes |
| wolfHSM | 105,712 bytes | 720 bytes | 58,353 bytes | 106,432 bytes | 59,073 bytes |
| wolfHSM overhead | 19,572 bytes | 12 bytes | 31,808 bytes | 19,584 bytes | 31,820 bytes |
With the default WT_MAX_GUESTS=2, the wolfHSM engine adds one server tasklet
stack slot per guest:
2 * (10,240-byte stack + 256-byte underflow guard) = 20,992 bytes
The stack payload is therefore 20,480 bytes and the guards add 512 bytes. The
linked wolfHSM image reports g_co_stack_slots as 0x5200 bytes, matching the
calculation. A one-guest build allocates one 10,496-byte slot. The remaining
10,828 bytes of the 31,820-byte static-RAM difference are server, protocol,
crypto, and per-guest context state.
WT_CO_STACK_SIZE defaults to 10,240 bytes. The positive, Crypto-validation,
and FF-M conformance M33MU workloads also passed with an 8 KiB configured stack
and PSPLIM overflow detection enabled. That threshold test establishes a peak
below 8 KiB for those workloads, so the 10 KiB default provides at least 2 KiB
of allocation headroom over the tested peak. It is not a precise high-water
measurement or a guarantee for different workloads. This is a fixed
allocation, not a heap or a claim that every run consumes all 10 KiB.
See TF-M Compatibility for the complete local footprint comparison and methodology.
Both engine builds enforce the following after linking:
-
mk/arch-armv8m.mkrunsarm-none-eabi-nmand writes the complete symbol list toBUILD_DIR/nsc-syms.txt. - The link check rejects any
__acle_se_*symbol outside this exactnmset:__acle_se_WolfTrust_FFM_FrameworkVersion,__acle_se_WolfTrust_FFM_ServiceVersion,__acle_se_WolfTrust_FFM_Connect,__acle_se_WolfTrust_FFM_Call, and__acle_se_WolfTrust_FFM_Close. - A separate count check requires exactly five
__acle_se_*symbols, so a missing veneer also fails the build. - The same symbol list is searched for
malloc,free,calloc,realloc,_sbrk,_malloc_r, and_free_r; finding one fails the zero-heap Secure-image build.
The measured native and wolfHSM images contain exactly those five veneers and
none of the guarded heap symbols. The source profile also defines
NO_WOLFSSL_MEMORY and WOLFSSL_NO_MALLOC.
See Security Model for the common boundary and Testing for the engine test matrix.