Skip to content

Crypto Engines

github-actions[bot] edited this page Oct 1, 2026 · 2 revisions

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.

At a glance

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.

One service seam

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.

Native crypto engine

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_HSM call;
  • 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.

Native request format

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.

Shared NVM object store

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.

wolfHSM engine

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.

Choosing an engine

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.

Selecting the engine

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-hsm

WT_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.

Measured Secure-image cost

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.elf

Flash 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

Stack contribution

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.

Build invariants

Both engine builds enforce the following after linking:

  1. mk/arch-armv8m.mk runs arm-none-eabi-nm and writes the complete symbol list to BUILD_DIR/nsc-syms.txt.
  2. The link check rejects any __acle_se_* symbol outside this exact nm set: __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.
  3. A separate count check requires exactly five __acle_se_* symbols, so a missing veneer also fails the build.
  4. 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.

Clone this wiki locally