diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index 0e80b033..9a073288 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -33,3 +33,7 @@ jobs: run: make -C tests/firmware/stm32h563 all TOOLPREFIX=arm-none-eabi- - name: Compile-check the no-attestation secure image (WT_ATTEST_COSE=0) run: make TARGET=stm32h563 WT_ATTEST_COSE=0 secure-image TOOLPREFIX=arm-none-eabi- + - name: Build the hsm-engine secure image (WT_ENGINE=hsm) + run: make TARGET=stm32h563 WT_ENGINE=hsm secure-image TOOLPREFIX=arm-none-eabi- BUILD_DIR=build_hsm + - name: Build the native-engine secure image (WT_ENGINE=native) + run: make TARGET=stm32h563 WT_ENGINE=native secure-image TOOLPREFIX=arm-none-eabi- BUILD_DIR=build_native diff --git a/.github/workflows/m33mu.yml b/.github/workflows/m33mu.yml index c69ee8f4..b7c2b182 100644 --- a/.github/workflows/m33mu.yml +++ b/.github/workflows/m33mu.yml @@ -80,12 +80,13 @@ jobs: ./test-app/emu-test-apps/test.sh wolfboot-wolftrust-m33mu: - name: wolfTrust ${{ matrix.guest }} lifecycle + name: wolfTrust ${{ matrix.guest }} lifecycle (${{ matrix.engine }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: guest: [zephyr, freertos] + engine: [native, hsm] timeout-minutes: 45 container: image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 @@ -93,6 +94,7 @@ jobs: # Keep the validated H5 secure-app handoff until that configuration is # upstreamed; upstream master does not yet ship this wolfTrust config. WOLFBOOT_REF: d85fa9dbdf6c36f47b7e96eba5c9df750ad3c963 + WT_ENGINE: ${{ matrix.engine }} CROSS_COMPILE: /usr/local/bin/arm-none-eabi- ZEPHYR_TOOLCHAIN_VARIANT: cross-compile # The wolfBoot configuration reserves the wolfTrust partition at the @@ -273,11 +275,12 @@ jobs: fi wolfboot-wolftrust-m33mu-scenarios: - name: ${{ matrix.scenario.name }} + name: ${{ matrix.scenario.name }} (${{ matrix.engine }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: + engine: [native, hsm] scenario: - { key: positive, name: "Positive lifecycle" } - { key: bothpsa, name: "Both-OS PSA parity (Zephyr + FreeRTOS)" } @@ -305,9 +308,17 @@ jobs: - { key: vnetneg, name: "Confined VNET isolation negatives" } - { key: manifestneg, name: "Corrupted-manifest activation refused" } - { key: gtzcneg, name: "NS MPU bypass cannot reach peer guest RAM" } + - { key: spbudgetneg, name: "SP restart-budget exhaustion escalates" } + exclude: + # hsmattackneg drives the raw wolfHSM client wire from the guest; + # the native engine does not link that wire, so the attack surface + # under test does not exist there. + - { engine: native, scenario: { key: hsmattackneg, name: "wolfHSM cross-namespace + NVM relay negatives" } } timeout-minutes: 45 container: image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 + env: + WT_ENGINE: ${{ matrix.engine }} steps: - uses: actions/checkout@v4 diff --git a/docs/Architecture.md b/docs/Architecture.md index 7bce0000..a006aba1 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -21,8 +21,8 @@ contain Armv8-M-specific types and assumptions. | wolfBoot | Performs the BL2 secure-boot role, authenticates wolfTrust, passes the measured-boot handoff, and swaps authenticated update images. | | wolfTrust | Configures isolation, validates the manifest and guest images, schedules guests and Secure Partitions, implements FF-M IPC, and manages lifecycle and recovery. | | wolfPSA | Implements PSA Crypto entry points over wolfCrypt for the Non-secure reference guests. | -| wolfCrypt | Supplies cryptographic implementations used by wolfPSA and Secure services. | -| wolfHSM | Owns protected key operations and the persistent NVM backend. | +| wolfCrypt | Supplies cryptographic implementations used by guest wolfPSA and Secure services. The native engine dispatches it directly. | +| wolfHSM | Optional crypto engine providing the client/server key-management model and external-HSM integration path. Both engines use its NVM object-store subset. | | wolfCOSE | Encodes and signs COSE_Sign1 attestation tokens. | | wolfIP | Supplies the TCP/IP stack for the optional bare-metal reference guests; the Secure virtual Ethernet switch itself is implemented by wolfTrust. | @@ -52,9 +52,9 @@ preserve the public manifest, service, IPC, and PSA API contracts. See and memory policies; validates and binds the generated manifest; registers the FF-M services; seeds each guest context; and performs an initial signed-record, image-size, manifest-version, digest, and optional WRP check. -3. wolfTrust consumes the measured-boot handoff, initializes wolfHSM and the - persistent vault backends, and checks the persistent Secure-image and guest - version floors. +3. wolfTrust consumes the measured-boot handoff, initializes the selected + [crypto engine](Crypto-Engines.md) and shared persistent vault backend, and + checks the persistent Secure-image and guest version floors. 4. wolfTrust starts the scheduled Secure Partitions. 5. Immediately before an accepted guest's first dispatch, the monitor repeats its signed-record, image-size, manifest-version, digest, and optional WRP diff --git a/docs/Building.md b/docs/Building.md index 331966ce..699ff479 100644 --- a/docs/Building.md +++ b/docs/Building.md @@ -31,6 +31,19 @@ reference is the mutable `main` branch, not a pinned workspace. make ``` +The default uses the native crypto engine. Keep separate output directories +when comparing or retaining both engine builds: + +```sh +make secure-image WT_ENGINE=native BUILD_DIR=build-native +make secure-image WT_ENGINE=hsm BUILD_DIR=build-hsm +``` + +`WT_ENGINE_HSM=0` and `WT_ENGINE_HSM=1` remain as legacy aliases for +`native` and `hsm`, respectively. `WT_ENGINE` is the public selector for new +builds. See [Crypto Engines](Crypto-Engines.md) for the behavior, key model, +and measured footprint of each choice. + The default target builds: | Output | Purpose | @@ -99,6 +112,31 @@ make -C tests/firmware/zephyr-stm32h5 \ build-guest0-psa build-freertos-guest1 ``` +`WT_ENGINE` must match the Secure image and every guest image. The guest build +scripts default to `native` and pass the same selector through the Secure and +guest builds: + +```sh +WT_ENGINE=native make -C tests/firmware/zephyr-stm32h5 \ + build-guest0-psa build-freertos-guest1 +WT_ENGINE=hsm make -C tests/firmware/zephyr-stm32h5 \ + build-guest0-psa build-freertos-guest1 +``` + +For a direct Zephyr configuration rather than the wrapper script: + +- native uses `CONFIG_WOLFTRUST_NATIVE_CLIENT=y` and + `CONFIG_WOLFTRUST_WOLFHSM_CLIENT=n`; and +- hsm uses `CONFIG_WOLFTRUST_WOLFHSM_CLIENT=y` and leaves + `CONFIG_WOLFTRUST_NATIVE_CLIENT` disabled. + +The native Zephyr module links guest wolfCrypt plus +`src/client/crypto_native_client.c`. The wolfHSM module instead links the +wolfHSM client, crypto-callback glue, and +`src/client/hsm_psa_transport.c`. The FreeRTOS +`build_freertos_guest.sh` script makes the same source and preprocessor choice +from its `WT_ENGINE` environment variable. + This produces: - `tests/firmware/zephyr-stm32h5/build/guest0_psa/zephyr/zephyr.bin` @@ -117,6 +155,19 @@ The same Makefile also provides: | `run-tui` | Same pair with the M33MU TUI | | `zephyr-freertos-uarts` | Zephyr and FreeRTOS PSA guests under M33MU | +## Engine coverage in CI + +The cross-compile workflow links a Secure image with each engine. The M33MU +lifecycle job crosses `guest: [zephyr, freertos]` with +`engine: [native, hsm]`. The scenario job also adds the engine as a matrix +dimension, and the label-selected pull-request workflow runs each requested +scenario under both engines. + +The `hsmattackneg` scenario is intentionally hsm-only. It injects raw wolfHSM +protocol packets and attacks a wolfHSM namespace and NVM relay surface that is +not linked into the native engine. All other scenario rows run under both +engines. See [Testing](Testing.md) for the commands and validation scope. + ## Authenticated image assembly `make` alone produces an unsigned flat Secure binary. The target diff --git a/docs/Crypto-Engines.md b/docs/Crypto-Engines.md new file mode 100644 index 00000000..9444dc91 --- /dev/null +++ b/docs/Crypto-Engines.md @@ -0,0 +1,298 @@ +# 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: + +```text +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 + +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: + +```sh +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](Building.md) 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](TF-M-Compatibility.md). 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: + +```sh +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: + +```text +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](TF-M-Compatibility.md) 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](Security-Model.md) for the common boundary and +[Testing](Testing.md) for the engine test matrix. diff --git a/docs/Home.md b/docs/Home.md index ff7d8818..6916ccc9 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -37,7 +37,7 @@ flowchart TB subgraph WT[wolfTrust policy and service runtime] SPM[Secure Partition Manager
policy, identity, IPC, scheduling, lifecycle, recovery] subgraph SP[Secure services] - CR["Cryptography and hardware
security module (HSM)"] + CR["Selected crypto engine
native or wolfHSM"] ST["Internal Trusted Storage (ITS),
Protected Storage, and vault"] AT[Initial Attestation] FW[Firmware Update] @@ -51,8 +51,8 @@ flowchart TB end subgraph LIBS[wolfSSL ecosystem components] - PSA[wolfPSA
guest wolfCrypt and wolfHSM client] - WC[Secure wolfCrypt and wolfHSM] + PSA[wolfPSA
guest wolfCrypt; optional wolfHSM client] + WC[Secure wolfCrypt
optional wolfHSM server] COSE[wolfCOSE] HAL[wolfHAL] IP[wolfIP
optional bare-metal reference networking] @@ -93,8 +93,8 @@ port's five CMSE gateway veneers. | One mediated client boundary | Application domains reach services only through the client gateway supplied by the architecture port. The current Armv8-M image exports exactly five `WolfTrust_FFM_*` veneers. | | Caller-bound IPC | wolfTrust derives the PSA client identity from the active application domain, copies vector descriptors, checks every range, and enforces manifest access policy. | | Port-defined isolation | Each port declares and enforces the protection capabilities required by its manifest. The STM32H563 reference uses TrustZone, the Secure MPU, and GTZC MPCBB attribution; its exact limits are documented in [Security Model](Security-Model.md). | -| PSA cryptography | Zephyr and FreeRTOS reference guests call wolfPSA's PSA Crypto API; operations are mediated to wolfCrypt and per-guest wolfHSM namespaces. | -| Secure services | Connection-based services provide attestation, hardware security module (HSM) access, Internal Trusted Storage (ITS), Protected Storage, firmware update, and an optional Secure virtual Ethernet switch. The optional bare-metal networking guests run wolfIP outside the Secure image. | +| PSA cryptography | Zephyr and FreeRTOS reference guests call wolfPSA's PSA Crypto API. The default native engine runs wolfCrypt in each guest and obtains DRBG seeds from the Secure vault; the optional wolfHSM engine routes supported operations to per-guest Secure server namespaces. | +| Secure services | Connection-based services provide the selected crypto engine, attestation, Internal Trusted Storage (ITS), Protected Storage, firmware update, and an optional Secure virtual Ethernet switch. The optional bare-metal networking guests run wolfIP outside the Secure image. | | Fault containment | A guest fault either restarts the guest within policy limits or leaves it quarantined. A Secure Partition fault releases synchronization state before failing affected calls. Restart paths scrub declared private writable memory before rearming; forbidden, exhausted, or failed recovery escalates to the port's fail-closed path. | | Static Secure memory | The Secure image is built with `WOLFSSL_NO_MALLOC` and `NO_WOLFSSL_MEMORY`; service buffers, stacks, and state are statically allocated. | @@ -104,6 +104,7 @@ port's five CMSE gateway veneers. | --- | --- | | [Getting Started](Getting-Started.md) | Prerequisites, checkout, first builds, emulator use, and hardware entry points | | [Architecture](Architecture.md) | Boot flow, isolation layers, FF-M IPC, services, and scheduling | +| [Crypto Engines](Crypto-Engines.md) | Native and wolfHSM engine behavior, selection, key models, and measured cost | | [Security Model](Security-Model.md) | Trust boundaries and enforced security properties | | [Threat Model](Threat-Model.md) | Protected assets, attacker capabilities, controls, and residual risks | | [API Reference](API-Reference.md) | PSA client, service, storage, update, lifecycle, attestation, and gateway APIs | diff --git a/docs/Macros.md b/docs/Macros.md index 85fe15ee..51e9df5b 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -12,6 +12,7 @@ selected values into C preprocessor defines. Defaults below come from | `TARGET` | Target build selector; default `stm32h563`. | Must match an `mk/target-.mk` fragment; the root Makefile includes it, the architecture fragment, and `mk/common.mk`. | | `TOOLPREFIX` | Cross-tool prefix; default `arm-none-eabi-`. | The prefixed GCC, objcopy, nm, and size tools must be available. | | `BUILD_DIR` | Secure build output directory; default `build`. | Must be writable. | +| `WT_ENGINE` | Secure crypto engine: `native` (default) dispatches wolfCrypt directly behind the SERVICE_HSM door with explicitly vault-backed keys stored as `SENSITIVE` and `NONEXPORTABLE` NVM objects; `hsm` links the wolfHSM server as a key-management add-on (server-keystore semantics and an external-HSM offload path). Legacy `WT_ENGINE_HSM=0/1` maps onto the selector. | Both engines share the identical FF-M surface (5 veneers, SIDs, manifest, and L3 bands) and run every applicable CI scenario. Guest builds must use the same engine as the Secure image. See [Crypto Engines](Crypto-Engines.md). | ## Core target configuration @@ -19,7 +20,7 @@ selected values into C preprocessor defines. Defaults below come from | --- | --- | --- | | `WT_MAX_GUESTS` | Selects one or two compiled STM32H563 guest contexts; default `2`. | The current port supports only `1` or `2`. Larger values require extending the partition tables and matching manifest, linker, emulator, flash, and measurement configuration. | | `WT_TIMESLICE_MS` | Guest scheduler interval in milliseconds; default `2`. | Must be nonzero and supported by the target timer. | -| `WT_CO_STACK_SIZE` | Default fixed coroutine stack size in bytes, including each per-guest wolfHSM tasklet; default `24576`. Manifest-sized Secure Partition stacks use their declared sizes instead. | Size from measured stack high-water marks and keep at least the scheduler minimum. | +| `WT_CO_STACK_SIZE` | Default fixed coroutine stack size in bytes, including each per-guest wolfHSM server tasklet in the hsm engine; default `10240` (measured: the deep M33MU workloads pass at 8K with PSPLIM overflow detection armed, so 10K carries at least 2K margin). With two guests, the hsm-only server slots total 20,480 stack bytes plus 512 guard bytes. Manifest-sized Secure Partition stacks use their declared sizes instead. | Size from measured stack high-water marks and keep at least the scheduler minimum. | | `WT_SHARED_UART` | Reference guest UART selection; default `3`. | Guest and Secure builds must use a consistent value. | | `WT_GUEST_CORE_CLOCK_HZ` | Guest core-clock value; default `240000000`. | Must match the configured target clock. | | `WT_GUEST_UART_CLOCK_HZ` | Guest UART-clock value; default `120000000`. | Must match the selected UART clock source. | @@ -29,7 +30,7 @@ selected values into C preprocessor defines. Defaults below come from | Define | Description | Requirement | | --- | --- | --- | | `WT_GUEST_FLASH_WRP` | When `1`, verify full STM32 guest-window WRP coverage before launch; default `0`. | Set to `1` for the hardened STM32H563 image and provision WRP after flashing. M33MU does not model WRP. | -| `WT_ENGINE_HSM` | Value passed to the wolfCrypt engine and Secure HSM integration; default `1`. | Keep `1` for the reference service configuration. | +| `WT_ENGINE_HSM` | Legacy engine selector; unset by default. `0` maps to `WT_ENGINE=native` and `1` maps to `WT_ENGINE=hsm` when the public selector is not supplied. The build also derives this internal value from `WT_ENGINE`. | Prefer `WT_ENGINE` for new builds and do not supply conflicting selectors. The guest and Secure image must select the same engine. | | `WT_ATTEST_COSE` | Must remain `1` in the current STM32H563 reference build; default `1`. The `0` configuration does not compile because the reset path still references attestation-gated handoff variables. | Requires the wolfCOSE submodule and the configured attestation key backend. | | `WT_WOLFCRYPT_SP_ASM` | Enable wolfCrypt SP Cortex-M assembly; default `1`. | Requires compatible Armv8-M assembly sources and toolchain. | | `WT_WOLFCRYPT_ARMASM` | Enable additional wolfCrypt Thumb-2 assembly; default `1`. | Requires a compatible GNU Arm toolchain. | diff --git a/docs/Porting.md b/docs/Porting.md index f9f81178..8badef87 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -110,9 +110,11 @@ const void* wt_hsm_flash_config(void); int wt_hsm_flash_format(void); ``` -The implementation must preserve the wolfHSM flash-log semantics, distinguish -foreign or corrupt media, honor checked object flags, and erase only the -dedicated vault region when lifecycle policy allows reformat. +Both crypto engines use this object store. The implementation must preserve +the wolfHSM NVM flash-log semantics, distinguish foreign or corrupt media, +honor checked object flags, and erase only the dedicated vault region when +lifecycle policy allows reformat. See [Crypto Engines](Crypto-Engines.md) for +the engine boundary above the common store. ### Entropy diff --git a/docs/Security-Model.md b/docs/Security-Model.md index e77045f7..f733124a 100644 --- a/docs/Security-Model.md +++ b/docs/Security-Model.md @@ -14,7 +14,8 @@ The reference trusted computing base includes: - wolfBoot and its verification key - the wolfTrust Secure image and generated manifest -- wolfCrypt, wolfHSM, wolfHAL, and wolfCOSE code linked into that image +- wolfCrypt, wolfHAL, wolfCOSE, the shared wolfHSM NVM components, and the + full wolfHSM server when the optional hsm engine is selected - Armv8-M exception, TrustZone, and MPU behavior - STM32H563 GTZC and flash option-byte configuration - privileged wolfTrust SVC and fault handlers @@ -34,7 +35,7 @@ trusted to access arbitrary SPM or peer-partition writable state. | Guest to service | Manifest service policy, connection ownership, generated handles, and SPM-stamped client identity | | Secure Partition writable state | Unprivileged Secure threads and a per-partition Secure MPU table | | Secure Partition to hardware backend | Operation-specific SVC gates pinned to the expected partition identity | -| Persistent objects | Vault ownership tuple, checked wolfHSM NVM operations, and key/storage type separation | +| Persistent objects | Vault ownership tuple, checked NVM operations, engine-specific non-exportable key policy, and key/storage type separation | ## The only Non-secure entry path @@ -111,21 +112,37 @@ operations are available only through narrow SVC operations that check the originating partition. This is writable-state isolation inside one linked image. Shared executable -text is not per-partition code isolation, and the HSM, vault, and attestation +text is not per-partition code isolation, and the crypto, vault, and attestation domains share the keystore data band required by their backends. ## Per-guest cryptographic keys -The HSM service receives one copied wolfHSM request packet through FF-M IPC. -The SPM-stamped negative client ID selects guest `N`, and the relay -forces wolfHSM server client ID `N + 1` before processing the packet. -A client-provided communication ID therefore cannot select another guest's -key namespace. - -The guest-facing HSM relay rejects wolfHSM NVM message groups. Guests can use -the intended cryptographic protocol but cannot directly reach vault objects, -the firmware-version floor, the Protected Storage counter table, or the -attestation key. +The SERVICE_HSM door carries the selected crypto engine's wire (`WT_ENGINE`): +the native engine (default) dispatches wolfCrypt directly, with explicitly +vault-backed keys stored as `SENSITIVE` and `NONEXPORTABLE` NVM objects whose +private material never leaves the Secure key-vault domain; the hsm engine +relays wolfHSM server packets. The FF-M surface, SIDs, and isolation bands are +identical in both engines. + +In the hsm engine the service receives one copied wolfHSM request packet +through FF-M IPC. The SPM-stamped negative client ID selects guest `N`, and +the relay forces wolfHSM server client ID `N + 1` before processing the +packet. In the native engine the same SPM-stamped identity becomes the vault +key namespace's delegated sub-owner. A client-provided communication ID +therefore cannot select another guest's key namespace in either engine. + +The native reference guests normally run wolfPSA and wolfCrypt locally. Their +ordinary volatile PSA keys therefore live in Non-secure guest RAM; only DRBG +seed requests and explicit native-wire vault-key requests cross the Secure +boundary. In the hsm engine, supported guest PSA operations and their private +key state are routed to the Secure wolfHSM server. + +The hsm engine's guest-facing relay rejects wolfHSM NVM message groups. The +native request format exposes no general NVM operation. Guests can use the +intended engine protocol but cannot directly reach storage objects, the +firmware-version floor, the Protected Storage counter table, or the +attestation key. See [Crypto Engines](Crypto-Engines.md) for the complete +selection and key-model comparison. ## Storage protection @@ -144,7 +161,7 @@ The current `psa_ps_get_info()` implementation echoes those requested hint flags instead of reporting the stronger protection actually applied. Sealing uses AES-256-GCM with: -- a device-local non-exportable key stored in wolfHSM NVM; +- a device-local non-exportable key stored in the shared Secure NVM store; - the object label as authenticated data; - a 12-byte nonce derived from a persisted per-write counter; and - a 16-byte authentication tag. @@ -167,9 +184,10 @@ The vault requires wolfHSM's rejects incompatible backends at initialization. The flash HAL may be supplied by the target port or the host RAM simulator. -Vault storage rejects its reserved key-object type. Guest cryptographic keys -instead use the separate wolfHSM keystore behind `SERVICE_HSM`, where the -relay binds operations to the caller's namespace. +Vault storage rejects its reserved key-object type. In the native engine, +explicit key requests use the vault's separate key face. In the hsm engine, +guest cryptographic keys instead use the wolfHSM server keystore behind +`SERVICE_HSM`. Both paths bind operations to the SPM-stamped caller namespace. ## Authenticated guest launch @@ -199,9 +217,10 @@ the option bytes as described in [STM32H5 Guide](STM32H5-Guide.md). ## Static memory The Secure wolfCrypt settings define both `NO_WOLFSSL_MEMORY` and -`WOLFSSL_NO_MALLOC`. Stacks, IPC transfers, service state, HSM +`WOLFSSL_NO_MALLOC`. Stacks, IPC transfers, service state, selected-engine contexts, and cryptographic scratch space use fixed storage. Oversized requests -fail instead of allocating. +fail instead of allocating. The link also rejects allocator symbols in both +engine images. ## Source anchors @@ -209,6 +228,8 @@ fail instead of allocating. - [Secure Partition scheduler and SVC gates](../src/arch/armv8m/spm_svc.c) - [Guest verification](../src/guest_verify.c) - [HSM relay binding](../src/services/wolfhsm/wt_hsm.c) +- [Native crypto dispatch](../src/services/native/crypto_native.c) +- [Native vault key backend](../src/services/native/keyvault.c) - [Vault storage](../src/services/wolfhsm/wt_hsm_vault.c) See [Threat Model](Threat-Model.md) for assumptions and residual risks. diff --git a/docs/Services.md b/docs/Services.md index a5d93b9b..e4943dd0 100644 --- a/docs/Services.md +++ b/docs/Services.md @@ -13,7 +13,7 @@ manifest with a seventh service. | `SERVICE_ITS` | `0x1003` | Yes | PSA Internal Trusted Storage | | `SERVICE_PS` | `0x1004` | Yes | PSA Protected Storage | | `SERVICE_FWU` | `0x1005` | Yes | PSA Firmware Update staging for wolfBoot | -| `SERVICE_HSM` | `0x1006` | Yes | Copied wolfHSM cryptographic request relay | +| `SERVICE_HSM` | `0x1006` | Yes | Copied request relay for the selected native or wolfHSM crypto engine | | `SERVICE_VNET` | `0x1007` | Yes, optional | Secure virtual Ethernet switch implemented by wolfTrust for optional Non-secure wolfIP guests | All service versions are `1`. ITS and Protected Storage declare a @@ -29,25 +29,32 @@ replies. Each thread receives shared Secure text plus its own stack and manifest-declared data. Privileged hardware operations are performed by narrow SVC gates that verify which partition issued the request. -## HSM and PSA Crypto +## Crypto engines and PSA Crypto -Zephyr and FreeRTOS reference guests expose wolfPSA's PSA Crypto API. wolfPSA -uses a wolfHSM client transport that places one complete wire packet in an -FF-M `psa_call` to `SERVICE_HSM`. +Zephyr and FreeRTOS reference guests expose wolfPSA's PSA Crypto API. The +`WT_ENGINE` build selector chooses how those guests and `SERVICE_HSM` are +wired. Both engines use one input and one output vector, bounded to 384 bytes, +through the same copied FF-M call. -The service: +With the default native engine, wolfPSA and wolfCrypt execute in the +Non-secure guest. DRBG seed requests use the native client to reach the Secure +vault RNG. The native wire also exposes explicit vault-backed P-256 and +AES-256 key operations, random generation, and SHA-256. Vault key objects are +namespaced by the SPM-stamped client ID and stored `SENSITIVE` and +`NONEXPORTABLE`. -- accepts one input packet and one output packet, each bounded to 512 bytes; -- uses the SPM-stamped PSA client ID rather than an ID in the packet; -- maps guest `N` to its dedicated wolfHSM server and forces server - client ID `N + 1`; -- rejects all guest-facing wolfHSM NVM message groups; and -- returns the copied response through the original output vector. +With the wolfHSM engine, wolfPSA uses a wolfHSM client transport that places +one complete wolfHSM packet in the FF-M call. The relay maps guest `N` to its +dedicated server, forces server client ID `N + 1`, and rejects guest-facing +wolfHSM NVM message groups. The reference build executes the server with +software wolfCrypt; deployments can configure wolfHSM's external-HSM path. The reference cryptographic profile is controlled by each guest's wolfCrypt `user_settings.h`. Consult the vendored `lib/wolfPSA/wolfpsa/psa/crypto.h` and the active guest configuration -before assuming a particular algorithm is available. +before assuming a particular algorithm is available. See +[Crypto Engines](Crypto-Engines.md) for the protocol, key-protection, and +footprint differences. ## Vault @@ -62,11 +69,12 @@ object is indexed by: This lets ITS and Protected Storage forward a guest's stamped client ID without allowing either front end to escape its own vault namespace. -The vault uses checked wolfHSM NVM operations for write-once and object -metadata policy. Storage calls cannot create, read, or overwrite the reserved -key-object type. The STM32H563 integration does not install the vault's -optional key-operation backend; guest cryptographic keys are managed through -`SERVICE_HSM` instead. +The vault uses the wolfHSM NVM object-store library and checked operations for +write-once and object-metadata policy in both engines. Storage calls cannot +create, read, or overwrite the reserved key-object type. The native engine +installs the vault key backend used by its explicit key wire. The wolfHSM +engine instead manages guest cryptographic keys in its server keystore behind +`SERVICE_HSM`. ## Internal Trusted Storage @@ -103,9 +111,10 @@ limited to 512 bytes. The underlying Secure vault still uses a 1024-byte object buffer and caps one copied vault response at 1000 bytes. Every stored object is AES-256-GCM sealed in the vault under a device-local -non-exportable key. A persisted write counter supplies the nonce and the object -label is authenticated data. Confidentiality and replay-protection hint flags do -not weaken storage: Protected Storage still seals and counter-binds the object. +non-exportable key in the shared NVM store. A persisted write counter supplies +the nonce and the object label is authenticated data. Confidentiality and +replay-protection hint flags do not weaken storage: Protected Storage still +seals and counter-binds the object. The current `psa_ps_get_info()` behavior echoes the requested flags instead of reporting the stronger protection actually applied, which differs from the PSA Secure Storage 1.0 recommendation. @@ -115,7 +124,8 @@ Secure Storage 1.0 recommendation. `SERVICE_ATTEST` implements the token and exact-size API operations from PSA Initial Attestation 1.0. It accepts a 32-, 48-, or 64-byte challenge and returns a tagged COSE_Sign1 token signed with ES256 through an external signer backed -by the protected attestation key. wolfCOSE performs the COSE encoding. +by the selected engine's protected attestation key. wolfCOSE performs the COSE +encoding. The emitted token is derived from [RFC 9783](https://www.rfc-editor.org/rfc/rfc9783.html) but is not conformant with its advertised diff --git a/docs/TF-M-Compatibility.md b/docs/TF-M-Compatibility.md index a40099e5..a2f87c33 100644 --- a/docs/TF-M-Compatibility.md +++ b/docs/TF-M-Compatibility.md @@ -9,12 +9,104 @@ This register describes the code in the repository. It is not a certification statement, and a declaration in a vendored header does not mean every optional algorithm or feature is enabled in every build. +## Measured Secure-image footprint + +The following table records local builds, not published reference figures. +The wolfTrust rows use the STM32H563 reference manifest with +`isolation_profile` set to 3 and include Internal Trusted Storage, Protected +Storage, Firmware Update, vault services, and COSE attestation. The TF-M rows +are the standard Small, Medium, and Large profiles built for AN521. + +| Secure image | Profile and enabled services | Flash | Static RAM | +| --- | --- | ---: | ---: | +| wolfTrust native | wolfTrust profile 3; Crypto, Internal Trusted Storage, Protected Storage, Firmware Update, vault, COSE attestation | 86,848 bytes | 27,253 bytes | +| wolfTrust wolfHSM | Same services plus the wolfHSM server | 106,432 bytes | 59,073 bytes | +| TF-M Small | Level 1; Crypto, Internal Trusted Storage, Initial Attestation; Protected Storage and Firmware Update off | 50,968 bytes | 14,296 bytes | +| TF-M Medium | Level 2; Crypto, Internal Trusted Storage, Protected Storage, Initial Attestation; Firmware Update off | 67,332 bytes | 42,468 bytes | +| TF-M Large | Level 3; Crypto, Internal Trusted Storage, Protected Storage, Initial Attestation; Firmware Update off | 115,460 bytes | 45,756 bytes | + +In these builds, native wolfTrust uses about 25% less flash than TF-M Large +and less static RAM than TF-M Medium while also including firmware update. +The wolfHSM engine remains smaller in flash than TF-M Large but uses more +static RAM because it adds per-guest server state and stacks. These results do +not imply that the projects, platforms, or enabled feature sets are identical. + +### Methodology + +All five images were built on `wolf-prec5560` with +`arm-none-eabi-gcc (15:13.2.rel1-2) 13.2.1 20231009`. Footprint was read from +the linked Secure ELF with `arm-none-eabi-size` and calculated as: + +```text +Flash = text + data +Static RAM = data + bss +``` + +The wolfTrust rows were reproduced on 2026-09-18 from the source tree +containing this page with the exact dependency set below. + +| Component | Pinned revision | Git description | +| --- | --- | --- | +| wolfCOSE | `f907071b10127f3ae2dd7719749a91b039ff04a1` | `v2.0.0` | +| wolfHSM | `a0323156606282448f00473a3fcb7aaa69361921` | `wolfHSM-v1.4.0-171-ga032315` | +| wolfIP | `146de4b6362c3a076787e27332f50daa0a445cf5` | `v1.0-91-g146de4b` | +| wolfPSA | `1b9ec29706bc63f785682ad688350195a33b22e8` | `v5.9.1-129-g1b9ec29` | +| wolfSSL | `22e505bcfad8ce21067ee4232128728543767a95` | `v5.9.1-stable-1088-g22e505bcf` | +| wolfHAL | `2bc2938b0bbcc977177153a7f38393710702bf70` | No reachable tag | + +The wolfTrust images used `-Os` and the following commands; all other build +variables retained their repository defaults: + +```sh +git submodule update --init --recursive +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 +``` + +The measured wolfTrust files were the two `wolftrust.elf` outputs. Their raw +`text`, `data`, and `bss` values are recorded in +[Crypto Engines](Crypto-Engines.md). + +The TF-M source was the `TF-Mv2.1.1-LTS` tag at commit +`02bf279913439a07082dd581df033f370a8fbb92`. The following commands reproduce +the AN521 GNU Arm builds with BL2 enabled and no regression tests: + +```sh +for profile in small medium large; do + cmake -S . -B "build_${profile}" \ + -DTFM_PLATFORM=arm/mps2/an521 \ + -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \ + -DTFM_PROFILE="profile_${profile}" \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DBL2=ON + cmake --build "build_${profile}" --parallel +done +arm-none-eabi-size build_small/bin/tfm_s.elf \ + build_medium/bin/tfm_s.elf \ + build_large/bin/tfm_s.elf +``` + +The measured TF-M file in each case was `tfm_s.elf`. + +Only the Secure runtime ELF is counted. wolfBoot and Non-secure wolfTrust +guests are excluded; TF-M BL2 and its Non-secure application are likewise +excluded. Although the TF-M configurations had `BL2=ON`, the separate BL2 +image is not part of `tfm_s.elf` and therefore is not in the table. + +The TF-M builds target AN521 while wolfTrust targets STM32H563, and their +profiles do not enable the same services. Treat the table as a reproducible +local build comparison, not a platform-normalized benchmark. See +[Crypto Engines](Crypto-Engines.md) for the measured cost within wolfTrust, +where the platform and feature set are held constant. + ## Compatibility register | API or behavior | Version | Status | Repository evidence | | --- | --- | --- | --- | | FF-M client API | 1.0 with the scoped deviations below | Connection-based IPC is implemented | `include/psa/client.h` and `src/client/psa_ffm_client.c` | -| Secure Partition IPC API | FF-M 1.0 plus a wolfTrust-specific backport of `psa_irq_enable()` from Arm's FF-M 1.1 Extension Beta, Issue 0 | Supported for scheduled IPC partitions; wolfTrust still reports framework version `0x0100` and does not accept 1.1 manifests | `include/psa/service.h`, `include/psa/client.h`, and `src/arch/armv8m/spm_sp_api.c` | +| Secure Partition IPC API | FF-M 1.0 plus a wolfTrust-specific backport of `psa_irq_enable()` from Arm's FF-M 1.1 Extension Beta, Issue 0 | Supported for scheduled IPC partitions; wolfTrust still reports framework version `0x0100` and does not accept 1.1 manifests | `include/psa/service.h`, `include/psa/client.h`, and `src/arch/common/spm_sp_api.c` | | Framework and service discovery | 1.0 | Supported | `psa_framework_version` and `psa_version` | | Copied input and output vectors | FF-M 1.0 | Supported, with at most four vectors total and a 1024-byte aggregate budget across input bytes and declared output capacity | `include/wolftrust/ffm.h` and `src/ffm.c` | | Manifest validation | wolfTrust format 1 | Supported for immutable generated C data | `tools/manifest/generate.py`, `src/manifest.c`, and `port/stm32h563/manifest.json` | @@ -23,7 +115,7 @@ algorithm or feature is enabled in every build. | Protected Storage | 1.0 | Core set/get/get-info/remove subset; optional create/set-extended absent and the `WRITE_ONCE` lifecycle deviation below applies | `include/psa/protected_storage.h` and `src/services/storage_service.c` | | Initial Attestation | 1.0 API subset with a nonconformant RFC 9783-derived token | Token and exact-size operations are supported, but the advertised TF-M profile has the claim-semantic deviations below | `lib/wolfPSA/wolfpsa/psa/initial_attestation.h` and `src/services/initial_attestation.c` | | Firmware Update | 1.0 subset | Single-component staging and authenticated reboot supported, with the alignment and status deviations below | `include/psa/update.h` and `src/services/fwu_service.c` | -| RoT lifecycle query | FF-M 1.0 | Secure Partition only; there is no Non-secure adapter or veneer | `include/psa/lifecycle.h` and `src/arch/armv8m/spm_sp_api.c` | +| RoT lifecycle query | FF-M 1.0 | Secure Partition only; there is no Non-secure adapter or veneer | `include/psa/lifecycle.h` and `src/arch/common/spm_sp_api.c` | | Secure Partition signals and IRQ APIs | FF-M 1.0 plus one wolfTrust-specific beta-extension backport | The 1.0 signal APIs and `psa_eoi` are supported; only `psa_irq_enable()` is backported from the FF-M 1.1 Extension Beta, Issue 0, while `psa_irq_status_t`, `psa_irq_is_enabled`, `psa_irq_disable`, and `psa_irq_restore` are absent | `include/psa/service.h` and the Armv8-M SVC implementation | | Guest identity | FF-M convention | Non-secure guest `N` is client `-(N + 1)` | `src/arch/armv8m/ffm_nsc.c` | @@ -40,7 +132,7 @@ algorithm or feature is enabled in every build. | Abnormal Non-secure guest termination reclaims the guest's connections without delivering `PSA_IPC_DISCONNECT` to the affected services. | Scoped deviation | Fault-handler cleanup cannot dispatch a Secure service inline without re-entering the scheduler. Shipped services do not use `psa_set_rhandle()` for per-connection cleanup, but a ported service that depends on disconnect cleanup must account for this behavior. | | PSA Crypto mechanisms are build-selected. | Standard profile behavior | The wolfPSA 1.4 header is present, while each guest's wolfCrypt settings determine available keys and algorithms. | | Protected Storage does not implement create or set-extended. | Scoped | `psa_ps_get_support()` returns zero and both optional operations return `PSA_ERROR_NOT_SUPPORTED`. | -| Protected Storage always applies confidentiality and replay protection even when `NO_CONFIDENTIALITY` or `NO_REPLAY_PROTECTION` is requested. | Known metadata deviation | Objects remain sealed and counter-bound, but `psa_ps_get_info()` echoes the requested hint flags instead of reporting the stronger protection actually applied, which differs from the PSA Secure Storage 1.0 recommendation. | +| Protected Storage always applies confidentiality and replay protection even when `NO_CONFIDENTIALITY` or `NO_REPLAY_PROTECTION` is requested. | Known metadata deviation | Objects remain sealed and counter-bound, but `psa_ps_get_info()` echoes the requested hint flags instead of reporting the stronger protection actually applied, which differs from the PSA Secure Storage 1.0 requirement. | | ITS and Protected Storage always enforce `PSA_STORAGE_FLAG_WRITE_ONCE`. | Known lifecycle deviation | The request path does not receive lifecycle state and always rejects modification or removal. PSA Secure Storage 1.0 requires the flag not to be enforced during `PSA_ROT_PROVISIONING`. | | Initial Attestation's public header omits `PSA_INITIAL_ATTEST_MAX_TOKEN_SIZE`. | Known header deviation | The service limit is 640 bytes, but callers cannot obtain that maximum from the public PSA header. | | A non-NULL attestation token buffer with zero capacity returns `PSA_ERROR_INVALID_ARGUMENT`. | Known status deviation | PSA Initial Attestation 1.0 specifies `PSA_ERROR_BUFFER_TOO_SMALL` for an undersized token buffer. Nonzero undersized buffers return `PSA_ERROR_BUFFER_TOO_SMALL`. | @@ -64,7 +156,7 @@ guest RAM, and unprivileged Secure threads use per-partition Secure MPU regions. The guest Non-secure MPU and interrupt masks are scheduling policy because a privileged guest can reprogram them. -The current single-image layout still shares Secure executable text, and HSM, +The current single-image layout still shares Secure executable text, and crypto, vault, and attestation share a keystore data band. Treat the profile field as a requested and validated wolfTrust policy level, not by itself as proof of independent TF-M isolation certification. [Security Model](Security-Model.md) describes the @@ -75,9 +167,12 @@ actual boundary. 1. Keep application calls on standard PSA headers where wolfTrust provides the corresponding Non-secure adapter: FF-M client, Crypto, ITS, Protected Storage, and Firmware Update. The lifecycle function is Secure-Partition-only. -2. Link `src/client/psa_ffm_client.c` and - `build/secure_cmse_implib.o`, then add the adapter required by each API: - wolfPSA plus `src/client/hsm_psa_transport.c` for Crypto, +2. Link `src/client/psa_ffm_client.c` and the generated + `secure_cmse_implib.o` from the selected Secure-image `BUILD_DIR`, then add + the adapter required by each API: + wolfPSA plus `src/client/crypto_native_client.c` for the native Crypto + configuration, or wolfPSA, the wolfHSM client, and + `src/client/hsm_psa_transport.c` for the hsm configuration; `src/client/psa_storage_client.c` for ITS and Protected Storage, `src/client/psa_fwu_client.c` for Firmware Update, and `src/client/vnet_psa_transport.c` for optional VNET. @@ -89,8 +184,10 @@ actual boundary. 4. Include the `psa_manifest/sid.h` generated for the selected target and manifest. 5. Check data-size assumptions against the copied IPC and service limits. - Stream update images in blocks no larger than - `PSA_FWU_MAX_WRITE_SIZE`. + Stream update images in blocks no larger than `PSA_FWU_MAX_WRITE_SIZE`. + For wolfTrust, both the image offset and block size must be aligned to + `1 << PSA_FWU_LOG2_WRITE_ALIGN`, including the final block. This is stricter + than PSA Firmware Update 1.0, which permits padding an unaligned final block. 6. Check optional APIs before use. In particular, treat Protected Storage create/set-extended and Firmware Update accept as unsupported. 7. Express Secure services, dependencies, memory, interrupts, restart policy, diff --git a/docs/Testing.md b/docs/Testing.md index f3e5e05c..831fa144 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -28,10 +28,10 @@ make -s -C tests/host print-suites Current suites cover domain and manifest validation, lifecycle, guest verification, rollback decisions, IPC and FF-M behavior, SPM policy, gateway -vectors, Secure Partition layout and recovery, HSM relay and key isolation, -vault and storage services, attestation and COSE integration, firmware update, -runtime remeasurement, VNET, public PSA headers, boot-handoff record -consumption, and negative paths. +vectors, Secure Partition layout and recovery, crypto-engine relay and key +isolation, vault and storage services, attestation and COSE integration, +firmware update, runtime remeasurement, VNET, public PSA headers, boot-handoff +record consumption, and negative paths. The attestation IAK suite runs wolfHSM NVM with both the default 8-byte and STM32H5 16-byte flash programming units. @@ -71,6 +71,14 @@ The baseline target command is: make test-target ``` +The engine defaults to `native`. Set `WT_ENGINE` to exercise the same target +path with either backend: + +```sh +WT_ENGINE=native make test-target +WT_ENGINE=hsm make test-target +``` + It runs the positive lifecycle, guest restart, cross-domain Secure fault, and FF-M conformance scenarios. Detection accepts `m33mu` on `PATH` or a path in `M33MU`. If the emulator is unavailable, @@ -79,7 +87,8 @@ the target reports a skip rather than a pass. Additional focused runs use: ```sh -tests/target/run_m33mu_scenario.sh positive +WT_ENGINE=native tests/target/run_m33mu_scenario.sh positive +WT_ENGINE=hsm tests/target/run_m33mu_scenario.sh positive ``` The runner's usage output is the authoritative scenario list. It includes @@ -97,6 +106,35 @@ make test-vnet-target `test-vnet` is host-only. `test-vnet-target` launches two authenticated wolfIP guests under M33MU. +### Engine matrix + +The full CI scenario list contains 27 scenarios and adds +`engine: [native, hsm]` as a matrix dimension. There are 53 applicable +engine/scenario cells: every scenario runs with both engines except +`hsmattackneg` under native. + +`hsmattackneg` drives the raw wolfHSM protocol from a compromised-guest probe. +It checks that a forged wolfHSM client ID cannot select the attestation key and +that a wolfHSM NVM-group packet cannot reach the rollback store. The native +engine does not link the wolfHSM client wire, server, or message handlers, so +that exact attack surface does not exist there. Native key and namespace +behavior remains covered by the common positive, cross-domain, keystore, +storage, attestation, and Crypto-validation rows. + +Validation of the engine split completed under both engines with: + +- the applicable M33MU scenario matrix; +- the Arm FF-M IPC suite at 85 passed, 4 heap-dependent tests skipped, and + 0 failed; +- the current dev_apis Crypto schedule at 64 passed, 13 skipped, and 0 failed + (77 scheduled tests; c047 is configuration-skipped in addition to the + upstream schedule); and +- the STM32H563 positive, restart, cross-domain, and conformance hardware + suite. + +The engine dimension changes crypto dispatch, not what M33MU proves. Emulator +results still do not establish STM32 attribution or physical flash behavior. + ## STM32H563 hardware The published hardware run requires: @@ -119,12 +157,18 @@ than an initial skip. Run the default hardware set with: ```sh -WT_H5_DOCKER_IMAGE=ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 make test-hardware +WT_ENGINE=native \ +WT_H5_DOCKER_IMAGE=ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 \ +make test-hardware +WT_ENGINE=hsm \ +WT_H5_DOCKER_IMAGE=ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 \ +make test-hardware ``` The target skips if hardware detection fails. It builds and flashes the -positive, restart, cross-domain, and conformance scenarios by default. Select -a narrower set with `WT_H5_SCENARIOS`: +positive, restart, cross-domain, and conformance scenarios by default. The +suite wrapper forwards `WT_ENGINE` into its build container. Select a narrower +set with `WT_H5_SCENARIOS`: ```sh WT_H5_SCENARIOS="positive bootupdate" \ @@ -175,7 +219,7 @@ The workflows under `.github/workflows/` separately run: - host unit tests; - compiler variants, sanitizers, and Valgrind; -- Cortex-M33 cross-compilation; +- Cortex-M33 cross-compilation of both crypto engines; - dependency integration; - the core/port split guard and the docs guard (no internal-ledger or home-directory references in the published docs); @@ -189,10 +233,11 @@ core/port split checks run on every pull request, including drafts. The fuzz target also runs on pull requests as a 60-second libFuzzer smoke pass; the nightly schedule and manual dispatch run the 600-second soak instead. -The full M33MU matrix (the `M33MU` workflow: wolfBoot plus both guest lifecycles -and every scenario) runs on every pull request, on a push to `master`, `main`, -or `wolfTrust-dev`, on the nightly schedule, and on manual dispatch. Every PR -gets the full emulator matrix automatically — no label or opt-in step. +The full M33MU matrix (the `M33MU` workflow: wolfBoot plus both guest +lifecycles, both crypto engines, and every scenario) runs on every pull +request, on a push to `master`, `main`, or `wolfTrust-dev`, on the nightly +schedule, and on manual dispatch. Every PR gets the full emulator matrix +automatically — no label or opt-in step. ### Running M33MU off a pull request diff --git a/docs/Threat-Model.md b/docs/Threat-Model.md index c8307787..bd31e3e6 100644 --- a/docs/Threat-Model.md +++ b/docs/Threat-Model.md @@ -43,7 +43,8 @@ The primary adversary controls all software in one Non-secure guest. It may: - issue arbitrary PSA requests, call types, handles, vector counts, addresses, lengths, and payloads; - race its own memory while a gateway call is in progress; -- spoof protocol fields, including wolfHSM communication IDs; +- spoof selected-engine protocol fields, including wolfHSM communication IDs + when the hsm engine is linked; - trigger faults, interrupt activity, repeated connects, and abandoned update sessions; - attempt to access peer RAM, Secure memory, peripherals, and inactive guest @@ -79,8 +80,8 @@ images. | Stale or stolen handles | Handle ownership, type, generation, and state transitions are checked by the SPM. | | Cross-guest RAM access | GTZC MPCBB attribution closes the full guest-RAM extent and reopens only the scheduled guest's writable SRAM blocks. Per-guest Non-secure MPU and interrupt state are restored scheduling policy, not adversarial boundaries against privileged guests. | | Inactive-guest flash modification | Signature-covered guest digests and runtime verification detect changes; hardened STM32H563 builds also require complete WRP coverage. | -| Cross-guest key use | The relay maps guest `N` to forced wolfHSM client ID `N + 1`. | -| Direct NVM access through HSM protocol | The guest HSM relay rejects NVM message groups. | +| Cross-guest key use | The SPM-stamped identity selects the native vault sub-owner; the hsm relay maps guest `N` to forced wolfHSM client ID `N + 1`. | +| Direct NVM access through a crypto protocol | The native format exposes no general NVM operation; the hsm relay rejects wolfHSM NVM message groups. | | Storage object confusion | The vault namespaces objects by front-end partition, stamped client, and UID, and storage requests cannot use its reserved key-object type. | | Protected Storage disclosure or edit | AES-256-GCM sealing, a non-exportable device key, authenticated labels, and checked NVM operations. | | Stale sealed-object replay | Each sealed write advances a persisted counter used in its nonce; the current slot counter authenticates reads. | @@ -105,7 +106,7 @@ peer secrets in guest images or claim peer-image confidentiality. ### Persistent counters use the same flash trust boundary Protected Storage counter records and firmware-version floors reside in the -wolfHSM NVM flash pool. The design detects stale object data when its live +shared NVM flash pool. The design detects stale object data when its live counter remains current and fails closed on malformed records. It does not claim resistance to a physical adversary that can restore a mutually consistent historical snapshot of the entire NVM pool. A target requiring @@ -114,9 +115,10 @@ that property needs rollback-resistant monotonic storage in its port. ### Secure code is shared Secure Partition writable state is narrowed by the Secure MPU, but all service -threads execute shared read/execute text from one linked image. HSM, vault, and -attestation also share a keystore data band. A defect in trusted shared code or -an allowed shared backend can therefore affect more than one service. +threads execute shared read/execute text from one linked image. The selected +crypto engine, vault, and attestation also share a keystore data band. A defect +in trusted shared code or an allowed shared backend can therefore affect more +than one service. ### Privileged handlers remain security-critical diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index cc2bf41f..a0ac5357 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -3,6 +3,7 @@ - [[Home]] - [[Getting Started]] - [[Architecture]] +- [[Crypto Engines]] - [[Security Model]] - [[Threat Model]] - [[API Reference]] diff --git a/include/wolftrust/crypto_native_client.h b/include/wolftrust/crypto_native_client.h new file mode 100644 index 00000000..5548ba7b --- /dev/null +++ b/include/wolftrust/crypto_native_client.h @@ -0,0 +1,51 @@ +/* crypto_native_client.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_CRYPTO_NATIVE_CLIENT_H +#define WOLFTRUST_CRYPTO_NATIVE_CLIENT_H + +#include +#include + +#include "psa/error.h" +#include "wolftrust/services/crypto_native.h" + +/* Non-secure client for the native crypto wire (WT_ENGINE=native): each + * request is one synchronous psa_call to the SERVICE_HSM door carrying + * [wt_crypto_wire_req_t][payload]; the response is [int32_t status][payload]. + * The connection is made lazily and healed on demand, mirroring the wolfHSM + * client glue's restart tolerance. */ + +/* One native wire round trip. out may be NULL when the op returns no + * payload. Returns the secure-side psa_status_t, or a client-side + * PSA_ERROR_* when the door is unreachable. */ +psa_status_t wt_crypto_native_call(const wt_crypto_wire_req_t* hdr, + const uint8_t* payload, + size_t payload_len, uint8_t* out, + size_t out_cap, size_t* out_len); + +/* Vault-domain randomness over the wire, chunked to WT_CRYPTO_RANDOM_MAX. */ +psa_status_t wt_crypto_native_random(uint8_t* out, size_t len); + +/* wolfCrypt CUSTOM_RAND_GENERATE_BLOCK hook: NS DRBG seeds come from the + * secure vault RNG, never a local entropy source. Returns 0 on success. */ +int wolftrust_guest_rng_stub(unsigned char* output, unsigned int sz); + +#endif /* WOLFTRUST_CRYPTO_NATIVE_CLIENT_H */ diff --git a/include/wolftrust/nvm_store.h b/include/wolftrust/nvm_store.h new file mode 100644 index 00000000..36fa2fd2 --- /dev/null +++ b/include/wolftrust/nvm_store.h @@ -0,0 +1,50 @@ +/* nvm_store.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_NVM_STORE_H +#define WOLFTRUST_NVM_STORE_H + +#include "wolfhsm/wh_nvm.h" +#include "wolftrust/sync/mutex.h" + +/* Engine-independent secure NVM store: the shared flash-backed object store + * and its serialisation lock, linked in both crypto engines. The lifecycle + * latch and rollback floors (wt_hsm_set_boot_lifecycle, wt_hsm_rollback_*) + * ride this store and keep their public names in services/hsm.h. */ + +/* The single shared NVM context and its serialisation mutex. The mutex must + * be initialised (wt_mutex_init) before wt_nvm_store_bind wires it in. */ +extern whNvmContext g_wt_nvm_ctx; +extern wt_mutex_t g_wt_nvm_lock_mutex; + +/* Wire the port flash callbacks and the lock config, then initialise the + * shared NVM context. Re-callable: the vault format/recovery path runs it + * again against a freshly erased pool. Returns a WH_ERROR_* code. */ +int wt_nvm_store_bind(void); + +/* Nonzero when the wolfBoot-reported lifecycle permits destructive store + * recovery (ASSEMBLY_AND_TEST or PSA_ROT_PROVISIONING); unset stays locked. */ +int wt_nvm_reformat_allowed(void); + +/* Record that the store was reformatted this boot (observability, reported + * by wt_hsm_vault_was_reformatted). */ +void wt_nvm_mark_reformatted(void); + +#endif /* WOLFTRUST_NVM_STORE_H */ diff --git a/include/wolftrust/services/crypto_native.h b/include/wolftrust/services/crypto_native.h new file mode 100644 index 00000000..d8686187 --- /dev/null +++ b/include/wolftrust/services/crypto_native.h @@ -0,0 +1,97 @@ +/* crypto_native.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_SERVICES_CRYPTO_NATIVE_H +#define WOLFTRUST_SERVICES_CRYPTO_NATIVE_H + +#include "wolftrust/services/vault_service.h" + +#include +#include + +/* Native crypto engine (WT_ENGINE=native): wolfCrypt dispatch behind the + * unchanged SERVICE_HSM door. The relay hands one opaque packet to + * wt_native_submit; keys are vault NVM objects served by the wolfCrypt key + * backend inside the privileged vault domain — the wolfHSM server, comm, + * and message layers are not linked. */ + +/* Native wire ops carried in wt_crypto_wire_req_t.op. Key ops address vault + * key objects namespaced (owner = SERVICE_HSM partition, sub = SPM-stamped + * NS client), so a client only ever reaches its own keys. */ +#define WT_CRYPTO_OP_KEY_GENERATE 1U +#define WT_CRYPTO_OP_KEY_IMPORT 2U +#define WT_CRYPTO_OP_KEY_EXPORT_PUBLIC 3U +#define WT_CRYPTO_OP_KEY_SIGN 4U +#define WT_CRYPTO_OP_KEY_VERIFY 5U +#define WT_CRYPTO_OP_KEY_ENCRYPT 6U +#define WT_CRYPTO_OP_KEY_DECRYPT 7U +#define WT_CRYPTO_OP_KEY_DESTROY 8U +#define WT_CRYPTO_OP_RANDOM 9U +#define WT_CRYPTO_OP_HASH 10U + +#define WT_CRYPTO_RANDOM_MAX 256U + +/* One request packet: [wt_crypto_wire_req_t][payload], bounded by the relay + * copy buffer (WT_HSM_RELAY_MSG_MAX). usage carries the WT_VAULT_KEY_USAGE_* + * bits on generate/import and the requested byte count on RANDOM; key_type + * uses the WT_VAULT_KEY_* encodings. The response packet is + * [int32_t psa_status][payload]. */ +typedef struct wt_crypto_wire_req { + uint64_t uid; + uint32_t op; + uint32_t usage; + uint32_t key_type; + uint32_t reserved; +} wt_crypto_wire_req_t; + +/* Boot bring-up for the native engine: wolfCrypt, the port flash, the shared + * NVM store, and the vault storage/seal/key/RNG bindings. The native peer of + * wt_hsm_init; failure is fatal — the caller should panic. */ +int wt_native_init(void); + +/* Fault-recovery entry for the native engine: invalidate operation-scoped + * crypto state (the shared vault DRBG) so a restarted relay partition serves + * from clean state instead of a generator torn mid-draw. */ +void wt_native_reinit(void); + +/* SERVICE_HSM submit hook (matches wt_hsm_relay_submit_fn): service one + * native wire packet. submit_ctx carries the owning partition id. */ +int wt_native_submit(void* submit_ctx, int32_t client_id, const uint8_t* req, + size_t req_len, uint8_t* resp, size_t resp_cap, + size_t* resp_len); + +/* wolfCrypt key-op backend over vault NVM objects (WT-FFM-0046): private + * material is stored SENSITIVE + NONEXPORTABLE and never leaves the + * privileged vault domain. */ +struct whNvmContext_t; +int wt_hsm_keyvault_init(struct whNvmContext_t* nvm); +extern const wt_vault_key_backend_t wt_hsm_key_backend; + +/* Destroy one key object in the caller's namespace. Refuses anything that + * is not a key, so the key wire can never delete a storage object. */ +psa_status_t wt_hsm_keyvault_destroy(int32_t owner, int32_t sub, uint64_t uid); + +/* Vault-domain DRBG for the RANDOM face and key generation. */ +psa_status_t wt_hsm_keyvault_random(uint8_t* out, size_t len); + +/* Invalidate the vault DRBG so it re-seeds on next use (fault recovery). */ +void wt_hsm_keyvault_reset(void); + +#endif /* WOLFTRUST_SERVICES_CRYPTO_NATIVE_H */ diff --git a/include/wolftrust/services/hsm.h b/include/wolftrust/services/hsm.h index d7728b4b..f7c3d1d3 100644 --- a/include/wolftrust/services/hsm.h +++ b/include/wolftrust/services/hsm.h @@ -211,6 +211,14 @@ void wt_hsm_vault_make_label(uint8_t* label, int32_t owner, int32_t sub, uint64_t uid, uint32_t flags); uint32_t wt_hsm_vault_flags_of(const uint8_t* label); +/* Reserve pool space for a shared-store object add of len bytes, holding back + * the counter-table headroom and compacting reclaimable entries first. A + * writer must call this before wh_Nvm_AddObject so a doomed add on a full pool + * cannot fail mid-write and poison later adds, and so key churn cannot starve + * the seal-counter table or the rollback floor. Returns INSUFFICIENT_STORAGE + * when even reclaim cannot make room. */ +psa_status_t wt_hsm_vault_reserve_object(whNvmSize len); + /* Vault-domain RNG (WT-FFM-0054): entropy for SERVICE_VAULT's RANDOM face, * produced by a wolfCrypt DRBG owned by the privileged vault domain. Installed * via wt_vault_service_set_rng at boot. Only linked into builds that carry diff --git a/mk/arch-armv8m.mk b/mk/arch-armv8m.mk index 62fdd494..c87a7e3c 100644 --- a/mk/arch-armv8m.mk +++ b/mk/arch-armv8m.mk @@ -64,4 +64,9 @@ define arch_image_checks echo "FAIL: expected $(NSC_COUNT) FF-M veneers, found $$n (WT-FFM-0057)" >&2; \ exit 1; \ fi + @if grep -E ' (malloc|free|calloc|realloc|_sbrk|_malloc_r|_free_r)$$' \ + $(BUILD_DIR)/nsc-syms.txt; then \ + echo "FAIL: heap allocator symbol in the zero-heap secure image" >&2; \ + exit 1; \ + fi endef diff --git a/mk/common.mk b/mk/common.mk index 0d2e7896..71b42169 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -22,7 +22,38 @@ WT_MAX_GUESTS ?= 2 # the SP_SMALL math switch; PSPLIM_S faults any real overflow, so this floor # is measured, not guessed. WT_CO_STACK_SIZE ?= 10240 -WT_ENGINE_HSM ?= 1 +# Secure crypto engine. native (the default) calls wolfCrypt directly; hsm +# links the wolfHSM server as a key-management add-on. Legacy WT_ENGINE_HSM +# values map onto the selector. +WT_ENGINE_LEGACY := +ifeq ($(WT_ENGINE_HSM),0) +WT_ENGINE_LEGACY := native +endif +ifeq ($(WT_ENGINE_HSM),1) +WT_ENGINE_LEGACY := hsm +endif +ifneq ($(WT_ENGINE_HSM),) +ifeq ($(WT_ENGINE_LEGACY),) +$(error unsupported WT_ENGINE_HSM='$(WT_ENGINE_HSM)' (want 0 or 1)) +endif +endif +ifneq ($(WT_ENGINE_LEGACY),) +ifneq ($(WT_ENGINE),) +ifneq ($(WT_ENGINE),$(WT_ENGINE_LEGACY)) +$(error conflicting engine selectors: WT_ENGINE=$(WT_ENGINE) but WT_ENGINE_HSM=$(WT_ENGINE_HSM) selects $(WT_ENGINE_LEGACY)) +endif +endif +WT_ENGINE := $(WT_ENGINE_LEGACY) +endif +WT_ENGINE ?= native +ifneq ($(words $(WT_ENGINE))/$(filter native hsm,$(WT_ENGINE)),1/$(WT_ENGINE)) +$(error unsupported WT_ENGINE='$(WT_ENGINE)' (want native or hsm)) +endif +ifeq ($(WT_ENGINE),hsm) +WT_ENGINE_HSM := 1 +else +WT_ENGINE_HSM := 0 +endif WT_ATTEST_COSE ?= 1 WT_FFM_NEGATIVE_PROBE ?= 0 WT_KEYSTORE_NEG_PROBE ?= 0 @@ -52,9 +83,16 @@ WT_VNET_UNKNOWN_UCAST_FLOOD ?= 0 HSM_INCLUDES := -I$(WOLFHSM_DIR) -I$(WOLFSSL_DIR) -I$(BUILD_DIR) HSM_INCLUDES_SECURE := $(HSM_INCLUDES) -I$(WOLFHAL_DIR) -I$(abspath $(WOLFHSM_RUNNER_DIR)) HSM_DEFS_SECURE := -DWOLFSSL_USER_SETTINGS -DWOLFHSM_CFG \ - -DWOLF_CRYPTO_CB -UNO_CODING \ - -DWC_RESEED_INTERVAL=1000000 -DWT_ENGINE_HSM=$(WT_ENGINE_HSM) \ + -UNO_CODING \ + -DWC_RESEED_INTERVAL=1000000 \ $(ARCH_HSM_DEFS) +ifeq ($(WT_ENGINE),hsm) +HSM_DEFS_SECURE += -DWOLF_CRYPTO_CB -DWT_ENGINE_HSM=1 +else +# Native links only the wolfHSM NVM object store; NO_CRYPTO drops the server's +# wolfCrypt dependency (and its WOLF_CRYPTO_CB requirement). +HSM_DEFS_SECURE += -DWT_ENGINE_NATIVE=1 -DWOLFHSM_CFG_NO_CRYPTO +endif ifeq ($(WT_ATTEST_COSE),1) SECURE_CFLAGS_COSE := -I$(WOLFCOSE_DIR)/include \ @@ -197,6 +235,13 @@ WOLFHSM_SECURE_SRCS := \ $(WOLFHSM_DIR)/src/wh_crypto.c \ $(WOLFHSM_DIR)/src/wh_keyid.c +# Native engine keeps only the self-contained NVM object store (vault/ITS/PS/FWU +# ride it); the wolfHSM server, comm, and message layers are hsm-only. +ifeq ($(WT_ENGINE),native) +WOLFHSM_SECURE_SRCS := $(filter %/wh_nvm.c %/wh_nvm_flash.c %/wh_flash_unit.c \ + %/wh_lock.c %/wh_utils.c %/wh_keyid.c,$(WOLFHSM_SECURE_SRCS)) +endif + WOLFCRYPT_SECURE_SRCS := \ $(WOLFSSL_DIR)/wolfcrypt/src/aes.c \ $(WOLFSSL_DIR)/wolfcrypt/src/asn.c \ @@ -226,12 +271,24 @@ WT_SECURE_EXTRA_SRCS := \ $(TARGET_EXTRA_SRCS) \ $(wildcard $(WOLFHSM_RUNNER_DIR)/libc_stubs.c) \ $(wildcard $(ROOT)/src/services/wolfhsm/*.c) \ + $(ROOT)/src/services/nvm_store.c \ $(ROOT)/src/services/boot_handoff.c \ $(ROOT)/src/services/hsm_relay_service.c \ $(ROOT)/src/services/storage_service.c \ $(ROOT)/src/services/fwu_service.c \ $(ROOT)/src/services/vault_service.c +# Engine split: wt_hsm.c drives the wolfHSM server (hsm engine only); the +# native engine dispatches wolfCrypt directly behind the same SERVICE_HSM +# door and keeps the server-free vault/seal/lock glue over the shared store. +ifeq ($(WT_ENGINE),native) +WT_SECURE_EXTRA_SRCS := $(filter-out %/wolfhsm/wt_hsm.c,$(WT_SECURE_EXTRA_SRCS)) +WT_SECURE_EXTRA_SRCS += \ + $(ROOT)/src/services/native/crypto_native.c \ + $(ROOT)/src/services/native/native_wire.c \ + $(ROOT)/src/services/native/keyvault.c +endif + ifeq ($(WT_ATTEST_COSE),1) WT_SECURE_EXTRA_SRCS += \ $(ROOT)/src/services/attestation_cose.c \ @@ -1300,7 +1357,9 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_SHARED_UART=$(WT_SHARED_UART)' \ 'WT_TIMESLICE_MS=$(WT_TIMESLICE_MS)' \ 'WT_GUEST_CORE_CLOCK_HZ=$(WT_GUEST_CORE_CLOCK_HZ)' \ - 'WT_GUEST_UART_CLOCK_HZ=$(WT_GUEST_UART_CLOCK_HZ)' > "$$tmp"; \ + 'WT_GUEST_UART_CLOCK_HZ=$(WT_GUEST_UART_CLOCK_HZ)' \ + 'WT_EXTRA_CFLAGS=$(WT_EXTRA_CFLAGS)' \ + 'WT_EXTRA_LDFLAGS=$(WT_EXTRA_LDFLAGS)' > "$$tmp"; \ if test -f "$@" && cmp -s "$$tmp" "$@"; then \ rm -f "$$tmp"; \ else \ @@ -1346,6 +1405,9 @@ $(BUILD_DIR)/wt_sec_%.o: $(WOLFHAL_DIR)/src/rng/%.c $(WOLFHSM_CFG_H) $(BUILD_MOD $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/services/wolfhsm/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< +$(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/services/native/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/vnet/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< diff --git a/src/arch/armv8m/guest_context_armv8m.c b/src/arch/armv8m/guest_context_armv8m.c index fff85819..89f108d7 100644 --- a/src/arch/armv8m/guest_context_armv8m.c +++ b/src/arch/armv8m/guest_context_armv8m.c @@ -116,7 +116,6 @@ void wt_arch_init(void) WT_SCB_AIRCR_S = WT_SCB_AIRCR_VECTKEY | (WT_SCB_AIRCR_S & WT_SCB_AIRCR_CFG_MASK) | WT_SCB_AIRCR_SYSRESETREQS; -#ifdef WT_ENGINE_HSM /* PendSV and the secure SysTick must share the lowest priority: SysTick at * the reset default (0, highest) would preempt PendSV mid-coroutine switch, * and a nested exception return off the half-saved frame faults INVPC. @@ -124,7 +123,6 @@ void wt_arch_init(void) WT_SCB_SHPR3_S |= (0xFFu << WT_SCB_SHPR3_PENDSV_SHIFT) | (0xFFu << WT_SCB_SHPR3_SYSTICK_SHIFT); WT_SCB_ICSR_S = WT_SCB_ICSR_PENDSVCLR; -#endif g_switch_count = 0u; g_active_guest = UINT32_MAX; } @@ -699,7 +697,6 @@ uint32_t wt_arch_active_guest_id(void) __attribute__((naked)) void SecureFault_Handler(void) { __asm volatile( -#ifdef WT_ENGINE_HSM /* EXC_RETURN bit6 = secure frame, bit3 = Thread. A fault from Secure * Thread with a live tasklet is a Secure Partition/tasklet fault, not * a guest escalation: blaming the scheduled NS guest would restart an @@ -718,7 +715,6 @@ __attribute__((naked)) void SecureFault_Handler(void) "beq 1f \n" "b wt_armv8m_tasklet_fault_entry \n" "1: \n" -#endif "mov r2, sp \n" "ldr r1, =g_secure_entry_sp \n" "str r2, [r1] \n" diff --git a/src/arch/armv8m/sp_fault_armv8m.c b/src/arch/armv8m/sp_fault_armv8m.c index 1ca3a996..e7ae92ec 100644 --- a/src/arch/armv8m/sp_fault_armv8m.c +++ b/src/arch/armv8m/sp_fault_armv8m.c @@ -32,8 +32,8 @@ #include -#ifdef WT_ENGINE_HSM #include "wolftrust/sched/tasklet.h" +#ifdef WT_ENGINE_HSM #include "wolftrust/services/hsm.h" #endif @@ -48,7 +48,6 @@ static volatile uint32_t g_tasklet_fault_icsr; static volatile uint32_t g_tasklet_fault_co; static volatile uint32_t g_tasklet_fault_co_sp; -#ifdef WT_ENGINE_HSM /* ----------------------------------------------------------------------- * Secure-side tasklet fault path. * @@ -140,10 +139,12 @@ static void wt_secure_tasklet_fault_dispatch(uint32_t *frame, return; } +#ifdef WT_ENGINE_HSM wt_guest_id_t gid = wt_hsm_guest_for_tasklet(tasklet); if (gid < WT_MAX_GUESTS) { (void)wt_hsm_signal_fault(gid); } +#endif wt_tasklet_mark_faulted(tasklet); } @@ -202,4 +203,3 @@ __attribute__((naked)) void UsageFault_Handler(void) { __asm volatile("b wt_armv8m_tasklet_fault_entry \n"); } -#endif /* WT_ENGINE_HSM */ diff --git a/src/arch/common/spm_gate_core.c b/src/arch/common/spm_gate_core.c index 89c2ea7e..7c864b43 100644 --- a/src/arch/common/spm_gate_core.c +++ b/src/arch/common/spm_gate_core.c @@ -41,6 +41,7 @@ #include "wolftrust/services/fwu_service.h" #include "wolftrust/services/hsm.h" #include "wolftrust/services/hsm_relay.h" +#include "wolftrust/services/crypto_native.h" #include "wolftrust/sync/mutex.h" #include "wolftrust/services/storage_service.h" #include "wolftrust/services/vault_service.h" @@ -51,6 +52,8 @@ #include "wolftrust/sp_recovery.h" #include "wolftrust/spm_gate.h" +#include + /* Generated in every secure build; the ITS entry embeds SERVICE_VAULT_SID as * a code constant — the unprivileged loop cannot read SPM RAM at runtime. */ #include "psa_manifest/pid.h" @@ -181,15 +184,21 @@ static void wt_spm_fault_release(void* ctx) { wt_spm_fault_ctx_t* c = (wt_spm_fault_ctx_t*)ctx; -#if defined(WT_ENGINE_HSM) + /* The shared NVM lock is engine-independent (nvm_store.c): a faulted + * holder must release it in both engines or later acquirers deadlock. */ wt_hsm_release_locks(c->slot->co); +#if defined(WT_ENGINE_HSM) if (c->slot->partition_id == g_spm_hsm_partition_id) { /* The fault may have torn a per-guest server mid-request; rebuild * them all. Fails closed — a guest whose re-init fails stays down. */ (void)wt_hsm_relay_reinit_servers(); } -#else - (void)c; +#elif defined(WT_ENGINE_NATIVE) + /* The native vault DRBG is process-global — the HSM relay, SERVICE_VAULT + * (RANDOM and key ops), and attestation signing all draw from the one + * g_kv_rng. Any recovered fault may have torn it mid-draw, so invalidate + * it unconditionally; the next draw re-seeds and fails closed on error. */ + wt_native_reinit(); #endif } diff --git a/src/boot.c b/src/boot.c index 6d5576b3..475b44ed 100644 --- a/src/boot.c +++ b/src/boot.c @@ -33,9 +33,10 @@ #include "wolftrust/platform.h" #include "wolftrust/services/hsm.h" #include "wolftrust/services/initial_attestation.h" -#ifdef WT_ENGINE_HSM -#include "wolftrust/sched/tasklet.h" #include "wolfhsm/wh_error.h" +#include "wolftrust/sched/tasklet.h" +#ifndef WT_ENGINE_HSM +#include "wolftrust/services/crypto_native.h" #endif #include @@ -56,12 +57,8 @@ void wt_boot_run(void) handoffRet = wt_boot_handoff_consume(&bootHandoff); wt_boot_handoff_clear(); #endif -#ifdef WT_ENGINE_HSM - /* Bring up the secure-side wolfHSM service before dispatching guests: - * 1. tasklet scheduler (provides the bootstrap context) - * 2. shared wolfCrypt + NVM + lock - * 3. one transport + server context + tasklet per guest - * Any failure here is fatal because guests require this engine. */ + /* Coroutine runtime first: the SP scheduler and (in the hsm engine) the + * per-guest server tasklets both ride it. */ wt_tasklet_init(); #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) /* Gate vault auto-reformat on the wolfBoot-reported lifecycle before the @@ -71,7 +68,11 @@ void wt_boot_run(void) wt_hsm_set_boot_lifecycle(bootHandoff.lifecycle); } #endif +#ifdef WT_ENGINE_HSM if (wt_hsm_init() != 0) wt_platform_panic(); +#else + if (wt_native_init() != 0) wt_platform_panic(); +#endif /* WT-FFM-0050: the vault NVM is live and no guest has dispatched, so the * monotonic version floors gate every domain now. A missing handoff * reports version zero, which fails closed once a floor is armed. */ @@ -81,6 +82,7 @@ void wt_boot_run(void) #else (void)wt_hsm_rollback_enforce(0u); #endif +#ifdef WT_ENGINE_HSM /* WT-FFM-0054: every guest server binds the secure relay capture * transport — packets arrive only through SERVICE_HSM's mediated * psa_call path, never a shared NS-RAM window. */ @@ -93,6 +95,7 @@ void wt_boot_run(void) wt_platform_panic(); } } +#endif #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) if (wt_hsm_attest_bootstrap() != WH_ERROR_OK) { /* The vault could not be provisioned and auto-reformat was not @@ -117,12 +120,11 @@ void wt_boot_run(void) wt_ffm_set_lifecycle(wt_ffm_boot_runtime_mut(), bootHandoff.lifecycle); } #endif - /* P1t: crypto SP becomes a scheduled unprivileged coroutine now that - * the tasklet scheduler exists. Fail closed — guests depend on it. */ + /* P1t: the service partitions become scheduled unprivileged coroutines. + * Fail closed — guests depend on them. */ if (wt_ffm_boot_start_sched() != WT_FFM_SUCCESS) { wt_platform_panic(); } -#endif #if defined(WT_REMEASURE_PROBE) wt_platform_remeasure_probe(); #endif diff --git a/src/client/crypto_native_client.c b/src/client/crypto_native_client.c new file mode 100644 index 00000000..c34adf0e --- /dev/null +++ b/src/client/crypto_native_client.c @@ -0,0 +1,173 @@ +/* crypto_native_client.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* OS-neutral Non-secure client for the native crypto wire: one mediated + * psa_call to SERVICE_HSM per request, no shared-RAM window, no raw veneer. + * A Secure Partition restart invalidates the cached handle; every call + * reconnects on demand so the client heals across recovery windows. */ + +#include +#include +#include + +#include "psa/client.h" +#include "wolftrust/services/hsm_relay.h" +#include "wolftrust/crypto_native_client.h" + +/* SERVICE_HSM SID from the platform manifest, same door the wolfHSM client + * glue connects to in the hsm engine. */ +#ifndef WT_CRYPTO_NATIVE_SID +#define WT_CRYPTO_NATIVE_SID 4102u +#endif +#ifndef WT_CRYPTO_NATIVE_SID_VERSION +#define WT_CRYPTO_NATIVE_SID_VERSION 1u +#endif + +/* A Secure Partition restart window (WT-SYS-0008 graceful recovery) makes + * the door refuse connects and calls for a short while; every consumer of + * this client — including a wc_InitRng seed fetch mid-keygen — must ride it + * out, so the round trip below retries with bounded patience, mirroring the + * wolfHSM client glue's heal-on-demand. */ +#ifndef WT_CRYPTO_NATIVE_RETRIES +#define WT_CRYPTO_NATIVE_RETRIES 64 +#endif + +static psa_handle_t g_native_handle; + +static int wt_crypto_native_ensure_connected(void) +{ + if (g_native_handle > 0) { + return 0; + } + g_native_handle = psa_connect(WT_CRYPTO_NATIVE_SID, + WT_CRYPTO_NATIVE_SID_VERSION); + return (g_native_handle > 0) ? 0 : -1; +} + +psa_status_t wt_crypto_native_call(const wt_crypto_wire_req_t* hdr, + const uint8_t* payload, + size_t payload_len, uint8_t* out, + size_t out_cap, size_t* out_len) +{ + uint8_t req[WT_HSM_RELAY_MSG_MAX]; + uint8_t resp[WT_HSM_RELAY_MSG_MAX]; + psa_invec in_vec; + psa_outvec out_vec; + psa_status_t status; + int32_t wire_status; + size_t got; + int attempt; + + /* Subtraction, not sizeof(*hdr) + payload_len, so a payload_len near + * SIZE_MAX cannot wrap the bound and let the memcpy overrun req. The + * header always fits, so sizeof(req) - sizeof(*hdr) never underflows. */ + if (hdr == NULL || (payload == NULL && payload_len != 0U) || + payload_len > sizeof(req) - sizeof(*hdr)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + (void)memcpy(req, hdr, sizeof(*hdr)); + if (payload_len != 0U) { + (void)memcpy(req + sizeof(*hdr), payload, payload_len); + } + + status = PSA_ERROR_CONNECTION_REFUSED; + for (attempt = 0; attempt < WT_CRYPTO_NATIVE_RETRIES; attempt++) { + if (wt_crypto_native_ensure_connected() != 0) { + continue; + } + in_vec.base = req; + in_vec.len = sizeof(*hdr) + payload_len; + out_vec.base = resp; + out_vec.len = sizeof(resp); + status = psa_call(g_native_handle, PSA_IPC_CALL, &in_vec, 1U, + &out_vec, 1U); + if (status == PSA_SUCCESS) { + break; + } + psa_close(g_native_handle); + g_native_handle = 0; + /* A call that failed after reaching the door is ambiguous: a mutating + * op may have committed to NVM before its reply was lost, so replaying + * it would corrupt the store or misreport the result. Surface the + * failure; only connect setup and idempotent ops are safe to retry. */ + if (hdr->op == WT_CRYPTO_OP_KEY_GENERATE || + hdr->op == WT_CRYPTO_OP_KEY_IMPORT || + hdr->op == WT_CRYPTO_OP_KEY_DESTROY) { + return status; + } + } + if (status != PSA_SUCCESS) { + return status; + } + if (out_vec.len < sizeof(wire_status)) { + return PSA_ERROR_COMMUNICATION_FAILURE; + } + (void)memcpy(&wire_status, resp, sizeof(wire_status)); + got = out_vec.len - sizeof(wire_status); + if (out != NULL && got != 0U) { + if (got > out_cap) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + (void)memcpy(out, resp + sizeof(wire_status), got); + } + if (out_len != NULL) { + *out_len = got; + } + return (psa_status_t)wire_status; +} + +psa_status_t wt_crypto_native_random(uint8_t* out, size_t len) +{ + wt_crypto_wire_req_t hdr; + size_t done = 0U; + size_t chunk; + size_t got = 0U; + psa_status_t status = PSA_SUCCESS; + + if (out == NULL || len == 0U) { + return PSA_ERROR_INVALID_ARGUMENT; + } + while (done < len) { + chunk = len - done; + if (chunk > WT_CRYPTO_RANDOM_MAX) { + chunk = WT_CRYPTO_RANDOM_MAX; + } + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.op = WT_CRYPTO_OP_RANDOM; + hdr.usage = (uint32_t)chunk; + status = wt_crypto_native_call(&hdr, NULL, 0U, out + done, chunk, + &got); + if (status != PSA_SUCCESS) { + break; + } + if (got != chunk) { + status = PSA_ERROR_COMMUNICATION_FAILURE; + break; + } + done += chunk; + } + return status; +} + +int wolftrust_guest_rng_stub(unsigned char* output, unsigned int sz) +{ + return (wt_crypto_native_random(output, (size_t)sz) == PSA_SUCCESS) ? + 0 : -1; +} diff --git a/src/services/hsm_relay_service.c b/src/services/hsm_relay_service.c index b3d54edd..25ccfb8f 100644 --- a/src/services/hsm_relay_service.c +++ b/src/services/hsm_relay_service.c @@ -108,9 +108,21 @@ static int wt_hsm_relay_write_resp(wt_ffm_runtime_t* runtime, return WT_FFM_SUCCESS; } -static psa_status_t wt_hsm_relay_call(wt_ffm_runtime_t* runtime, - int32_t partition_id, - const psa_msg_t* msg) +/* Scrub a relay copy buffer. volatile so the clear is not optimized away; the + * relay unit is port-free and does not link wolfCrypt's ForceZero. */ +static void wt_hsm_relay_zeroize(uint8_t* buf, size_t len) +{ + volatile uint8_t* p = buf; + size_t i; + + for (i = 0U; i < len; i++) { + p[i] = 0U; + } +} + +static psa_status_t wt_hsm_relay_call_inner(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) { size_t req_len = 0U; size_t resp_len = 0U; @@ -157,6 +169,21 @@ static psa_status_t wt_hsm_relay_call(wt_ffm_runtime_t* runtime, return PSA_SUCCESS; } +/* Single cleanup path (key hygiene): the relay copy buffers live in the shared + * keystore band and carry imported key material and decrypted plaintext, so + * scrub both after every call, on success and on every error path. */ +static psa_status_t wt_hsm_relay_call(wt_ffm_runtime_t* runtime, + int32_t partition_id, + const psa_msg_t* msg) +{ + psa_status_t status; + + status = wt_hsm_relay_call_inner(runtime, partition_id, msg); + wt_hsm_relay_zeroize(g_relay_io.req, sizeof(g_relay_io.req)); + wt_hsm_relay_zeroize(g_relay_io.resp, sizeof(g_relay_io.resp)); + return status; +} + int wt_hsm_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, int32_t partition_id) { diff --git a/src/services/native/crypto_native.c b/src/services/native/crypto_native.c new file mode 100644 index 00000000..e70f6689 --- /dev/null +++ b/src/services/native/crypto_native.c @@ -0,0 +1,246 @@ +/* crypto_native.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* + * Native crypto engine (WT_ENGINE=native): direct wolfCrypt dispatch behind + * the unchanged SERVICE_HSM door. wt_native_init is the boot bring-up peer + * of wt_hsm_init; wt_native_submit services the native wire at the relay + * seam; the attestation signer runs wc_ecc against a vault-stored IAK. + */ + +/* wolfCrypt settings must come first. */ +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/error-crypt.h" + +#include "wolfhsm/wh_error.h" + +#include "wolftrust/types.h" +#include "wolftrust/sync/mutex.h" +#include "wolftrust/nvm_store.h" +#include "wolftrust/services/hsm.h" +#include "wolftrust/services/hsm_relay.h" +#include "wolftrust/services/vault_service.h" +#include "wolftrust/services/crypto_native.h" + +#include "wolftrust/port_nvm.h" +#include "psa/lifecycle.h" + +#include +#include +#include + +/* The IAK vault home: owner/sub 0 is reachable by no SPM-stamped caller + * (partitions are positive, NS clients negative), so only the boot and + * attestation paths below can address it. */ +#define WT_NATIVE_IAK_OWNER 0 +#define WT_NATIVE_IAK_SUB 0 +#define WT_NATIVE_IAK_UID 0xF0u + +static bool g_native_attest_ready; + +int wt_native_init(void) +{ + int rc; + + rc = wolfCrypt_Init(); + if (rc != 0) { + return rc; + } + + rc = g_wt_hsm_flash_cb.Init(wt_hsm_flash_context(), + wt_hsm_flash_config()); + if (rc != 0) { + return rc; + } + + /* Initialise the shared NVM lock once, before wh_Nvm_Init wires it in. */ + wt_mutex_init(&g_wt_nvm_lock_mutex); + + rc = wt_nvm_store_bind(); + if (rc != WH_ERROR_OK) { + return rc; + } + + if (wt_hsm_vault_init(&g_wt_nvm_ctx) == 0) { + wt_vault_service_set_backend(&wt_hsm_vault_backend); + if (wt_hsm_seal_init(&g_wt_nvm_ctx) == 0) { + wt_hsm_vault_set_sealer(&wt_hsm_sealer); + } + else { + wt_hsm_vault_set_sealer(NULL); + } + if (wt_hsm_keyvault_init(&g_wt_nvm_ctx) == 0) { + wt_vault_service_set_key_backend(&wt_hsm_key_backend); + } + wt_vault_service_set_rng(wt_hsm_keyvault_random); + } + + return 0; +} + +/* Native-engine fault-recovery hook (WT-SYS-0008): invalidate the shared vault + * DRBG a torn request may have left mid-draw so the restart re-seeds from clean + * state. Per-operation wolfCrypt contexts are stack-local and die with the + * scrubbed coroutine, so the DRBG is the only mutable crypto state to reset. */ +void wt_native_reinit(void) +{ + wt_hsm_keyvault_reset(); +} + +/* ========================================================================= + * Attestation (native): the IAK is a vault key object, provisioned at boot + * and exercised through the key backend so private material never leaves + * the privileged vault domain. Same wire forms as the hsm engine: 64-byte + * r||s signatures, 65-byte X9.63 public point. + * ====================================================================== */ +static psa_status_t wt_native_iak_generate(void) +{ + return wt_hsm_key_backend.generate(WT_NATIVE_IAK_OWNER, + WT_NATIVE_IAK_SUB, WT_NATIVE_IAK_UID, + WT_VAULT_KEY_P256, + WT_VAULT_KEY_USAGE_SIGN | + WT_VAULT_KEY_USAGE_VERIFY); +} + +static psa_status_t wt_native_iak_export(void) +{ + uint8_t publicKey[WT_VAULT_KEY_PUB_LEN]; + size_t publicKeySize = 0U; + + return wt_hsm_key_backend.export_public(WT_NATIVE_IAK_OWNER, + WT_NATIVE_IAK_SUB, + WT_NATIVE_IAK_UID, publicKey, + sizeof(publicKey), + &publicKeySize); +} + +#if defined(WT_VAULT_FOREIGN_PROBE) +/* Negative test, same contract as the hsm engine: make the first provisioning + * look blocked so the real recovery path runs exactly once (self-heal when + * unlocked, fail closed when WT_VAULT_PROBE_SECURED forces a locked + * lifecycle). */ +static int g_native_foreign_probe_fired; +#endif + +int wt_hsm_attest_init(void) +{ + psa_status_t status; + +#if defined(WT_VAULT_FOREIGN_PROBE) && defined(WT_VAULT_PROBE_SECURED) + wt_hsm_set_boot_lifecycle(PSA_LIFECYCLE_SECURED); +#endif + if (g_native_attest_ready) { + return WH_ERROR_OK; + } + status = wt_native_iak_export(); +#if defined(WT_VAULT_FOREIGN_PROBE) + if (g_native_foreign_probe_fired == 0) { + status = PSA_ERROR_NOT_PERMITTED; + } +#endif + if (status != PSA_SUCCESS && wt_nvm_reformat_allowed() != 0) { + /* An unreadable IAK (absent, foreign, or corrupt) is (re)provisioned + * here, but only while the lifecycle still permits it. A SECURED + * device must never silently mint a fresh attestation identity for an + * absent IAK (e.g. across an hsm-to-native engine migration): it falls + * through and fails closed, so attestation degrades rather than the + * device adopting a new key that pinned verifiers would reject. */ + status = wt_native_iak_generate(); +#if defined(WT_VAULT_FOREIGN_PROBE) + if (g_native_foreign_probe_fired == 0) { + g_native_foreign_probe_fired = 1; + status = PSA_ERROR_NOT_PERMITTED; + } +#endif + if (status != PSA_SUCCESS) { + if (wt_hsm_flash_format() == 0 && wt_native_init() == 0) { + wt_nvm_mark_reformatted(); + status = wt_native_iak_generate(); + } + } + if (status == PSA_SUCCESS) { + status = wt_native_iak_export(); + } + } + if (status != PSA_SUCCESS) { + return WH_ERROR_ABORTED; + } + g_native_attest_ready = true; + return WH_ERROR_OK; +} + +int wt_hsm_attest_bootstrap(void) +{ + /* No server tasklet to pump in the native engine: provisioning runs on + * the boot stack against the vault directly. */ + return wt_hsm_attest_init(); +} + +int wt_hsm_attest_sign(const uint8_t* digest, size_t digestSize, + uint8_t* signature, size_t signatureCapacity, + size_t* signatureSize) +{ + psa_status_t status; + + /* Same argument contract as the hsm engine: reject NULL/undersized + * buffers here so a bad caller cannot fault the key backend. */ + if (signatureSize == NULL) { + return WH_ERROR_BADARGS; + } + *signatureSize = 0u; + if (!g_native_attest_ready) { + return WH_ERROR_NOTREADY; + } + if (digest == NULL || digestSize != 32u || signature == NULL || + signatureCapacity < 64u) { + return WH_ERROR_BADARGS; + } + status = wt_hsm_key_backend.sign(WT_NATIVE_IAK_OWNER, WT_NATIVE_IAK_SUB, + WT_NATIVE_IAK_UID, digest, digestSize, + signature, signatureCapacity, + signatureSize); + return (status == PSA_SUCCESS) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} + +int wt_hsm_attest_public_key(uint8_t* publicKey, size_t publicKeyCapacity, + size_t* publicKeySize) +{ + psa_status_t status; + + /* Report the required size and reject NULL/undersized buffers as the hsm + * engine does, before forwarding to the key backend. */ + if (publicKeySize == NULL) { + return WH_ERROR_BADARGS; + } + *publicKeySize = WT_VAULT_KEY_PUB_LEN; + if (!g_native_attest_ready) { + return WH_ERROR_NOTREADY; + } + if (publicKey == NULL || publicKeyCapacity < WT_VAULT_KEY_PUB_LEN) { + return WH_ERROR_BADARGS; + } + status = wt_hsm_key_backend.export_public(WT_NATIVE_IAK_OWNER, + WT_NATIVE_IAK_SUB, + WT_NATIVE_IAK_UID, publicKey, + publicKeyCapacity, + publicKeySize); + return (status == PSA_SUCCESS) ? WH_ERROR_OK : WH_ERROR_ABORTED; +} diff --git a/src/services/native/keyvault.c b/src/services/native/keyvault.c new file mode 100644 index 00000000..6519e5c7 --- /dev/null +++ b/src/services/native/keyvault.c @@ -0,0 +1,582 @@ +/* keyvault.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* wolfCrypt key-op backend (WT-FFM-0046): every private-key computation runs + * inside the privileged vault domain against material that never leaves it. + * Keys are vault NVM objects (shared (owner, sub_owner, uid) directory) + * stored SENSITIVE + NONEXPORTABLE, so no *Checked NVM path can return the + * bytes, the storage face refuses key-flagged objects, and no wire op + * exports private material — three independent layers between a compromised + * Secure Partition and raw key bytes, which is the property TF-M's + * Crypto-partition-RAM key storage does not have. P-256 objects store + * [d 32][X9.63 public 65] with the public point derived once at creation; + * AES-256 objects store the raw 32-byte key. */ + +/* wolfCrypt settings must come first. */ +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/ecc.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/error-crypt.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_nvm.h" + +#include "wolftrust/services/vault_service.h" +#include "wolftrust/services/hsm.h" +#include "wolftrust/services/crypto_native.h" + +#include + +#define WT_HSM_KEY_P256_OBJ_LEN \ + (WT_VAULT_KEY_MATERIAL_LEN + WT_VAULT_KEY_PUB_LEN) +#define WT_HSM_KEY_AES_OBJ_LEN WT_VAULT_KEY_MATERIAL_LEN + +#define WT_HSM_KEY_DER_SIG_MAX 80U + +#define WT_HSM_KEY_LABEL(usage, type) \ + (WT_VAULT_FLAG_KEY | ((usage) & WT_VAULT_KEY_USAGE_MASK) | \ + (((uint32_t)(type) & 0xFFU) << 24)) +#define WT_HSM_KEY_TYPE_OF(flags) (((flags) >> 24) & 0xFFU) + +static whNvmContext* g_kv_nvm; +static WC_RNG g_kv_rng; +static int g_kv_rng_ready; + +int wt_hsm_keyvault_init(whNvmContext* nvm) +{ + if (nvm == NULL) { + return -1; + } + g_kv_nvm = nvm; + return 0; +} + +/* Fault-recovery hook (WT-SYS-0008): the vault DRBG is the only mutable crypto + * state that outlives a torn native request. Invalidate it so the restarted + * service re-seeds on next use; a re-seed failure then fails the op closed + * rather than drawing from a half-updated generator. */ +void wt_hsm_keyvault_reset(void) +{ + if (g_kv_rng_ready != 0) { + (void)wc_FreeRng(&g_kv_rng); + g_kv_rng_ready = 0; + } +} + +static void wt_hsm_kv_zeroize(uint8_t* buf, size_t len) +{ + volatile uint8_t* p = buf; + size_t i; + + for (i = 0U; i < len; i++) { + p[i] = 0U; + } +} + +static psa_status_t wt_hsm_kv_rng(WC_RNG** out) +{ + if (g_kv_rng_ready == 0) { + if (wc_InitRng_ex(&g_kv_rng, NULL, INVALID_DEVID) != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + g_kv_rng_ready = 1; + } + *out = &g_kv_rng; + return PSA_SUCCESS; +} + +/* Vault-domain randomness (WT-FFM-0054): serves the vault RANDOM face and + * the native wire so non-secure DRBG seeds come from the same vault RNG + * that generates key material. */ +psa_status_t wt_hsm_keyvault_random(uint8_t* out, size_t len) +{ + WC_RNG* rng; + psa_status_t status; + + if (out == NULL || len == 0U) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wt_hsm_kv_rng(&rng); + if (status != PSA_SUCCESS) { + return status; + } + if (wc_RNG_GenerateBlock(rng, out, (word32)len) != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + return PSA_SUCCESS; +} + +/* Store new key material at a free directory slot; refuses any existing + * object (storage or key) at the same (owner, sub, uid) with ALREADY_EXISTS, + * matching psa_import_key/psa_generate_key on an occupied persistent id. */ +static psa_status_t wt_hsm_kv_store(int32_t owner, int32_t sub, uint64_t uid, + uint32_t type, uint32_t usage, + const uint8_t* obj, size_t obj_len) +{ + whNvmMetadata meta; + whNvmId free_id = WH_NVM_ID_INVALID; + psa_status_t status; + int rc; + + status = wt_hsm_vault_lookup(owner, sub, uid, NULL, NULL, &free_id); + if (status == PSA_SUCCESS) { + return PSA_ERROR_ALREADY_EXISTS; + } + if (status != PSA_ERROR_DOES_NOT_EXIST) { + return status; + } + if (free_id == WH_NVM_ID_INVALID) { + return PSA_ERROR_INSUFFICIENT_STORAGE; + } + /* Reserve and reclaim before the add, exactly like wt_hsm_vault_set: key + * generate/destroy churn over the native wire must not fill the shared log + * or eat the headroom the seal-counter table and rollback floor need. */ + status = wt_hsm_vault_reserve_object((whNvmSize)obj_len); + if (status != PSA_SUCCESS) { + return status; + } + (void)memset(&meta, 0, sizeof(meta)); + meta.id = free_id; + meta.access = WH_NVM_ACCESS_ANY; + meta.flags = WH_NVM_FLAGS_SENSITIVE | WH_NVM_FLAGS_NONEXPORTABLE; + meta.len = (whNvmSize)obj_len; + wt_hsm_vault_make_label(meta.label, owner, sub, uid, + WT_HSM_KEY_LABEL(usage, type)); + rc = wh_Nvm_AddObject(g_kv_nvm, &meta, (whNvmSize)obj_len, obj); + return (rc == WH_ERROR_OK) ? PSA_SUCCESS : PSA_ERROR_STORAGE_FAILURE; +} + +/* Load key material for one operation: the object must be a key of the + * expected type carrying the required usage bit. The plain (non-Checked) + * read is the privileged domain's internal path — NONEXPORTABLE blocks + * every Checked consumer. Caller zeroizes obj after use. */ +static psa_status_t wt_hsm_kv_load(int32_t owner, int32_t sub, uint64_t uid, + uint32_t type, uint32_t usage_needed, + uint8_t* obj, size_t obj_len) +{ + whNvmMetadata meta; + whNvmId id = WH_NVM_ID_INVALID; + uint32_t flags; + psa_status_t status; + + if (g_kv_nvm == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + status = wt_hsm_vault_lookup(owner, sub, uid, &id, &meta, NULL); + if (status != PSA_SUCCESS) { + return status; + } + flags = wt_hsm_vault_flags_of(meta.label); + if ((flags & WT_VAULT_FLAG_KEY) == 0U) { + return PSA_ERROR_NOT_PERMITTED; + } + if (WT_HSM_KEY_TYPE_OF(flags) != type || + (flags & usage_needed) != usage_needed) { + return PSA_ERROR_NOT_PERMITTED; + } + if (meta.len != obj_len) { + return PSA_ERROR_STORAGE_FAILURE; + } + if (wh_Nvm_Read(g_kv_nvm, id, 0U, (whNvmSize)obj_len, obj) != + WH_ERROR_OK) { + return PSA_ERROR_STORAGE_FAILURE; + } + return PSA_SUCCESS; +} + +/* Derive [d][X9.63 public] from a P-256 private scalar. */ +static psa_status_t wt_hsm_kv_p256_object(const uint8_t* d, uint8_t* obj) +{ + ecc_key key; + word32 len; + int rc; + + rc = wc_ecc_init_ex(&key, NULL, INVALID_DEVID); + if (rc != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + rc = wc_ecc_import_private_key_ex(d, WT_VAULT_KEY_MATERIAL_LEN, NULL, 0U, + &key, ECC_SECP256R1); + if (rc == 0) { + rc = wc_ecc_make_pub(&key, NULL); + } + if (rc == 0) { + (void)memcpy(obj, d, WT_VAULT_KEY_MATERIAL_LEN); + len = WT_VAULT_KEY_PUB_LEN; + rc = wc_ecc_export_x963(&key, obj + WT_VAULT_KEY_MATERIAL_LEN, &len); + if (rc == 0 && len != WT_VAULT_KEY_PUB_LEN) { + rc = -1; + } + } + wc_ecc_free(&key); + return (rc == 0) ? PSA_SUCCESS : PSA_ERROR_GENERIC_ERROR; +} + +static psa_status_t wt_hsm_kv_generate(int32_t owner, int32_t sub, + uint64_t uid, uint32_t type, + uint32_t usage) +{ + uint8_t obj[WT_HSM_KEY_P256_OBJ_LEN]; + uint8_t d[WT_VAULT_KEY_MATERIAL_LEN]; + ecc_key key; + WC_RNG* rng; + word32 len; + psa_status_t status; + int rc; + + if (g_kv_nvm == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + if ((usage & ~WT_VAULT_KEY_USAGE_MASK) != 0U || usage == 0U) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wt_hsm_kv_rng(&rng); + if (status != PSA_SUCCESS) { + return status; + } + if (type == WT_VAULT_KEY_P256) { + rc = wc_ecc_init_ex(&key, NULL, INVALID_DEVID); + if (rc != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + rc = wc_ecc_make_key_ex(rng, 32, &key, ECC_SECP256R1); + if (rc == 0) { + len = WT_VAULT_KEY_MATERIAL_LEN; + rc = wc_ecc_export_private_only(&key, obj, &len); + } + if (rc == 0) { + len = WT_VAULT_KEY_PUB_LEN; + rc = wc_ecc_export_x963(&key, obj + WT_VAULT_KEY_MATERIAL_LEN, + &len); + } + wc_ecc_free(&key); + if (rc != 0) { + wt_hsm_kv_zeroize(obj, sizeof(obj)); + return PSA_ERROR_GENERIC_ERROR; + } + status = wt_hsm_kv_store(owner, sub, uid, type, usage, obj, + WT_HSM_KEY_P256_OBJ_LEN); + wt_hsm_kv_zeroize(obj, sizeof(obj)); + return status; + } + if (type == WT_VAULT_KEY_AES256) { + if (wc_RNG_GenerateBlock(rng, d, WT_VAULT_KEY_MATERIAL_LEN) != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + status = wt_hsm_kv_store(owner, sub, uid, type, usage, d, + WT_HSM_KEY_AES_OBJ_LEN); + wt_hsm_kv_zeroize(d, sizeof(d)); + return status; + } + return PSA_ERROR_NOT_SUPPORTED; +} + +static psa_status_t wt_hsm_kv_import(int32_t owner, int32_t sub, uint64_t uid, + uint32_t type, uint32_t usage, + const uint8_t* data, size_t len) +{ + uint8_t obj[WT_HSM_KEY_P256_OBJ_LEN]; + psa_status_t status; + + if (g_kv_nvm == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + if ((usage & ~WT_VAULT_KEY_USAGE_MASK) != 0U || usage == 0U || + len != WT_VAULT_KEY_MATERIAL_LEN) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (type == WT_VAULT_KEY_P256) { + status = wt_hsm_kv_p256_object(data, obj); + if (status == PSA_SUCCESS) { + status = wt_hsm_kv_store(owner, sub, uid, type, usage, obj, + WT_HSM_KEY_P256_OBJ_LEN); + } + wt_hsm_kv_zeroize(obj, sizeof(obj)); + return status; + } + if (type == WT_VAULT_KEY_AES256) { + return wt_hsm_kv_store(owner, sub, uid, type, usage, data, + WT_HSM_KEY_AES_OBJ_LEN); + } + return PSA_ERROR_NOT_SUPPORTED; +} + +/* Public halves are exportable by design; usage bits do not gate this. */ +static psa_status_t wt_hsm_kv_export_public(int32_t owner, int32_t sub, + uint64_t uid, uint8_t* out, + size_t cap, size_t* out_len) +{ + uint8_t obj[WT_HSM_KEY_P256_OBJ_LEN]; + psa_status_t status; + + if (cap < WT_VAULT_KEY_PUB_LEN) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + status = wt_hsm_kv_load(owner, sub, uid, WT_VAULT_KEY_P256, 0U, obj, + WT_HSM_KEY_P256_OBJ_LEN); + if (status != PSA_SUCCESS) { + return status; + } + (void)memcpy(out, obj + WT_VAULT_KEY_MATERIAL_LEN, WT_VAULT_KEY_PUB_LEN); + wt_hsm_kv_zeroize(obj, sizeof(obj)); + *out_len = WT_VAULT_KEY_PUB_LEN; + return PSA_SUCCESS; +} + +static psa_status_t wt_hsm_kv_sign(int32_t owner, int32_t sub, uint64_t uid, + const uint8_t* digest, size_t digest_len, + uint8_t* sig, size_t cap, size_t* out_len) +{ + uint8_t obj[WT_HSM_KEY_P256_OBJ_LEN]; + uint8_t der[WT_HSM_KEY_DER_SIG_MAX]; + uint8_t r[WT_VAULT_KEY_MATERIAL_LEN]; + uint8_t s[WT_VAULT_KEY_MATERIAL_LEN]; + ecc_key key; + WC_RNG* rng; + word32 der_len = sizeof(der); + word32 r_len = sizeof(r); + word32 s_len = sizeof(s); + psa_status_t status; + int rc; + + if (digest_len != WT_VAULT_KEY_DIGEST_LEN || + cap < WT_VAULT_KEY_SIG_LEN) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wt_hsm_kv_load(owner, sub, uid, WT_VAULT_KEY_P256, + WT_VAULT_KEY_USAGE_SIGN, obj, + WT_HSM_KEY_P256_OBJ_LEN); + if (status != PSA_SUCCESS) { + return status; + } + /* Capture the vault DRBG only after the NVM lock is dropped: a + * fault-recovery reset during the lock wait must not free the RNG out + * from under this in-flight signature. */ + status = wt_hsm_kv_rng(&rng); + if (status != PSA_SUCCESS) { + wt_hsm_kv_zeroize(obj, sizeof(obj)); + return status; + } + rc = wc_ecc_init_ex(&key, NULL, INVALID_DEVID); + if (rc == 0) { + rc = wc_ecc_import_private_key_ex(obj, WT_VAULT_KEY_MATERIAL_LEN, + NULL, 0U, &key, ECC_SECP256R1); + if (rc == 0) { + rc = wc_ecc_sign_hash(digest, (word32)digest_len, der, &der_len, + rng, &key); + } + wc_ecc_free(&key); + } + wt_hsm_kv_zeroize(obj, sizeof(obj)); + if (rc == 0) { + rc = wc_ecc_sig_to_rs(der, der_len, r, &r_len, s, &s_len); + } + if (rc != 0 || r_len > 32U || s_len > 32U) { + return PSA_ERROR_GENERIC_ERROR; + } + /* Fixed-width raw r||s, left-padded with zeros. */ + (void)memset(sig, 0, WT_VAULT_KEY_SIG_LEN); + (void)memcpy(sig + (32U - r_len), r, r_len); + (void)memcpy(sig + 32U + (32U - s_len), s, s_len); + *out_len = WT_VAULT_KEY_SIG_LEN; + return PSA_SUCCESS; +} + +static psa_status_t wt_hsm_kv_verify(int32_t owner, int32_t sub, uint64_t uid, + const uint8_t* digest, size_t digest_len, + const uint8_t* sig, size_t sig_len) +{ + uint8_t obj[WT_HSM_KEY_P256_OBJ_LEN]; + uint8_t der[WT_HSM_KEY_DER_SIG_MAX]; + ecc_key key; + word32 der_len = sizeof(der); + int res = 0; + psa_status_t status; + int rc; + + if (digest_len != WT_VAULT_KEY_DIGEST_LEN || + sig_len != WT_VAULT_KEY_SIG_LEN) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wt_hsm_kv_load(owner, sub, uid, WT_VAULT_KEY_P256, + WT_VAULT_KEY_USAGE_VERIFY, obj, + WT_HSM_KEY_P256_OBJ_LEN); + if (status != PSA_SUCCESS) { + return status; + } + rc = wc_ecc_rs_raw_to_sig(sig, 32U, sig + 32U, 32U, der, &der_len); + if (rc == 0) { + rc = wc_ecc_init_ex(&key, NULL, INVALID_DEVID); + } + if (rc == 0) { + rc = wc_ecc_import_x963_ex(obj + WT_VAULT_KEY_MATERIAL_LEN, + WT_VAULT_KEY_PUB_LEN, &key, ECC_SECP256R1); + if (rc == 0) { + rc = wc_ecc_verify_hash(der, der_len, digest, (word32)digest_len, + &res, &key); + } + wc_ecc_free(&key); + } + wt_hsm_kv_zeroize(obj, sizeof(obj)); + if (rc != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + return (res == 1) ? PSA_SUCCESS : PSA_ERROR_INVALID_SIGNATURE; +} + +static psa_status_t wt_hsm_kv_encrypt(int32_t owner, int32_t sub, + uint64_t uid, const uint8_t* input, + size_t input_len, uint8_t* out, + size_t cap, size_t* out_len) +{ + uint8_t obj[WT_HSM_KEY_AES_OBJ_LEN]; + Aes aes; + WC_RNG* rng; + psa_status_t status; + int rc; + + if (cap < input_len + WT_VAULT_KEY_NONCE_LEN + WT_VAULT_KEY_TAG_LEN) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + status = wt_hsm_kv_load(owner, sub, uid, WT_VAULT_KEY_AES256, + WT_VAULT_KEY_USAGE_ENCRYPT, obj, + WT_HSM_KEY_AES_OBJ_LEN); + if (status != PSA_SUCCESS) { + return status; + } + /* Capture the vault DRBG only after the NVM lock is dropped (see the + * sign path): a fault-recovery reset during the lock wait must not free + * the RNG mid-encrypt. */ + status = wt_hsm_kv_rng(&rng); + if (status != PSA_SUCCESS) { + wt_hsm_kv_zeroize(obj, sizeof(obj)); + return status; + } + rc = wc_RNG_GenerateBlock(rng, out, WT_VAULT_KEY_NONCE_LEN); + if (rc == 0) { + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + rc = wc_AesGcmSetKey(&aes, obj, WT_VAULT_KEY_MATERIAL_LEN); + if (rc == 0) { + rc = wc_AesGcmEncrypt(&aes, out + WT_VAULT_KEY_NONCE_LEN, + input, (word32)input_len, out, + WT_VAULT_KEY_NONCE_LEN, + out + WT_VAULT_KEY_NONCE_LEN + + input_len, + WT_VAULT_KEY_TAG_LEN, NULL, 0U); + } + wc_AesFree(&aes); + } + } + wt_hsm_kv_zeroize(obj, sizeof(obj)); + if (rc != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + *out_len = input_len + WT_VAULT_KEY_NONCE_LEN + WT_VAULT_KEY_TAG_LEN; + return PSA_SUCCESS; +} + +static psa_status_t wt_hsm_kv_decrypt(int32_t owner, int32_t sub, + uint64_t uid, const uint8_t* input, + size_t input_len, uint8_t* out, + size_t cap, size_t* out_len) +{ + uint8_t obj[WT_HSM_KEY_AES_OBJ_LEN]; + Aes aes; + size_t pt_len; + psa_status_t status; + int rc; + + if (input_len < WT_VAULT_KEY_NONCE_LEN + WT_VAULT_KEY_TAG_LEN) { + return PSA_ERROR_INVALID_ARGUMENT; + } + pt_len = input_len - WT_VAULT_KEY_NONCE_LEN - WT_VAULT_KEY_TAG_LEN; + if (cap < pt_len) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + status = wt_hsm_kv_load(owner, sub, uid, WT_VAULT_KEY_AES256, + WT_VAULT_KEY_USAGE_DECRYPT, obj, + WT_HSM_KEY_AES_OBJ_LEN); + if (status != PSA_SUCCESS) { + return status; + } + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + rc = wc_AesGcmSetKey(&aes, obj, WT_VAULT_KEY_MATERIAL_LEN); + if (rc == 0) { + rc = wc_AesGcmDecrypt(&aes, out, + input + WT_VAULT_KEY_NONCE_LEN, + (word32)pt_len, input, + WT_VAULT_KEY_NONCE_LEN, + input + input_len - WT_VAULT_KEY_TAG_LEN, + WT_VAULT_KEY_TAG_LEN, NULL, 0U); + } + wc_AesFree(&aes); + } + wt_hsm_kv_zeroize(obj, sizeof(obj)); + if (rc != 0) { + /* wolfSSL leaves out[] undefined after a non-zero AES-GCM decrypt; + * clear the plaintext region so no unauthenticated bytes survive in + * the shared response band. */ + wt_hsm_kv_zeroize(out, pt_len); + return (rc == AES_GCM_AUTH_E) ? PSA_ERROR_INVALID_SIGNATURE : + PSA_ERROR_GENERIC_ERROR; + } + *out_len = pt_len; + return PSA_SUCCESS; +} + +/* Key destruction is key-typed on purpose: the storage-face remove applies + * PSA storage semantics to the label's low bits, where a key object keeps its + * usage mask, so it would read a signing key's usage bit as WRITE_ONCE. */ +psa_status_t wt_hsm_keyvault_destroy(int32_t owner, int32_t sub, uint64_t uid) +{ + whNvmMetadata meta; + whNvmId id = WH_NVM_ID_INVALID; + psa_status_t status; + + if (g_kv_nvm == NULL) { + return PSA_ERROR_NOT_SUPPORTED; + } + status = wt_hsm_vault_lookup(owner, sub, uid, &id, &meta, NULL); + if (status != PSA_SUCCESS) { + return status; + } + if ((wt_hsm_vault_flags_of(meta.label) & WT_VAULT_FLAG_KEY) == 0U) { + return PSA_ERROR_NOT_PERMITTED; + } + if (wh_Nvm_DestroyObjectsChecked(g_kv_nvm, 1U, &id) != WH_ERROR_OK) { + return PSA_ERROR_STORAGE_FAILURE; + } + return PSA_SUCCESS; +} + +const wt_vault_key_backend_t wt_hsm_key_backend = { + wt_hsm_kv_generate, + wt_hsm_kv_import, + wt_hsm_kv_export_public, + wt_hsm_kv_sign, + wt_hsm_kv_verify, + wt_hsm_kv_encrypt, + wt_hsm_kv_decrypt +}; diff --git a/src/services/native/native_wire.c b/src/services/native/native_wire.c new file mode 100644 index 00000000..4eafe6ed --- /dev/null +++ b/src/services/native/native_wire.c @@ -0,0 +1,175 @@ +/* native_wire.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* + * Native crypto wire: SERVICE_HSM's submit backend for WT_ENGINE=native. + * Port-free by design (no flash, boot, or attestation dependencies) so the + * packet parser and every operation are host-testable against a RAM-backed + * NVM store. + */ + +/* wolfCrypt settings must come first. */ +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/sha256.h" +#include "wolfssl/wolfcrypt/error-crypt.h" + +#include "wolftrust/services/hsm.h" +#include "wolftrust/services/vault_service.h" +#include "wolftrust/services/crypto_native.h" + +#include +#include + +/* ========================================================================= + * wt_native_submit — SERVICE_HSM's native wire backend. + * + * One request packet ([wt_crypto_wire_req_t][payload]) in, one response + * packet ([int32_t psa_status][payload]) out, both bounded by the relay's + * copied buffers. Key ops execute in the key backend with the SPM-stamped + * client as delegated sub_owner, so a client only reaches its own keys. + * ====================================================================== */ +static psa_status_t wt_native_hash(const uint8_t* input, size_t input_len, + uint8_t* out, size_t out_cap, + size_t* out_len) +{ + wc_Sha256 sha; + int rc; + + if (out_cap < WC_SHA256_DIGEST_SIZE) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + rc = wc_InitSha256_ex(&sha, NULL, INVALID_DEVID); + if (rc == 0) { + rc = wc_Sha256Update(&sha, input, (word32)input_len); + } + if (rc == 0) { + rc = wc_Sha256Final(&sha, out); + } + if (rc != 0) { + return PSA_ERROR_GENERIC_ERROR; + } + *out_len = WC_SHA256_DIGEST_SIZE; + return PSA_SUCCESS; +} + +int wt_native_submit(void* submit_ctx, int32_t client_id, const uint8_t* req, + size_t req_len, uint8_t* resp, size_t resp_cap, + size_t* resp_len) +{ + wt_crypto_wire_req_t hdr; + int32_t owner = (int32_t)(intptr_t)submit_ctx; + const uint8_t* payload; + uint8_t* out; + size_t payload_len; + size_t out_cap; + size_t out_len = 0U; + size_t rand_len; + int32_t wire_status; + psa_status_t status; + + if (req == NULL || resp == NULL || resp_len == NULL || + req_len < sizeof(hdr) || resp_cap < sizeof(wire_status)) { + return -1; + } + (void)memcpy(&hdr, req, sizeof(hdr)); + payload = req + sizeof(hdr); + payload_len = req_len - sizeof(hdr); + out = resp + sizeof(wire_status); + out_cap = resp_cap - sizeof(wire_status); + + /* Strict parser: reserved must be zero so a future op cannot repurpose + * it as a flag a current client left set. Clients are told to zero it. */ + if (hdr.reserved != 0U) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else switch (hdr.op) { + case WT_CRYPTO_OP_KEY_GENERATE: + status = wt_hsm_key_backend.generate(owner, client_id, hdr.uid, + hdr.key_type, hdr.usage); + break; + case WT_CRYPTO_OP_KEY_IMPORT: + status = wt_hsm_key_backend.import(owner, client_id, hdr.uid, + hdr.key_type, hdr.usage, payload, + payload_len); + break; + case WT_CRYPTO_OP_KEY_EXPORT_PUBLIC: + status = wt_hsm_key_backend.export_public(owner, client_id, hdr.uid, + out, out_cap, &out_len); + break; + case WT_CRYPTO_OP_KEY_SIGN: + status = wt_hsm_key_backend.sign(owner, client_id, hdr.uid, payload, + payload_len, out, out_cap, + &out_len); + break; + case WT_CRYPTO_OP_KEY_VERIFY: + /* payload = [digest 32][signature 64]. */ + if (payload_len != WT_VAULT_KEY_DIGEST_LEN + WT_VAULT_KEY_SIG_LEN) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = wt_hsm_key_backend.verify(owner, client_id, hdr.uid, + payload, + WT_VAULT_KEY_DIGEST_LEN, + payload + + WT_VAULT_KEY_DIGEST_LEN, + WT_VAULT_KEY_SIG_LEN); + } + break; + case WT_CRYPTO_OP_KEY_ENCRYPT: + status = wt_hsm_key_backend.encrypt(owner, client_id, hdr.uid, + payload, payload_len, out, + out_cap, &out_len); + break; + case WT_CRYPTO_OP_KEY_DECRYPT: + status = wt_hsm_key_backend.decrypt(owner, client_id, hdr.uid, + payload, payload_len, out, + out_cap, &out_len); + break; + case WT_CRYPTO_OP_KEY_DESTROY: + status = wt_hsm_keyvault_destroy(owner, client_id, hdr.uid); + break; + case WT_CRYPTO_OP_RANDOM: + rand_len = hdr.usage; + if (rand_len == 0U || rand_len > WT_CRYPTO_RANDOM_MAX || + rand_len > out_cap) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = wt_hsm_keyvault_random(out, rand_len); + if (status == PSA_SUCCESS) { + out_len = rand_len; + } + } + break; + case WT_CRYPTO_OP_HASH: + status = wt_native_hash(payload, payload_len, out, out_cap, + &out_len); + break; + default: + status = PSA_ERROR_NOT_SUPPORTED; + break; + } + + wire_status = (int32_t)status; + (void)memcpy(resp, &wire_status, sizeof(wire_status)); + *resp_len = sizeof(wire_status) + out_len; + return 0; +} diff --git a/src/services/nvm_store.c b/src/services/nvm_store.c new file mode 100644 index 00000000..58f185d3 --- /dev/null +++ b/src/services/nvm_store.c @@ -0,0 +1,291 @@ +/* nvm_store.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* + * Engine-independent secure NVM store: the shared flash-backed object store, + * its serialisation lock, the boot lifecycle latch, and the WT-FFM-0050 + * anti-rollback floors. Linked in both crypto engines — the wolfHSM server + * (hsm engine) and the native dispatch both ride this store. + */ + +/* wolfCrypt settings must come first. */ +#include "wolfssl/wolfcrypt/settings.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_lock.h" + +#include "wolftrust/types.h" +#include "wolftrust/guest_verify.h" +#include "wolftrust/monitor.h" +#include "wolftrust/platform.h" +#include "wolftrust/rollback.h" +#include "wolftrust/sync/mutex.h" +#include "wolftrust/nvm_store.h" +#include "wolftrust/services/hsm.h" + +#include "wolftrust/port_nvm.h" +#include "psa/lifecycle.h" + +#include +#include + +#ifndef WOLFHSM_CFG_THREADSAFE +#error "wolfTrust requires WOLFHSM_CFG_THREADSAFE for the shared NVM store" +#endif + +/* ------------------------------------------------------------------------- + * Shared NVM state (one instance, serialised by g_wt_nvm_lock_mutex). + * ---------------------------------------------------------------------- */ +whNvmContext g_wt_nvm_ctx; +static whNvmFlashContext g_nvm_flash_ctx; + +static const whNvmCb g_nvm_flash_cb[1] = {WH_NVM_FLASH_CB}; + +/* ------------------------------------------------------------------------- + * Shared NVM lock. + * + * g_wt_hsm_lock_cb is defined in the parallel Wave 3B file wt_hsm_lock.c. + * Its callbacks dispatch acquire/release to g_wt_nvm_lock_mutex. + * ---------------------------------------------------------------------- */ +wt_mutex_t g_wt_nvm_lock_mutex; +static whLockConfig g_nvm_lock_cfg; +extern const whLockCb g_wt_hsm_lock_cb; /* defined in wt_hsm_lock.c */ + +/* ------------------------------------------------------------------------- + * Store recovery policy. A pool written by an older firmware generation (or + * a corrupt one) can block boot provisioning; recovery reformats it, but + * only in an unlocked development lifecycle — a SECURED device must never + * auto-wipe WRITE_ONCE storage or the sealed device key, so an unset or + * unknown lifecycle stays locked. + * ---------------------------------------------------------------------- */ +static uint32_t g_boot_lifecycle; /* PSA lifecycle from wolfBoot handoff */ +static int g_vault_reformatted; /* observability: reformatted this boot */ + +void wt_hsm_set_boot_lifecycle(uint32_t lifecycle) +{ + g_boot_lifecycle = lifecycle; +} + +int wt_hsm_vault_was_reformatted(void) +{ + return g_vault_reformatted; +} + +void wt_nvm_mark_reformatted(void) +{ + g_vault_reformatted = 1; +} + +int wt_nvm_reformat_allowed(void) +{ + return (g_boot_lifecycle == PSA_LIFECYCLE_ASSEMBLY_AND_TEST) || + (g_boot_lifecycle == PSA_LIFECYCLE_PSA_ROT_PROVISIONING); +} + +int wt_nvm_store_bind(void) +{ + whNvmFlashConfig nvm_flash_cfg; + whNvmConfig nvm_cfg; + + (void)memset(&g_nvm_flash_ctx, 0, sizeof(g_nvm_flash_ctx)); + (void)memset(&nvm_flash_cfg, 0, sizeof(nvm_flash_cfg)); + nvm_flash_cfg.cb = &g_wt_hsm_flash_cb; + nvm_flash_cfg.context = wt_hsm_flash_context(); + nvm_flash_cfg.config = wt_hsm_flash_config(); + + g_nvm_lock_cfg.cb = &g_wt_hsm_lock_cb; + g_nvm_lock_cfg.context = &g_wt_nvm_lock_mutex; + g_nvm_lock_cfg.config = NULL; + + (void)memset(&g_wt_nvm_ctx, 0, sizeof(g_wt_nvm_ctx)); + (void)memset(&nvm_cfg, 0, sizeof(nvm_cfg)); + nvm_cfg.cb = (whNvmCb *)g_nvm_flash_cb; + nvm_cfg.context = &g_nvm_flash_ctx; + nvm_cfg.config = &nvm_flash_cfg; + nvm_cfg.lockConfig = &g_nvm_lock_cfg; + + return wh_Nvm_Init(&g_wt_nvm_ctx, &nvm_cfg); +} + +void wt_hsm_release_locks(struct wt_co *co) +{ + /* Drop every secure-side store lock the faulted coroutine still held, and + * unlink it if it died parked as a waiter, so no later acquirer deadlocks + * behind a dead holder or a dead queued waiter. The NVM lock is the only + * such mutex today; add any future ones here. Recovery runs this before + * the partition is restarted, so the waiter is gone before it can + * re-enqueue. */ + if (co != NULL) { + wt_mutex_release_if_holder(&g_wt_nvm_lock_mutex, co); + wt_mutex_remove_waiter(&g_wt_nvm_lock_mutex, co); + } +} + +wt_mutex_t *wt_hsm_nvm_lock_mutex(void) +{ + return &g_wt_nvm_lock_mutex; +} + +/* ------------------------------------------------------------------------- + * WT-FFM-0050 firmware anti-rollback: monotonic version floors in a plain + * NVM object (WT_HSM_ROLLBACK_TABLE_ID), same access idiom as the vault + * counter table. Runs on the boot stack after the store binds and before + * the first dispatch. + * ---------------------------------------------------------------------- */ +static int wt_nvm_rollback_load(wt_rollback_table_t* table) +{ + whNvmMetadata meta; + int rc; + + rc = wh_Nvm_GetMetadata(&g_wt_nvm_ctx, WT_HSM_ROLLBACK_TABLE_ID, &meta); + if (rc == WH_ERROR_NOTFOUND) { + wt_rollback_table_init(table); + return 0; + } + if (rc != WH_ERROR_OK || meta.len != sizeof(*table)) { + return -1; + } + rc = wh_Nvm_Read(&g_wt_nvm_ctx, WT_HSM_ROLLBACK_TABLE_ID, 0U, + (whNvmSize)sizeof(*table), (uint8_t*)table); + if (rc != WH_ERROR_OK || !wt_rollback_table_valid(table)) { + return -1; + } + return 0; +} + +static int wt_nvm_rollback_store(const wt_rollback_table_t* table) +{ + whNvmMetadata meta; + int rc; + + (void)memset(&meta, 0, sizeof(meta)); + meta.id = WT_HSM_ROLLBACK_TABLE_ID; + meta.access = WH_NVM_ACCESS_ANY; + meta.flags = 0U; + meta.len = (whNvmSize)sizeof(*table); + rc = wh_Nvm_AddObject(&g_wt_nvm_ctx, &meta, (whNvmSize)sizeof(*table), + (const uint8_t*)table); + return (rc == WH_ERROR_OK) ? 0 : -1; +} + +static uint32_t g_active_image_version; + +uint32_t wt_hsm_active_image_version(void) +{ + return g_active_image_version; +} + +int wt_hsm_rollback_enforce(uint32_t image_version) +{ + wt_rollback_table_t table; + const wt_guest_measurement_t* records; + size_t record_count = 0U; + size_t guest_count; + size_t i; + int refused_platform = 0; + int changed = 0; + + g_active_image_version = image_version; + guest_count = wt_monitor_state()->guest_count; + + if (wt_nvm_rollback_load(&table) != 0) { + /* An unreadable floor cannot prove anything: fail closed. */ + refused_platform = 1; + } + +#if defined(WT_ROLLBACK_PROBE) + /* Negative test: force the locked lifecycle (the emulator chain boots in + * assembly-and-test, which rightly bypasses enforcement), then on the + * first pass arm the image floor one above the running version and + * reboot, so the second pass exercises the real downgrade refusal + * against a floor that survived SYSRESETREQ. A failed arming store is a + * broken test, not a refusal: trap loudly. */ + g_boot_lifecycle = PSA_LIFECYCLE_SECURED; + if (!refused_platform && table.image_floor <= image_version) { + table.image_floor = image_version + 1U; + if (wt_nvm_rollback_store(&table) != 0) { + wt_platform_panic(); + } + wt_platform_system_reset(); + } +#endif + + if (!refused_platform && + wt_rollback_check(g_boot_lifecycle, image_version, + table.image_floor) != WT_ROLLBACK_OK) { + refused_platform = 1; + } + + records = wt_platform_guest_measurements(&record_count); + + if (refused_platform) { + for (i = 0U; i < guest_count; i++) { + wt_monitor_quarantine_guest((wt_guest_id_t)i); + } + return WT_ROLLBACK_REFUSED; + } + + changed = wt_rollback_advance(image_version, &table.image_floor); + for (i = 0U; records != NULL && i < record_count; i++) { + uint32_t guest = records[i].guest_id; + + if (guest >= WT_GUEST_MEAS_MAX_RECORDS) { + continue; + } + if (wt_rollback_check(g_boot_lifecycle, records[i].version, + table.guest_floor[guest]) != WT_ROLLBACK_OK) { + wt_monitor_quarantine_guest((wt_guest_id_t)guest); + } + else if (wt_rollback_advance(records[i].version, + &table.guest_floor[guest]) != 0) { + changed = 1; + } + } + + if (changed && wt_nvm_rollback_store(&table) != 0) { + /* An unpersisted floor must not launch guests: the next reset would + * accept the previous floor again. Quarantine fail-closed; secure + * services stay up so the wedge is observable and recoverable. */ + for (i = 0U; i < guest_count; i++) { + wt_monitor_quarantine_guest((wt_guest_id_t)i); + } + return WT_ROLLBACK_REFUSED; + } + + return WT_ROLLBACK_OK; +} + +int wt_hsm_rollback_image_floor(uint32_t* floor) +{ + wt_rollback_table_t table; + + if (floor == NULL) { + return -1; + } + if (wt_nvm_rollback_load(&table) != 0) { + return -1; + } + /* The unlocked provisioning lifecycles bypass refusal at boot; the + * staging floor mirrors that so development flows are never bricked. */ + *floor = wt_nvm_reformat_allowed() ? 0U : table.image_floor; + return 0; +} diff --git a/src/services/wolfhsm/runner/secure.ld b/src/services/wolfhsm/runner/secure.ld index d0d02774..d0ab5cf5 100644 --- a/src/services/wolfhsm/runner/secure.ld +++ b/src/services/wolfhsm/runner/secure.ld @@ -159,6 +159,9 @@ SECTIONS { . = ALIGN(32); _s_keystore = .; *wt_hsm.o(.data .data.*) + *nvm_store.o(.data .data.*) + *crypto_native.o(.data .data.*) + *keyvault.o(.data .data.*) *wt_hsm_lock.o(.data .data.*) *wt_hsm_seal.o(.data .data.*) *wt_hsm_vault.o(.data .data.*) @@ -180,6 +183,9 @@ SECTIONS { .keystore_bss (NOLOAD) : { _s_keystore_bss = .; *wt_hsm.o(.bss .bss.* COMMON) + *nvm_store.o(.bss .bss.* COMMON) + *crypto_native.o(.bss .bss.* COMMON) + *keyvault.o(.bss .bss.* COMMON) *wt_hsm_lock.o(.bss .bss.* COMMON) *wt_hsm_seal.o(.bss .bss.* COMMON) *wt_hsm_vault.o(.bss .bss.* COMMON) diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index 3808a294..90965fdb 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -67,6 +67,7 @@ #include "wolftrust/rollback.h" #include "wolftrust/sched/tasklet.h" #include "wolftrust/sync/mutex.h" +#include "wolftrust/nvm_store.h" #include "wolftrust/services/hsm.h" #include "wolftrust/services/hsm_relay.h" #include "wolftrust/services/vault_service.h" @@ -132,34 +133,8 @@ static int g_attest_init_status = WH_ERROR_NOTREADY; static bool g_attest_init_attempted; static bool g_attest_ready; -/* ------------------------------------------------------------------------- - * Shared NVM state (one instance, serialised by g_nvm_lock_mutex). - * ---------------------------------------------------------------------- */ -static whNvmContext g_nvm_ctx; -static whNvmFlashContext g_nvm_flash_ctx; - -static const whNvmCb g_nvm_flash_cb[1] = {WH_NVM_FLASH_CB}; - -/* ------------------------------------------------------------------------- - * Shared NVM lock. - * - * g_wt_hsm_lock_cb is defined in the parallel Wave 3B file wt_hsm_lock.c. - * Its callbacks dispatch acquire/release to g_nvm_lock_mutex. - * ---------------------------------------------------------------------- */ -static wt_mutex_t g_nvm_lock_mutex; -static whLockConfig g_nvm_lock_cfg; -extern const whLockCb g_wt_hsm_lock_cb; /* defined in wt_hsm_lock.c */ - -/* ------------------------------------------------------------------------- - * Vault recovery policy. A vault pool written by an older firmware generation - * (or a corrupt one) can block boot provisioning: the IAK slot is held by a - * NONMODIFIABLE object, so a fresh keygen commit returns WH_ERROR_ACCESS. - * The recovery reformats and re-provisions, but only in an unlocked - * development lifecycle -- a SECURED device must never auto-wipe WRITE_ONCE - * storage or the sealed device key, so an unset/unknown lifecycle stays locked. - * ---------------------------------------------------------------------- */ -static uint32_t g_boot_lifecycle; /* PSA lifecycle from wolfBoot handoff */ -static int g_vault_reformatted; /* observability: reformatted this boot */ +/* The shared NVM store, lock, lifecycle latch, and rollback floors moved to + * the engine-independent src/services/nvm_store.c (wolftrust/nvm_store.h). */ #if defined(WT_VAULT_FOREIGN_PROBE) /* Negative test: make the first provisioning look blocked, as if a @@ -169,167 +144,6 @@ static int g_vault_reformatted; /* observability: reformatted this boot static int g_foreign_probe_fired; #endif -void wt_hsm_set_boot_lifecycle(uint32_t lifecycle) -{ - g_boot_lifecycle = lifecycle; -} - -int wt_hsm_vault_was_reformatted(void) -{ - return g_vault_reformatted; -} - -static int wt_hsm_reformat_allowed(void) -{ - return (g_boot_lifecycle == PSA_LIFECYCLE_ASSEMBLY_AND_TEST) || - (g_boot_lifecycle == PSA_LIFECYCLE_PSA_ROT_PROVISIONING); -} - -/* ------------------------------------------------------------------------- - * WT-FFM-0050 firmware anti-rollback: monotonic version floors in a plain - * NVM object (WT_HSM_ROLLBACK_TABLE_ID), same access idiom as the vault - * counter table. Runs on the boot stack after wt_hsm_init and before the - * first dispatch. - * ---------------------------------------------------------------------- */ -static int wt_hsm_rollback_load(wt_rollback_table_t* table) -{ - whNvmMetadata meta; - int rc; - - rc = wh_Nvm_GetMetadata(&g_nvm_ctx, WT_HSM_ROLLBACK_TABLE_ID, &meta); - if (rc == WH_ERROR_NOTFOUND) { - wt_rollback_table_init(table); - return 0; - } - if (rc != WH_ERROR_OK || meta.len != sizeof(*table)) { - return -1; - } - rc = wh_Nvm_Read(&g_nvm_ctx, WT_HSM_ROLLBACK_TABLE_ID, 0U, - (whNvmSize)sizeof(*table), (uint8_t*)table); - if (rc != WH_ERROR_OK || !wt_rollback_table_valid(table)) { - return -1; - } - return 0; -} - -static int wt_hsm_rollback_store(const wt_rollback_table_t* table) -{ - whNvmMetadata meta; - int rc; - - (void)memset(&meta, 0, sizeof(meta)); - meta.id = WT_HSM_ROLLBACK_TABLE_ID; - meta.access = WH_NVM_ACCESS_ANY; - meta.flags = 0U; - meta.len = (whNvmSize)sizeof(*table); - rc = wh_Nvm_AddObject(&g_nvm_ctx, &meta, (whNvmSize)sizeof(*table), - (const uint8_t*)table); - return (rc == WH_ERROR_OK) ? 0 : -1; -} - -static uint32_t g_active_image_version; - -uint32_t wt_hsm_active_image_version(void) -{ - return g_active_image_version; -} - -int wt_hsm_rollback_enforce(uint32_t image_version) -{ - wt_rollback_table_t table; - const wt_guest_measurement_t* records; - size_t record_count = 0U; - size_t guest_count; - size_t i; - int refused_platform = 0; - int changed = 0; - - g_active_image_version = image_version; - guest_count = wt_monitor_state()->guest_count; - - if (wt_hsm_rollback_load(&table) != 0) { - /* An unreadable floor cannot prove anything: fail closed. */ - refused_platform = 1; - } - -#if defined(WT_ROLLBACK_PROBE) - /* Negative test: force the locked lifecycle (the emulator chain boots in - * assembly-and-test, which rightly bypasses enforcement), then on the - * first pass arm the image floor one above the running version and - * reboot, so the second pass exercises the real downgrade refusal - * against a floor that survived SYSRESETREQ. A failed arming store is a - * broken test, not a refusal: trap loudly. */ - g_boot_lifecycle = PSA_LIFECYCLE_SECURED; - if (!refused_platform && table.image_floor <= image_version) { - table.image_floor = image_version + 1U; - if (wt_hsm_rollback_store(&table) != 0) { - wt_platform_panic(); - } - wt_platform_system_reset(); - } -#endif - - if (!refused_platform && - wt_rollback_check(g_boot_lifecycle, image_version, - table.image_floor) != WT_ROLLBACK_OK) { - refused_platform = 1; - } - - records = wt_platform_guest_measurements(&record_count); - - if (refused_platform) { - for (i = 0U; i < guest_count; i++) { - wt_monitor_quarantine_guest((wt_guest_id_t)i); - } - return WT_ROLLBACK_REFUSED; - } - - changed = wt_rollback_advance(image_version, &table.image_floor); - for (i = 0U; records != NULL && i < record_count; i++) { - uint32_t guest = records[i].guest_id; - - if (guest >= WT_GUEST_MEAS_MAX_RECORDS) { - continue; - } - if (wt_rollback_check(g_boot_lifecycle, records[i].version, - table.guest_floor[guest]) != WT_ROLLBACK_OK) { - wt_monitor_quarantine_guest((wt_guest_id_t)guest); - } - else if (wt_rollback_advance(records[i].version, - &table.guest_floor[guest]) != 0) { - changed = 1; - } - } - - if (changed && wt_hsm_rollback_store(&table) != 0) { - /* An unpersisted floor must not launch guests: the next reset would - * accept the previous floor again. Quarantine fail-closed; secure - * services stay up so the wedge is observable and recoverable. */ - for (i = 0U; i < guest_count; i++) { - wt_monitor_quarantine_guest((wt_guest_id_t)i); - } - return WT_ROLLBACK_REFUSED; - } - - return WT_ROLLBACK_OK; -} - -int wt_hsm_rollback_image_floor(uint32_t* floor) -{ - wt_rollback_table_t table; - - if (floor == NULL) { - return -1; - } - if (wt_hsm_rollback_load(&table) != 0) { - return -1; - } - /* The unlocked provisioning lifecycles bypass refusal at boot; the - * staging floor mirrors that so development flows are never bricked. */ - *floor = wt_hsm_reformat_allowed() ? 0U : table.image_floor; - return 0; -} - /* ------------------------------------------------------------------------- * Forward declaration — tasklet body defined below. * ---------------------------------------------------------------------- */ @@ -344,35 +158,16 @@ static void wt_hsm_tasklet_main(void *arg); * per-guest tasklets share one wolfHSM NVM context. */ static int wt_hsm_bind_store(void) { - whNvmFlashConfig nvm_flash_cfg; - whNvmConfig nvm_cfg; - int rc; - - (void)memset(&g_nvm_flash_ctx, 0, sizeof(g_nvm_flash_ctx)); - (void)memset(&nvm_flash_cfg, 0, sizeof(nvm_flash_cfg)); - nvm_flash_cfg.cb = &g_wt_hsm_flash_cb; - nvm_flash_cfg.context = wt_hsm_flash_context(); - nvm_flash_cfg.config = wt_hsm_flash_config(); - - g_nvm_lock_cfg.cb = &g_wt_hsm_lock_cb; - g_nvm_lock_cfg.context = &g_nvm_lock_mutex; - g_nvm_lock_cfg.config = NULL; - - (void)memset(&g_nvm_ctx, 0, sizeof(g_nvm_ctx)); - (void)memset(&nvm_cfg, 0, sizeof(nvm_cfg)); - nvm_cfg.cb = (whNvmCb *)g_nvm_flash_cb; - nvm_cfg.context = &g_nvm_flash_ctx; - nvm_cfg.config = &nvm_flash_cfg; - nvm_cfg.lockConfig = &g_nvm_lock_cfg; - - rc = wh_Nvm_Init(&g_nvm_ctx, &nvm_cfg); + int rc; + + rc = wt_nvm_store_bind(); if (rc != WH_ERROR_OK) { return rc; } - if (wt_hsm_vault_init(&g_nvm_ctx) == 0) { + if (wt_hsm_vault_init(&g_wt_nvm_ctx) == 0) { wt_vault_service_set_backend(&wt_hsm_vault_backend); - if (wt_hsm_seal_init(&g_nvm_ctx) == 0) { + if (wt_hsm_seal_init(&g_wt_nvm_ctx) == 0) { wt_hsm_vault_set_sealer(&wt_hsm_sealer); } else { @@ -391,7 +186,7 @@ static int wt_hsm_bind_store(void) } /* Erase the whole vault region and rebuild a blank store. Caller must have - * checked wt_hsm_reformat_allowed() -- this destroys every object, including + * checked wt_nvm_reformat_allowed() -- this destroys every object, including * WRITE_ONCE storage and the sealed device key. */ static int wt_hsm_vault_format(void) { @@ -402,7 +197,7 @@ static int wt_hsm_vault_format(void) rc = wt_hsm_bind_store(); } if (rc == 0) { - g_vault_reformatted = 1; + wt_nvm_mark_reformatted(); } return rc; } @@ -447,7 +242,7 @@ int wt_hsm_init(void) } /* Initialise the shared NVM lock once, before wh_Nvm_Init wires it in. */ - wt_mutex_init(&g_nvm_lock_mutex); + wt_mutex_init(&g_wt_nvm_lock_mutex); return wt_hsm_bind_store(); } @@ -550,7 +345,7 @@ int wt_hsm_guest_init(wt_guest_id_t guest_id, * 5. Build server config. * ---------------------------------------------------------------- */ g->server_cfg.comm_config = &g->comm_cfg; - g->server_cfg.nvm = &g_nvm_ctx; + g->server_cfg.nvm = &g_wt_nvm_ctx; g->server_cfg.crypto = &g->crypto; #if defined(WOLF_CRYPTO_CB) g->server_cfg.devId = INVALID_DEVID; @@ -864,24 +659,6 @@ void wt_hsm_set_fault_notify(wt_hsm_fault_notify_fn fn) g_hsm_fault_notify = (fn != NULL) ? fn : wt_hsm_fault_notify_noop; } -void wt_hsm_release_locks(struct wt_co *co) -{ - /* Drop every secure-side wolfHSM lock the faulted coroutine still held, and - * unlink it if it died parked as a waiter, so no later acquirer deadlocks - * behind a dead holder or a dead queued waiter. The NVM lock is the only - * such mutex today; add any future ones here. Recovery runs this before the - * partition is restarted, so the waiter is gone before it can re-enqueue. */ - if (co != NULL) { - wt_mutex_release_if_holder(&g_nvm_lock_mutex, co); - wt_mutex_remove_waiter(&g_nvm_lock_mutex, co); - } -} - -wt_mutex_t *wt_hsm_nvm_lock_mutex(void) -{ - return &g_nvm_lock_mutex; -} - int wt_hsm_relay_reinit_servers(void) { int rc = WH_ERROR_OK; @@ -1141,7 +918,7 @@ int wt_hsm_attest_init(void) int ret; #if defined(WT_VAULT_FOREIGN_PROBE) && defined(WT_VAULT_PROBE_SECURED) - g_boot_lifecycle = PSA_LIFECYCLE_SECURED; + wt_hsm_set_boot_lifecycle(PSA_LIFECYCLE_SECURED); #endif if (g_attest_ready) { return WH_ERROR_OK; @@ -1164,7 +941,7 @@ int wt_hsm_attest_init(void) (void)memset(&g_attest_server_cfg, 0, sizeof(g_attest_server_cfg)); g_attest_server_cfg.comm_config = &g_attest_comm_cfg; - g_attest_server_cfg.nvm = &g_nvm_ctx; + g_attest_server_cfg.nvm = &g_wt_nvm_ctx; g_attest_server_cfg.crypto = &g_attest_crypto; #if defined(WOLF_CRYPTO_CB) g_attest_server_cfg.devId = INVALID_DEVID; @@ -1198,9 +975,9 @@ int wt_hsm_attest_init(void) * NONMODIFIABLE/NONDESTROYABLE flags reject the fresh commit. In an * unlocked lifecycle, reformat the vault once and re-provision; a * SECURED device never reaches here, so its data is never wiped. */ - if (ret != WH_ERROR_OK && wt_hsm_reformat_allowed()) { + if (ret != WH_ERROR_OK && wt_nvm_reformat_allowed()) { if (wt_hsm_vault_format() == 0) { - /* vault_format re-inited g_nvm_ctx under the attest server; + /* vault_format re-inited the store under the attest server; * rebind the server to the fresh store before re-provisioning * so its keystore view is not stale. */ ret = wh_Server_Init(&g_attest_server, &g_attest_server_cfg); diff --git a/src/services/wolfhsm/wt_hsm_vault.c b/src/services/wolfhsm/wt_hsm_vault.c index d8d7d648..9a27b672 100644 --- a/src/services/wolfhsm/wt_hsm_vault.c +++ b/src/services/wolfhsm/wt_hsm_vault.c @@ -164,6 +164,17 @@ static psa_status_t wt_hsm_vault_reserve(uint32_t need_size, return PSA_SUCCESS; } +/* Object-add reservation for the native key backend: reserve the object's + * bytes plus the counter-table headroom, and two directory entries (the add + * plus one kept free) so a later sealed write's counter-table create always + * has a slot (WT-FFM-0048). Exported for keyvault.c. */ +psa_status_t wt_hsm_vault_reserve_object(whNvmSize len) +{ + return wt_hsm_vault_reserve( + wt_hsm_vault_storage_size(len) + + wt_hsm_vault_storage_size(sizeof(wt_hsm_vault_table_t)), 2U); +} + static psa_status_t wt_hsm_vault_table_store(const wt_hsm_vault_table_t* table) { whNvmMetadata meta; @@ -717,8 +728,13 @@ static psa_status_t wt_hsm_vault_remove(int32_t owner, int32_t sub, if (status != PSA_SUCCESS) { return status; } - if ((wt_hsm_vault_flags_of(meta.label) & - WT_VAULT_FLAG_WRITE_ONCE) != 0U) { + /* Key objects hold WT_VAULT_KEY_USAGE_* bits in the low label bits that + * alias WT_VAULT_FLAG_WRITE_ONCE (USAGE_SIGN == WRITE_ONCE == 0x1); they + * are never write-once stores, so apply the storage gate to non-key + * objects only and let REMOVE destroy a key as psa_destroy_key promises. */ + if ((wt_hsm_vault_flags_of(meta.label) & WT_VAULT_FLAG_KEY) == 0U && + (wt_hsm_vault_flags_of(meta.label) & + WT_VAULT_FLAG_WRITE_ONCE) != 0U) { return PSA_ERROR_NOT_PERMITTED; } if ((wt_hsm_vault_flags_of(meta.label) & WT_VAULT_FLAG_SEALED) != 0U) { diff --git a/src/spm_partitions.c b/src/spm_partitions.c index a01f10c7..c6acfb99 100644 --- a/src/spm_partitions.c +++ b/src/spm_partitions.c @@ -39,6 +39,9 @@ #include "wolftrust/services/fwu_service.h" #include "wolftrust/services/hsm.h" #include "wolftrust/services/hsm_relay.h" +#ifndef WT_ENGINE_HSM +#include "wolftrust/services/crypto_native.h" +#endif #include "wolftrust/sync/mutex.h" #include "wolftrust/services/storage_service.h" #include "wolftrust/services/vault_service.h" @@ -49,6 +52,8 @@ #include "wolftrust/sp_recovery.h" #include "wolftrust/spm_gate.h" +#include + /* Generated in every secure build; the ITS entry embeds SERVICE_VAULT_SID as * a code constant — the unprivileged loop cannot read SPM RAM at runtime. */ #include "psa_manifest/sid.h" @@ -92,6 +97,10 @@ int wt_spm_hsm_start(wt_ffm_runtime_t* runtime, int32_t partition_id) wt_hsm_relay_set_transport(wt_spm_svc_transport); #if defined(WT_ENGINE_HSM) wt_hsm_relay_set_submit(wt_hsm_relay_submit, NULL); +#else + /* Native engine: SERVICE_HSM stays the single mediated door; its packets + * carry the native wire and dispatch straight into wolfCrypt. */ + wt_hsm_relay_set_submit(wt_native_submit, (void*)(intptr_t)partition_id); #endif wt_spm_set_hsm_partition(partition_id); return wt_spm_sched_add(runtime, partition_id, wt_spm_hsm_entry, diff --git a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c index 0ca23c9e..aa1a6393 100644 --- a/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c +++ b/tests/firmware/zephyr-stm32h5/apps/freertos_guest1/main.c @@ -26,8 +26,12 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/random.h" +#if defined(WT_ENGINE_HSM) +#include "wolfssl/wolfcrypt/cryptocb.h" #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_cryptocb.h" +#endif #include #include "wolfpsa/psa_engine.h" @@ -35,12 +39,14 @@ #include "psa/client.h" /* SERVICE_HSM (port/stm32h563/manifest.json): the single mediated door to - * the wolfHSM server — the same path guest0 uses. */ + * the secure crypto engine — the same path guest0 uses. */ #define WT_SERVICE_HSM_SID 4102u +#if defined(WT_ENGINE_HSM) /* wolfHSM client glue (module/wolfhsm-client/src/wolfhsm_client_glue.c). */ int wolfhsm_guest_init(void); whClientContext *wolfhsm_guest_client(void); +#endif int wolftrust_guest_rng_stub(unsigned char *output, unsigned int sz); extern uint32_t _sidata; @@ -380,8 +386,10 @@ static int guest_crypto_init(void) { int rc; - /* Boot can race a Secure Partition restart window. The shared glue - * installs a retry callback when the first connection is refused. */ +#if defined(WT_ENGINE_HSM) + /* Boot can race a Secure Partition restart window; the glue cryptocb + * heals by retrying the connect on demand, so a failed init here is a + * warning, not a terminal error. */ rc = wolfhsm_guest_init(); if (rc != WH_ERROR_OK) { uart_puts("freertos_guest1: hsm client init FAILED rc="); @@ -390,6 +398,7 @@ static int guest_crypto_init(void) return -1; } (void)wolfPSA_SetDefaultDevID(WH_DEV_ID); +#endif /* PSA requires psa_crypto_init before any other psa_* call; guest0 gets * this from wolfPSA's Zephyr SYS_INIT, the bare FreeRTOS guest does it * here so the first mediated psa_hash_compute is not BAD_STATE. */ diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c index 0440d2ad..2ef564fc 100644 --- a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c @@ -52,6 +52,9 @@ #include "attestation_verify.h" +/* The hsmattackneg probe drives the raw wolfHSM client; only that probe + * build (hsm engine) links the client, so the headers gate with it. */ +#if defined(WT_HSM_ATTACK_PROBE) #include "wolfssl/wolfcrypt/ecc.h" #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_comm.h" @@ -61,6 +64,7 @@ #include "wolfhsm/wh_message.h" #include "wolfhsm/wh_message_nvm.h" #include "wolftrust/services/hsm.h" +#endif LOG_MODULE_REGISTER(guest0_psa, LOG_LEVEL_INF); diff --git a/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/CMakeLists.txt b/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/CMakeLists.txt index 785654a9..ed8ca5e0 100644 --- a/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/CMakeLists.txt +++ b/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/CMakeLists.txt @@ -160,3 +160,90 @@ if(CONFIG_WOLFTRUST_WOLFHSM_CLIENT) ) endif() + +if(CONFIG_WOLFTRUST_NATIVE_CLIENT) + + set(WT_REPO_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../..) + set(WT_WOLFSSL_DIR ${WT_REPO_ROOT}/lib/wolfSSL) + set(WT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) + + zephyr_include_directories( + ${WT_MODULE_DIR}/include + ${WT_MODULE_DIR}/include/wolftrust/wolfhsm + ${WT_WOLFSSL_DIR} + ${WT_REPO_ROOT}/include + ) + + # Same wolfCrypt shape as the hsm client, minus the crypto_cb routing and + # every wolfHSM define: primitives run Non-secure, seeds ride the native + # wire (crypto_native_client.c) to the secure vault RNG. + zephyr_compile_definitions( + WOLFSSL_USER_SETTINGS + WC_RESEED_INTERVAL=1000000 + WT_ENGINE_NATIVE=1 + WOLFSSL_SP_ASM + WOLFSSL_SP_ARM_CORTEX_M_ASM + WOLFSSL_ARM_ARCH=8 + WOLFSSL_ECDSA_DETERMINISTIC_K + WOLFSSL_ARMASM + WOLFSSL_ARMASM_NO_HW_CRYPTO + WOLFSSL_ARMASM_INLINE + WOLFSSL_ARMASM_NO_NEON + WOLFSSL_ARMASM_THUMB2 + NO_ERROR_STRINGS + HAVE_ANONYMOUS_INLINE_AGGREGATES=1 + WOLFSSL_ZEPHYR + ) + + zephyr_library_named(wolftrust_native_client) + + zephyr_library_sources( + ${WT_WOLFSSL_DIR}/wolfcrypt/src/aes.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/ecc.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/random.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sha256.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/asn.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/coding.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/error.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/hash.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/hmac.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/memory.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/pwdbased.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sha.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sp_cortexm.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sp_int.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/wolfmath.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/wc_port.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/port/arm/thumb2-aes-asm_c.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/port/arm/thumb2-sha256-asm_c.c + ) + + zephyr_library_sources( + ${WT_REPO_ROOT}/src/client/crypto_native_client.c + ${WT_REPO_ROOT}/src/client/psa_ffm_client.c + ) + + set_source_files_properties( + ${WT_WOLFSSL_DIR}/wolfcrypt/src/aes.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/ecc.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/random.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sha256.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/asn.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/coding.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/error.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/hash.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/hmac.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/memory.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/pwdbased.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sha.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sp_cortexm.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/sp_int.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/wolfmath.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/wc_port.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/port/arm/thumb2-aes-asm_c.c + ${WT_WOLFSSL_DIR}/wolfcrypt/src/port/arm/thumb2-sha256-asm_c.c + PROPERTIES COMPILE_OPTIONS + "-Wno-unused-function;-Wno-unused-variable;-Wno-unused-parameter;-Wno-type-limits" + ) + +endif() diff --git a/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/Kconfig b/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/Kconfig index 86a2c47d..53be928d 100644 --- a/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/Kconfig +++ b/tests/firmware/zephyr-stm32h5/module/wolfhsm-client/Kconfig @@ -17,3 +17,13 @@ if WOLFTRUST_WOLFHSM_CLIENT # in the module's SYS_INIT hook. Nothing to configure here yet. endif # WOLFTRUST_WOLFHSM_CLIENT + +config WOLFTRUST_NATIVE_CLIENT + bool "wolfTrust non-secure native crypto client (WT_ENGINE=native)" + depends on ARM + depends on !WOLFTRUST_WOLFHSM_CLIENT + help + Link the native-engine crypto client into the Zephyr image: wolfCrypt + runs Non-secure with no crypto_cb routing, and DRBG seed material + comes from the secure vault RNG over the SERVICE_HSM native wire + (one psa_call per request through the same 5-veneer FF-M gateway). diff --git a/tests/firmware/zephyr-stm32h5/module/wolfpsa/Kconfig b/tests/firmware/zephyr-stm32h5/module/wolfpsa/Kconfig index bbcae718..abc1c44f 100644 --- a/tests/firmware/zephyr-stm32h5/module/wolfpsa/Kconfig +++ b/tests/firmware/zephyr-stm32h5/module/wolfpsa/Kconfig @@ -1,13 +1,14 @@ menuconfig WOLFPSA - bool "wolfPSA Crypto engine (PSA API over wolfCrypt + wolfHSM)" - depends on WOLFTRUST_WOLFHSM_CLIENT + bool "wolfPSA Crypto engine (PSA API over wolfCrypt)" + depends on WOLFTRUST_WOLFHSM_CLIENT || WOLFTRUST_NATIVE_CLIENT depends on !MBEDTLS depends on !BUILD_WITH_TFM help Link the lib/wolfPSA submodule into the Zephyr image and expose - the PSA Crypto API on top of wolfCrypt. The wolfPSA glue calls - wolfPSA_SetDefaultDevID(WH_DEV_ID) in its SYS_INIT hook so every - psa_*() call routes through the wolfHSM client (registered by - the wolfhsm-client module) and across the CMSE boundary into - the secure side. Mutually exclusive with Zephyr's bundled - mbedTLS PSA front-end and TF-M. + the PSA Crypto API on top of wolfCrypt. With the wolfhsm-client + module (hsm engine) the glue calls wolfPSA_SetDefaultDevID(WH_DEV_ID) + in its SYS_INIT hook so every psa_*() call routes through the wolfHSM + client across the CMSE boundary; with the native client, primitives + run Non-secure and only DRBG seeds cross to the secure vault RNG. + Mutually exclusive with Zephyr's bundled mbedTLS PSA front-end + and TF-M. diff --git a/tests/firmware/zephyr-stm32h5/module/wolfpsa/src/wolfpsa_zephyr_init.c b/tests/firmware/zephyr-stm32h5/module/wolfpsa/src/wolfpsa_zephyr_init.c index 31db2754..021207f4 100644 --- a/tests/firmware/zephyr-stm32h5/module/wolfpsa/src/wolfpsa_zephyr_init.c +++ b/tests/firmware/zephyr-stm32h5/module/wolfpsa/src/wolfpsa_zephyr_init.c @@ -35,7 +35,9 @@ #include #include +#ifdef CONFIG_WOLFTRUST_WOLFHSM_CLIENT #include "wolfhsm/wh_client.h" +#endif LOG_MODULE_REGISTER(wolfpsa_zephyr, LOG_LEVEL_INF); @@ -43,7 +45,9 @@ static int wolfpsa_zephyr_sys_init(void) { psa_status_t st; +#ifdef CONFIG_WOLFTRUST_WOLFHSM_CLIENT (void)wolfPSA_SetDefaultDevID(WH_DEV_ID); +#endif st = psa_crypto_init(); if (st != PSA_SUCCESS) { @@ -51,7 +55,11 @@ static int wolfpsa_zephyr_sys_init(void) return -EIO; } +#ifdef CONFIG_WOLFTRUST_WOLFHSM_CLIENT LOG_INF("wolfPSA up; default devId=0x%08x", (unsigned)WH_DEV_ID); +#else + LOG_INF("wolfPSA up; native engine (Non-secure wolfCrypt)"); +#endif return 0; } diff --git a/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh b/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh index 9b872dc5..f2b169fd 100755 --- a/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh +++ b/tests/firmware/zephyr-stm32h5/scripts/build_freertos_guest.sh @@ -32,6 +32,7 @@ WOLFPSA_DIR="${ROOT}/lib/wolfPSA" WOLFHSM_DIR="${ROOT}/lib/wolfHSM" BAREMETAL_NS_DIR="${ROOT}/tests/firmware/stm32h563/nonsecure" WOLFHSM_MODULE_DIR="${SUBTREE_DIR}/module/wolfhsm-client" +. "${ROOT}/tests/target/lib/engine.sh" if [ ! -d "${FREERTOS_KERNEL}" ]; then echo "missing FreeRTOS workspace: ${FREERTOS_KERNEL}" >&2 @@ -106,8 +107,18 @@ ${WOLFHSM_DIR}/src/wh_message_counter.c \ ${WOLFHSM_DIR}/src/wh_utils.c" # OS-neutral wolfTrust NS client core: psa_connect/call/close over the -# WolfTrust_FFM_* veneers, plus the wolfHSM-over-psa_call transport and the -# guest glue that registers the wolfHSM crypto-callback device. +# WolfTrust_FFM_* veneers, plus the engine-specific client: the +# wolfHSM-over-psa_call transport + cryptocb glue (hsm), or the native wire +# client whose only secure crossing is the vault RNG seed (native). +if [ "${WT_ENGINE}" = "native" ]; then +WOLFHSM_CLIENT_SRCS="" +WT_CLIENT_SRCS="\ +${ROOT}/src/client/psa_ffm_client.c \ +${ROOT}/src/client/crypto_native_client.c" + +GUEST_GLUE_SRCS="\ +${BAREMETAL_NS_DIR}/libc_stubs_guest.c" +else WT_CLIENT_SRCS="\ ${ROOT}/src/client/psa_ffm_client.c \ ${ROOT}/src/client/hsm_psa_transport.c" @@ -115,6 +126,7 @@ ${ROOT}/src/client/hsm_psa_transport.c" GUEST_GLUE_SRCS="\ ${BAREMETAL_NS_DIR}/libc_stubs_guest.c \ ${WOLFHSM_MODULE_DIR}/src/wolfhsm_client_glue.c" +fi # wh_settings.h picks up config via `#ifdef WOLFHSM_CFG / #include # "wolfhsm_cfg.h"`; generate the trampolines pointing at the shared guest @@ -128,6 +140,12 @@ printf '#include "%s"\n' "${BAREMETAL_NS_DIR}/wh_settings_guest.h" \ APP_SRCS="${APP_DIR}/main.c" +if [ "${WT_ENGINE}" = "native" ]; then +WT_ENGINE_DEFS="-DWT_ENGINE_NATIVE=1" +else +WT_ENGINE_DEFS="-DWOLFHSM_CFG -DWOLF_CRYPTO_CB -DWT_ENGINE_HSM=1 -DWT_WOLFHSM_CLIENT_ID=2" +fi + CFLAGS="\ -mcpu=cortex-m33 -mthumb -mgeneral-regs-only \ -ffreestanding -fno-builtin -nostdlib -Os -g \ @@ -149,10 +167,7 @@ CFLAGS="\ -DWOLFSSL_USER_SETTINGS \ -DWOLFSSL_PSA_ENGINE \ -DWOLFPSA_NO_TRACE \ --DWOLFHSM_CFG \ --DWOLF_CRYPTO_CB \ --DWT_ENGINE_HSM=1 \ --DWT_WOLFHSM_CLIENT_ID=2 \ +${WT_ENGINE_DEFS} \ -DWC_RESEED_INTERVAL=1000000 \ -include ${SUBTREE_DIR}/module/wolfpsa/wolfpsa_no_trace.h \ -DWOLFSSL_SP_ASM -DWOLFSSL_SP_ARM_CORTEX_M_ASM -DWOLFSSL_ARM_ARCH=8 \ @@ -191,7 +206,16 @@ NM_OUT=$(arm-none-eabi-nm "${BUILD_DIR}/freertos_guest1.elf") || { echo "FAIL: nm on the guest1 image failed" >&2 exit 1 } -if ! printf '%s\n' "${NM_OUT}" | grep -q "wt_hsm_psa_transport_cb"; then +if [ "${WT_ENGINE}" = "native" ]; then + if ! printf '%s\n' "${NM_OUT}" | grep -q "wt_crypto_native_call"; then + echo "guest1 is not wired to the native crypto wire client" >&2 + exit 1 + fi + if printf '%s\n' "${NM_OUT}" | grep -q "wh_Client"; then + echo "FAIL: wolfHSM client linked into a native-engine guest1" >&2 + exit 1 + fi +elif ! printf '%s\n' "${NM_OUT}" | grep -q "wt_hsm_psa_transport_cb"; then echo "guest1 is not wired to the SPM-mediated wolfHSM transport" >&2 exit 1 fi diff --git a/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh b/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh index 80beafda..f784ef3b 100755 --- a/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh +++ b/tests/firmware/zephyr-stm32h5/scripts/build_guest.sh @@ -30,6 +30,7 @@ WT_FWU_PROBE="${WT_FWU_PROBE:-0}" WT_WRITE_ONCE_RESET_PROBE="${WT_WRITE_ONCE_RESET_PROBE:-0}" WT_HSM_ATTACK_PROBE="${WT_HSM_ATTACK_PROBE:-0}" WT_MPU_BYPASS_PROBE="${WT_MPU_BYPASS_PROBE:-0}" +. "$ROOT/tests/target/lib/engine.sh" if [ ! -d "$APP_DIR" ]; then echo "unknown guest app: $APP_NAME" >&2 @@ -65,6 +66,16 @@ set -- \ "-DWT_HSM_ATTACK_PROBE=$WT_HSM_ATTACK_PROBE" \ "-DWT_MPU_BYPASS_PROBE=$WT_MPU_BYPASS_PROBE" +if [ "$WT_ENGINE" = "native" ]; then + set -- "$@" \ + "-DCONFIG_WOLFTRUST_WOLFHSM_CLIENT=n" \ + "-DCONFIG_WOLFTRUST_NATIVE_CLIENT=y" +else + set -- "$@" \ + "-DCONFIG_WOLFTRUST_WOLFHSM_CLIENT=y" \ + "-DCONFIG_WOLFTRUST_NATIVE_CLIENT=n" +fi + if [ -n "${ZEPHYR_TOOLCHAIN_VARIANT:-}" ]; then set -- "$@" "-DZEPHYR_TOOLCHAIN_VARIANT=$ZEPHYR_TOOLCHAIN_VARIANT" fi @@ -115,7 +126,16 @@ if printf '%s\n' "$NM_OUT" | \ echo "FAIL: retired direct veneers linked into $APP_NAME" >&2 exit 1 fi -if ! printf '%s\n' "$NM_OUT" | grep -q 'wt_hsm_psa_transport_cb'; then +if [ "$WT_ENGINE" = "native" ]; then + if ! printf '%s\n' "$NM_OUT" | grep -q 'wt_crypto_native_call'; then + echo "FAIL: $APP_NAME is not wired to the native crypto wire client" >&2 + exit 1 + fi + if printf '%s\n' "$NM_OUT" | grep -q 'wh_Client'; then + echo "FAIL: wolfHSM client linked into a native-engine $APP_NAME" >&2 + exit 1 + fi +elif ! printf '%s\n' "$NM_OUT" | grep -q 'wt_hsm_psa_transport_cb'; then echo "FAIL: $APP_NAME is not wired to the SPM-mediated wolfHSM transport" >&2 exit 1 fi diff --git a/tests/host/Makefile b/tests/host/Makefile index 49afe1ab..0b7d178c 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -32,7 +32,8 @@ endif UNIT_SUITES := domain manifest lifecycle guest_verify rollback sp_recovery \ ipc ffm spm \ - vnet wolfhsm_loopback wolfhsm_relay keystore_isolation \ + vnet wolfhsm_loopback wolfhsm_relay native_wire native_wire_client \ + keystore_isolation \ attestation_service attestation attestation_token \ attestation_golden attestation_negatives attestation_replay \ attestation_iak ffm_domain sp_layout port_binding boot_handoff spm_gate \ diff --git a/tests/host/native_wire/Makefile b/tests/host/native_wire/Makefile new file mode 100644 index 00000000..bdb37b23 --- /dev/null +++ b/tests/host/native_wire/Makefile @@ -0,0 +1,128 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. +WOLFHSM := $(ROOT)/lib/wolfHSM +WOLFSSL := $(ROOT)/lib/wolfSSL + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -DWOLFSSL_USER_SETTINGS \ + -UHAVE_CONFIG_H \ + -DWOLFHSM_CFG_NO_CRYPTO \ + -DWOLFHSM_CFG_NO_SYS_TIME \ + -I. \ + -I$(ROOT)/include \ + -I$(WOLFHSM) \ + -I$(WOLFSSL) \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +# wolfCrypt's ECC/ASN sources need GNU libc declarations that strict C11 +# hides; wolfTrust and test sources keep the strict flags above. +WC_CFLAGS := $(filter-out -std=c11 -Werror -pedantic -Wextra,$(CFLAGS)) + +# The native crypto wire and its key backend over a RAM-backed NVM store: +# native_wire.c is port-free, so the packet parser and every operation run +# here exactly as SERVICE_HSM's submit hook drives them on target. +_WT_SRCS := \ + $(ROOT)/src/services/native/native_wire.c \ + $(ROOT)/src/services/native/keyvault.c \ + $(ROOT)/src/services/wolfhsm/wt_hsm_vault.c + +_WH_SRCS := \ + $(WOLFHSM)/src/wh_nvm.c \ + $(WOLFHSM)/src/wh_nvm_flash.c \ + $(WOLFHSM)/src/wh_flash_ramsim.c \ + $(WOLFHSM)/src/wh_flash_unit.c \ + $(WOLFHSM)/src/wh_utils.c + +_WC_SRCS := \ + $(WOLFSSL)/wolfcrypt/src/aes.c \ + $(WOLFSSL)/wolfcrypt/src/asn.c \ + $(WOLFSSL)/wolfcrypt/src/coding.c \ + $(WOLFSSL)/wolfcrypt/src/ecc.c \ + $(WOLFSSL)/wolfcrypt/src/sp_int.c \ + $(WOLFSSL)/wolfcrypt/src/wolfmath.c \ + $(WOLFSSL)/wolfcrypt/src/sha256.c \ + $(WOLFSSL)/wolfcrypt/src/random.c \ + $(WOLFSSL)/wolfcrypt/src/memory.c \ + $(WOLFSSL)/wolfcrypt/src/error.c \ + $(WOLFSSL)/wolfcrypt/src/logging.c \ + $(WOLFSSL)/wolfcrypt/src/hash.c \ + $(WOLFSSL)/wolfcrypt/src/wc_port.c \ + $(WOLFSSL)/wolfcrypt/src/misc.c + +_TEST_SRCS := main.c + +_WT_OBJS := $(patsubst %.c,wt_%.o,$(notdir $(_WT_SRCS))) +_WH_OBJS := $(patsubst %.c,wh_%.o,$(notdir $(_WH_SRCS))) +_WC_OBJS := $(patsubst %.c,wc_%.o,$(notdir $(_WC_SRCS))) +_TEST_OBJS := $(patsubst %.c,%.o,$(notdir $(_TEST_SRCS))) + +ALL_OBJS := $(addprefix $(BUILD_DIR)/,$(_WT_OBJS) $(_WH_OBJS) $(_WC_OBJS) $(_TEST_OBJS)) +TEST_BIN := $(BUILD_DIR)/test_native_wire + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/services/native/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/services/wolfhsm/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wh_%.o: $(WOLFHSM)/src/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wc_%.o: $(WOLFSSL)/wolfcrypt/src/%.c | $(BUILD_DIR) + $(CC) $(WC_CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(ALL_OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(ALL_OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/native_wire/main.c b/tests/host/native_wire/main.c new file mode 100644 index 00000000..6a1d8cda --- /dev/null +++ b/tests/host/native_wire/main.c @@ -0,0 +1,460 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Native crypto wire suite: drives wt_native_submit with real request + * packets over a RAM-backed NVM store, exactly as SERVICE_HSM's relay hands + * them over on target — every operation, the per-client key namespace, usage + * and type enforcement, malformed packets, and AES-GCM tamper rejection. */ + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" + +#include "wolftrust/services/hsm.h" +#include "wolftrust/services/hsm_relay.h" +#include "wolftrust/services/vault_service.h" +#include "wolftrust/services/crypto_native.h" + +#include +#include +#include + +#define TEST_OWNER 6 +#define TEST_NS_GUEST0 (-1) +#define TEST_NS_GUEST1 (-2) + +#define RAMSIM_SIZE (64 * 1024) +#define RAMSIM_SECTOR 4096 +#define RAMSIM_PAGE 8 + +static uint8_t g_flash_memory[RAMSIM_SIZE]; +static whFlashRamsimCfg g_ramsim_cfg; +static whFlashRamsimCtx g_ramsim_ctx; +static const whFlashCb g_ramsim_cb[1] = {WH_FLASH_RAMSIM_CB}; +static whNvmFlashConfig g_nvm_flash_cfg; +static whNvmFlashContext g_nvm_flash_ctx; +static const whNvmCb g_nvm_cb[1] = {WH_NVM_FLASH_CB}; +static whNvmConfig g_nvm_cfg; +static whNvmContext g_nvm_ctx; + +static int g_failures; + +static void check(int ok, const char* what) +{ + if (ok) { + (void)printf("PASS: %s\n", what); + } + else { + (void)printf("FAIL: %s\n", what); + g_failures++; + } +} + +static int test_store_up(void) +{ + (void)memset(&g_ramsim_cfg, 0, sizeof(g_ramsim_cfg)); + g_ramsim_cfg.memory = g_flash_memory; + g_ramsim_cfg.size = RAMSIM_SIZE; + g_ramsim_cfg.sectorSize = RAMSIM_SECTOR; + g_ramsim_cfg.pageSize = RAMSIM_PAGE; + g_ramsim_cfg.erasedByte = 0xFF; + (void)memset(&g_ramsim_ctx, 0, sizeof(g_ramsim_ctx)); + (void)memset(&g_nvm_flash_cfg, 0, sizeof(g_nvm_flash_cfg)); + g_nvm_flash_cfg.cb = g_ramsim_cb; + g_nvm_flash_cfg.context = &g_ramsim_ctx; + g_nvm_flash_cfg.config = &g_ramsim_cfg; + (void)memset(&g_nvm_flash_ctx, 0, sizeof(g_nvm_flash_ctx)); + (void)memset(&g_nvm_cfg, 0, sizeof(g_nvm_cfg)); + g_nvm_cfg.cb = (whNvmCb*)g_nvm_cb; + g_nvm_cfg.context = &g_nvm_flash_ctx; + g_nvm_cfg.config = &g_nvm_flash_cfg; + (void)memset(&g_nvm_ctx, 0, sizeof(g_nvm_ctx)); + if (wh_Nvm_Init(&g_nvm_ctx, &g_nvm_cfg) != WH_ERROR_OK) { + return -1; + } + if (wt_hsm_vault_init(&g_nvm_ctx) != 0) { + return -1; + } + if (wt_hsm_keyvault_init(&g_nvm_ctx) != 0) { + return -1; + } + return 0; +} + +/* One wire round trip as the relay performs it: [header][payload] in, + * [status][payload] out, both bounded by the relay's copied buffers. */ +static int wire(int32_t client, uint32_t op, uint64_t uid, uint32_t usage, + uint32_t key_type, const uint8_t* payload, + size_t payload_len, uint8_t* out, size_t out_cap, + size_t* out_len, psa_status_t* status) +{ + uint8_t req[WT_HSM_RELAY_MSG_MAX]; + uint8_t resp[WT_HSM_RELAY_MSG_MAX]; + wt_crypto_wire_req_t hdr; + size_t resp_len = 0U; + size_t got; + int32_t wire_status = 0; + int rc; + + if (sizeof(hdr) + payload_len > sizeof(req)) { + return -2; + } + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.uid = uid; + hdr.op = op; + hdr.usage = usage; + hdr.key_type = key_type; + (void)memcpy(req, &hdr, sizeof(hdr)); + if (payload_len != 0U) { + (void)memcpy(req + sizeof(hdr), payload, payload_len); + } + rc = wt_native_submit((void*)(intptr_t)TEST_OWNER, client, req, + sizeof(hdr) + payload_len, resp, sizeof(resp), + &resp_len); + if (rc != 0) { + return rc; + } + if (resp_len < sizeof(wire_status)) { + return -3; + } + (void)memcpy(&wire_status, resp, sizeof(wire_status)); + got = resp_len - sizeof(wire_status); + if (out != NULL) { + if (got > out_cap) { + return -4; + } + (void)memcpy(out, resp + sizeof(wire_status), got); + } + if (out_len != NULL) { + *out_len = got; + } + *status = (psa_status_t)wire_status; + return 0; +} + +static void test_random_and_hash(void) +{ + /* SHA-256("abc") */ + static const uint8_t abc_digest[32] = { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad + }; + uint8_t out[WT_HSM_RELAY_MSG_MAX]; + uint8_t zero[64]; + size_t got = 0U; + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int rc; + + (void)memset(zero, 0, sizeof(zero)); + (void)memset(out, 0, sizeof(out)); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_RANDOM, 0U, 64U, 0U, NULL, 0U, + out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_SUCCESS && got == 64U && + memcmp(out, zero, sizeof(zero)) != 0, + "RANDOM returns exactly the requested nonzero bytes"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_RANDOM, 0U, 0U, 0U, NULL, 0U, + out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_ARGUMENT && got == 0U, + "RANDOM refuses a zero-length request"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_RANDOM, 0U, + WT_CRYPTO_RANDOM_MAX + 1U, 0U, NULL, 0U, out, sizeof(out), + &got, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_ARGUMENT && got == 0U, + "RANDOM refuses a request above the per-call cap"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_HASH, 0U, 0U, 0U, + (const uint8_t*)"abc", 3U, out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_SUCCESS && got == sizeof(abc_digest) && + memcmp(out, abc_digest, sizeof(abc_digest)) == 0, + "HASH returns the SHA-256 known answer"); +} + +static void test_p256_lifecycle(void) +{ + static const uint8_t digest[32] = { + 0x57, 0x54, 0x4e, 0x57, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c + }; + uint8_t out[WT_HSM_RELAY_MSG_MAX]; + uint8_t sig[WT_VAULT_KEY_SIG_LEN]; + uint8_t verify_in[WT_VAULT_KEY_DIGEST_LEN + WT_VAULT_KEY_SIG_LEN]; + size_t got = 0U; + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int rc; + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_GENERATE, 0x1001ULL, + WT_VAULT_KEY_USAGE_SIGN | WT_VAULT_KEY_USAGE_VERIFY, + WT_VAULT_KEY_P256, NULL, 0U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_SUCCESS, "P-256 key generates"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_GENERATE, 0x1001ULL, + WT_VAULT_KEY_USAGE_SIGN, WT_VAULT_KEY_P256, NULL, 0U, NULL, + 0U, &got, &status); + check(rc == 0 && status == PSA_ERROR_ALREADY_EXISTS, + "generate refuses an occupied UID"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_EXPORT_PUBLIC, 0x1001ULL, 0U, + 0U, NULL, 0U, out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_SUCCESS && got == WT_VAULT_KEY_PUB_LEN && + out[0] == 0x04U, + "public export returns the 65-byte X9.63 point"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_SIGN, 0x1001ULL, 0U, 0U, + digest, sizeof(digest), sig, sizeof(sig), &got, &status); + check(rc == 0 && status == PSA_SUCCESS && got == sizeof(sig), + "sign returns a 64-byte r||s signature"); + + (void)memcpy(verify_in, digest, sizeof(digest)); + (void)memcpy(verify_in + sizeof(digest), sig, sizeof(sig)); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_VERIFY, 0x1001ULL, 0U, 0U, + verify_in, sizeof(verify_in), NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_SUCCESS, "verify accepts the signature"); + + verify_in[0] ^= 0x01U; + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_VERIFY, 0x1001ULL, 0U, 0U, + verify_in, sizeof(verify_in), NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_SIGNATURE, + "verify rejects a tampered digest"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_VERIFY, 0x1001ULL, 0U, 0U, + verify_in, sizeof(verify_in) - 1U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_ARGUMENT, + "verify refuses a malformed digest+signature payload"); + + /* The key namespace is (owner, SPM-stamped client, uid): another client + * using the same UID reaches nothing. */ + rc = wire(TEST_NS_GUEST1, WT_CRYPTO_OP_KEY_SIGN, 0x1001ULL, 0U, 0U, + digest, sizeof(digest), sig, sizeof(sig), &got, &status); + check(rc == 0 && status == PSA_ERROR_DOES_NOT_EXIST && got == 0U, + "another client cannot sign with this client's key"); + rc = wire(TEST_NS_GUEST1, WT_CRYPTO_OP_KEY_EXPORT_PUBLIC, 0x1001ULL, 0U, + 0U, NULL, 0U, out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_ERROR_DOES_NOT_EXIST, + "another client cannot export this client's public key"); + rc = wire(TEST_NS_GUEST1, WT_CRYPTO_OP_KEY_DESTROY, 0x1001ULL, 0U, 0U, + NULL, 0U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_ERROR_DOES_NOT_EXIST, + "another client cannot destroy this client's key"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_DESTROY, 0x1001ULL, 0U, 0U, + NULL, 0U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_SUCCESS, "owner destroys the key"); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_EXPORT_PUBLIC, 0x1001ULL, 0U, + 0U, NULL, 0U, out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_ERROR_DOES_NOT_EXIST, + "a destroyed key is gone"); +} + +static void test_usage_and_type(void) +{ + static const uint8_t digest[32] = { 0x01 }; + uint8_t out[WT_HSM_RELAY_MSG_MAX]; + size_t got = 0U; + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int rc; + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_GENERATE, 0x2001ULL, + WT_VAULT_KEY_USAGE_VERIFY, WT_VAULT_KEY_P256, NULL, 0U, NULL, + 0U, &got, &status); + check(rc == 0 && status == PSA_SUCCESS, "verify-only P-256 key generates"); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_SIGN, 0x2001ULL, 0U, 0U, + digest, sizeof(digest), out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_ERROR_NOT_PERMITTED && got == 0U, + "sign is refused without the SIGN usage bit"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_GENERATE, 0x2002ULL, 0U, + WT_VAULT_KEY_P256, NULL, 0U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_ARGUMENT, + "generate refuses an empty usage mask"); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_GENERATE, 0x2003ULL, + WT_VAULT_KEY_USAGE_SIGN, 0x7fU, NULL, 0U, NULL, 0U, &got, + &status); + check(rc == 0 && status == PSA_ERROR_NOT_SUPPORTED, + "generate refuses an unknown key type"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_GENERATE, 0x2004ULL, + WT_VAULT_KEY_USAGE_ENCRYPT | WT_VAULT_KEY_USAGE_DECRYPT, + WT_VAULT_KEY_AES256, NULL, 0U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_SUCCESS, "AES-256 key generates"); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_SIGN, 0x2004ULL, 0U, 0U, + digest, sizeof(digest), out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_ERROR_NOT_PERMITTED, + "an AES key cannot be used to sign"); +} + +static void test_aes_gcm(void) +{ + static const uint8_t plaintext[] = "native wire AES-GCM round trip"; + uint8_t ct[WT_HSM_RELAY_MSG_MAX]; + uint8_t pt[WT_HSM_RELAY_MSG_MAX]; + size_t ct_len = 0U; + size_t pt_len = 0U; + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int rc; + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_ENCRYPT, 0x2004ULL, 0U, 0U, + plaintext, sizeof(plaintext), ct, sizeof(ct), &ct_len, + &status); + check(rc == 0 && status == PSA_SUCCESS && + ct_len == sizeof(plaintext) + WT_VAULT_KEY_NONCE_LEN + + WT_VAULT_KEY_TAG_LEN, + "encrypt frames nonce || ciphertext || tag"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_DECRYPT, 0x2004ULL, 0U, 0U, + ct, ct_len, pt, sizeof(pt), &pt_len, &status); + check(rc == 0 && status == PSA_SUCCESS && pt_len == sizeof(plaintext) && + memcmp(pt, plaintext, sizeof(plaintext)) == 0, + "decrypt recovers the plaintext"); + + ct[ct_len - 1U] ^= 0x01U; + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_DECRYPT, 0x2004ULL, 0U, 0U, + ct, ct_len, pt, sizeof(pt), &pt_len, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_SIGNATURE && pt_len == 0U, + "decrypt rejects a tampered tag and returns no plaintext"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_DECRYPT, 0x2004ULL, 0U, 0U, + ct, WT_VAULT_KEY_NONCE_LEN, pt, sizeof(pt), &pt_len, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_ARGUMENT, + "decrypt refuses input shorter than nonce + tag"); +} + +static void test_import(void) +{ + static const uint8_t scalar[WT_VAULT_KEY_MATERIAL_LEN] = { + 0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16, + 0x6b, 0x5c, 0x21, 0x57, 0x67, 0xb1, 0xd6, 0x93, + 0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8, 0x9b, 0x12, + 0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f, 0x67, 0x21 + }; + /* RFC 6979 A.2.5 public point for the scalar above. */ + static const uint8_t expect_x[8] = { + 0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31 + }; + uint8_t out[WT_HSM_RELAY_MSG_MAX]; + size_t got = 0U; + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int rc; + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_IMPORT, 0x3001ULL, + WT_VAULT_KEY_USAGE_SIGN, WT_VAULT_KEY_P256, scalar, + sizeof(scalar), NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_SUCCESS, "P-256 scalar imports"); + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_EXPORT_PUBLIC, 0x3001ULL, 0U, + 0U, NULL, 0U, out, sizeof(out), &got, &status); + check(rc == 0 && status == PSA_SUCCESS && got == WT_VAULT_KEY_PUB_LEN && + out[0] == 0x04U && memcmp(out + 1, expect_x, sizeof(expect_x)) == 0, + "imported key derives the RFC 6979 public point"); + + rc = wire(TEST_NS_GUEST0, WT_CRYPTO_OP_KEY_IMPORT, 0x3002ULL, + WT_VAULT_KEY_USAGE_SIGN, WT_VAULT_KEY_P256, scalar, + sizeof(scalar) - 1U, NULL, 0U, &got, &status); + check(rc == 0 && status == PSA_ERROR_INVALID_ARGUMENT, + "import refuses a short scalar"); +} + +static void test_malformed(void) +{ + uint8_t req[WT_HSM_RELAY_MSG_MAX]; + uint8_t resp[WT_HSM_RELAY_MSG_MAX]; + uint8_t out[WT_HSM_RELAY_MSG_MAX]; + wt_crypto_wire_req_t hdr; + size_t resp_len = 0U; + size_t got = 0U; + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int32_t wire_status = 0; + int rc; + + (void)memset(req, 0, sizeof(req)); + rc = wt_native_submit((void*)(intptr_t)TEST_OWNER, TEST_NS_GUEST0, req, + sizeof(hdr) - 1U, resp, sizeof(resp), &resp_len); + check(rc != 0, "a packet shorter than the header is refused"); + + rc = wt_native_submit((void*)(intptr_t)TEST_OWNER, TEST_NS_GUEST0, req, + sizeof(hdr), resp, sizeof(wire_status) - 1U, + &resp_len); + check(rc != 0, "a response buffer smaller than the status is refused"); + + rc = wt_native_submit((void*)(intptr_t)TEST_OWNER, TEST_NS_GUEST0, NULL, + sizeof(hdr), resp, sizeof(resp), &resp_len); + check(rc != 0, "a NULL request is refused"); + + rc = wire(TEST_NS_GUEST0, 0x7fffU, 0U, 0U, 0U, NULL, 0U, out, + sizeof(out), &got, &status); + check(rc == 0 && status == PSA_ERROR_NOT_SUPPORTED && got == 0U, + "an unknown operation is refused"); + + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.op = WT_CRYPTO_OP_RANDOM; + hdr.usage = 16U; + hdr.reserved = 0xdeadbeefU; + (void)memcpy(req, &hdr, sizeof(hdr)); + resp_len = 0U; + rc = wt_native_submit((void*)(intptr_t)TEST_OWNER, TEST_NS_GUEST0, req, + sizeof(hdr), resp, sizeof(resp), &resp_len); + (void)memcpy(&wire_status, resp, sizeof(wire_status)); + check(rc == 0 && resp_len == sizeof(wire_status) && + wire_status == (int32_t)PSA_ERROR_INVALID_ARGUMENT, + "a nonzero reserved field is refused"); + + /* A response buffer too small for the public point: status only. */ + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.uid = 0x3001ULL; + hdr.op = WT_CRYPTO_OP_KEY_EXPORT_PUBLIC; + (void)memcpy(req, &hdr, sizeof(hdr)); + rc = wt_native_submit((void*)(intptr_t)TEST_OWNER, TEST_NS_GUEST0, req, + sizeof(hdr), resp, + sizeof(wire_status) + WT_VAULT_KEY_PUB_LEN - 1U, + &resp_len); + (void)memcpy(&wire_status, resp, sizeof(wire_status)); + check(rc == 0 && resp_len == sizeof(wire_status) && + wire_status == (int32_t)PSA_ERROR_BUFFER_TOO_SMALL, + "an undersized output buffer yields BUFFER_TOO_SMALL and no data"); +} + +int main(void) +{ + if (test_store_up() != 0) { + (void)fprintf(stderr, "NVM/keyvault bring-up failed\n"); + return 1; + } + + test_random_and_hash(); + test_p256_lifecycle(); + test_usage_and_type(); + test_aes_gcm(); + test_import(); + test_malformed(); + + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: native crypto wire over the vault key backend\n"); + return 0; +} diff --git a/tests/host/native_wire/user_settings.h b/tests/host/native_wire/user_settings.h new file mode 100644 index 00000000..2bb04ff8 --- /dev/null +++ b/tests/host/native_wire/user_settings.h @@ -0,0 +1,65 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Host wolfCrypt profile for the native crypto wire suite: the same + * algorithm scope as the Secure image (P-256, AES-GCM, SHA-256, HashDRBG) + * on portable C math. */ + +#ifndef USER_SETTINGS_H +#define USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define WOLFSSL_USER_IO +#define NO_TLS + +#define HAVE_ECC +#define ECC_USER_CURVES +#define ECC_TIMING_RESISTANT +#define WOLFSSL_SP_MATH_ALL + +#define HAVE_AESGCM +#define WOLFSSL_SHA256 +#define HAVE_HASHDRBG + +#define NO_SHA +#define NO_RSA +#define NO_DH +#define NO_DSA +#define NO_DES3 +#define NO_RC4 +#define NO_MD4 +#define NO_MD5 +#define NO_PWDBASED +#define NO_PKCS7 +#define NO_ASN_TIME + +#define WOLFSSL_USE_ALIGN +#define WOLFSSL_IGNORE_FILE_WARN +#define NO_MAIN_DRIVER +#define NO_OLD_RNGNAME +#define NO_OLD_WC_NAMES +#define NO_OLD_SSL_NAMES +#define NO_OLD_SHA_NAMES +#define NO_OLD_MD5_NAME +#define NO_ERROR_STRINGS +#define NO_ERROR_QUEUE +#define NO_INLINE + +#endif /* USER_SETTINGS_H */ diff --git a/tests/host/native_wire_client/Makefile b/tests/host/native_wire_client/Makefile new file mode 100644 index 00000000..c1811e7f --- /dev/null +++ b/tests/host/native_wire_client/Makefile @@ -0,0 +1,82 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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, see . + +ROOT := ../../.. +WOLFHSM := $(ROOT)/lib/wolfHSM +WOLFSSL := $(ROOT)/lib/wolfSSL + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -DWOLFSSL_USER_SETTINGS \ + -UHAVE_CONFIG_H \ + -DWOLFHSM_CFG_NO_CRYPTO \ + -DWOLFHSM_CFG_NO_SYS_TIME \ + -I. \ + -I$(ROOT)/include \ + -I$(WOLFHSM) \ + -I$(WOLFSSL) \ + -std=c11 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) + +# The client is marshalling only: no wolfCrypt, no wolfHSM store. A scripted +# PSA transport in main.c stands in for the SERVICE_HSM door. +_SRCS := \ + $(ROOT)/src/client/crypto_native_client.c \ + main.c + +_OBJS := $(addprefix $(BUILD_DIR)/,crypto_native_client.o main.o) +TEST_BIN := $(BUILD_DIR)/test_native_wire_client + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/crypto_native_client.o: $(ROOT)/src/client/crypto_native_client.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(_OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(_OBJS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/native_wire_client/main.c b/tests/host/native_wire_client/main.c new file mode 100644 index 00000000..62e74335 --- /dev/null +++ b/tests/host/native_wire_client/main.c @@ -0,0 +1,358 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Native wire client suite: a scripted PSA transport stands in for the + * SERVICE_HSM door so the client's connect caching, restart heal, retry + * exhaustion, chunked randomness, response framing, and output-length + * handling are all exercised without a Secure image. */ + +#include "psa/client.h" +#include "psa/error.h" +#include "wolftrust/services/hsm_relay.h" +#include "wolftrust/services/crypto_native.h" +#include "wolftrust/crypto_native_client.h" + +#include +#include +#include + +static int g_failures; + +static void check(int ok, const char* what) +{ + if (ok) { + (void)printf("PASS: %s\n", what); + } + else { + (void)printf("FAIL: %s\n", what); + g_failures++; + } +} + +/* ---- Scripted PSA transport ------------------------------------------- */ +static int g_connect_refuse; /* refuse this many connects, then grant */ +static int g_call_fail; /* fail this many calls, then serve */ +static int32_t g_resp_status; /* wire status the served call reports */ +static size_t g_resp_payload; /* served payload byte count */ +static int g_resp_len_override; /* forced outvec len, or -1 for real */ +static int g_connects; +static int g_calls; +static int g_closes; +static uint32_t g_last_random_usage; + +static void mock_reset(void) +{ + g_connect_refuse = 0; + g_call_fail = 0; + g_resp_status = (int32_t)PSA_SUCCESS; + g_resp_payload = 0U; + g_resp_len_override = -1; + g_connects = 0; + g_calls = 0; + g_closes = 0; + g_last_random_usage = 0U; +} + +uint32_t psa_framework_version(void) { return 0x0100u; } +uint32_t psa_version(uint32_t sid) { (void)sid; return 1u; } + +psa_handle_t psa_connect(uint32_t sid, uint32_t version) +{ + (void)sid; + (void)version; + g_connects++; + if (g_connect_refuse > 0) { + g_connect_refuse--; + return (psa_handle_t)PSA_ERROR_CONNECTION_REFUSED; + } + return (psa_handle_t)42; +} + +void psa_close(psa_handle_t handle) +{ + (void)handle; + g_closes++; +} + +psa_status_t psa_call(psa_handle_t handle, int32_t type, + const psa_invec* in_vec, size_t in_len, + psa_outvec* out_vec, size_t out_len) +{ + wt_crypto_wire_req_t hdr; + uint8_t* out; + int32_t wire_status; + size_t payload; + size_t i; + + (void)type; + g_calls++; + if (handle <= 0 || in_vec == NULL || in_len != 1U || out_vec == NULL || + out_len != 1U) { + return PSA_ERROR_PROGRAMMER_ERROR; + } + if (g_call_fail > 0) { + g_call_fail--; + return PSA_ERROR_COMMUNICATION_FAILURE; + } + + /* Serve a framed [status][payload] response. RANDOM echoes exactly the + * requested byte count so the client's chunk reassembly is exercised. */ + (void)memset(&hdr, 0, sizeof(hdr)); + if (in_vec[0].len >= sizeof(hdr)) { + (void)memcpy(&hdr, in_vec[0].base, sizeof(hdr)); + } + payload = g_resp_payload; + if (hdr.op == WT_CRYPTO_OP_RANDOM) { + g_last_random_usage = hdr.usage; + payload = hdr.usage; + } + out = (uint8_t*)out_vec[0].base; + wire_status = g_resp_status; + (void)memcpy(out, &wire_status, sizeof(wire_status)); + for (i = 0U; i < payload && sizeof(wire_status) + i < out_vec[0].len; + i++) { + out[sizeof(wire_status) + i] = (uint8_t)(0xA0U + (i & 0x0FU)); + } + if (g_resp_len_override >= 0) { + out_vec[0].len = (size_t)g_resp_len_override; + } + else { + out_vec[0].len = sizeof(wire_status) + payload; + } + return PSA_SUCCESS; +} + +/* The client caches its connection handle across calls. These drive it to a + * known warm (handle cached) or cold (handle dropped) state before a test + * asserts connect/call counts. Call mock_reset() afterwards to zero counters; + * neither helper's handle state is disturbed by mock_reset. */ +static void client_go_warm(void) +{ + uint8_t b[4]; + + g_connect_refuse = 0; + g_call_fail = 0; + g_resp_status = (int32_t)PSA_SUCCESS; + g_resp_payload = 0U; + g_resp_len_override = -1; + (void)wt_crypto_native_random(b, 4U); +} + +static void client_go_cold(void) +{ + uint8_t b[4]; + + g_connect_refuse = 1000000; + g_call_fail = 1000000; + (void)wt_crypto_native_random(b, 4U); +} + +/* ---- Tests ------------------------------------------------------------- */ +static void test_happy_and_status(void) +{ + uint8_t buf[64]; + psa_status_t st; + + mock_reset(); + g_resp_payload = 32U; + st = wt_crypto_native_random(buf, 32U); + check(st == PSA_SUCCESS && g_connects == 1 && g_calls == 1, + "first request connects once and calls once"); + + mock_reset(); + g_resp_status = (int32_t)PSA_ERROR_NOT_PERMITTED; + st = wt_crypto_native_random(buf, 16U); + check(st == PSA_ERROR_NOT_PERMITTED, + "the secure-side status passes through unchanged"); +} + +static void test_heal_after_restart(void) +{ + uint8_t buf[32]; + psa_status_t st; + + /* One in-flight call fails as a partition restarts; the client closes the + * torn handle, reconnects, and the retry succeeds. Start warm so the + * first call uses the cached handle, as it would on a live guest. */ + client_go_warm(); + mock_reset(); + g_call_fail = 1; + st = wt_crypto_native_random(buf, 16U); + check(st == PSA_SUCCESS && g_closes >= 1 && g_calls == 2, + "a torn call heals by reconnecting and retrying"); +} + +static void test_mutating_no_replay(void) +{ + wt_crypto_wire_req_t hdr; + uint8_t out[16]; + size_t got = 0U; + psa_status_t st; + uint32_t muta[3]; + size_t i; + + /* A mutating op whose call fails after reaching the door is ambiguous: the + * store may already be changed, so the client must NOT replay it. Unlike + * idempotent RANDOM (test_heal_after_restart), it surfaces the failure + * after exactly one call rather than retrying. */ + muta[0] = WT_CRYPTO_OP_KEY_GENERATE; + muta[1] = WT_CRYPTO_OP_KEY_IMPORT; + muta[2] = WT_CRYPTO_OP_KEY_DESTROY; + for (i = 0U; i < 3U; i++) { + client_go_warm(); + mock_reset(); + g_call_fail = 1; + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.op = muta[i]; + hdr.uid = 0x3001U; + st = wt_crypto_native_call(&hdr, NULL, 0U, out, sizeof(out), &got); + check(st == PSA_ERROR_COMMUNICATION_FAILURE && g_calls == 1 && + g_closes >= 1, + "a failed mutating call surfaces the failure without replay"); + } +} + +static void test_connect_refused(void) +{ + uint8_t buf[16]; + psa_status_t st; + + /* The door refuses every connect: no call is ever issued and the client + * reports the refusal rather than spinning forever. */ + client_go_cold(); + mock_reset(); + g_connect_refuse = 1000000; + st = wt_crypto_native_random(buf, 16U); + check(st == PSA_ERROR_CONNECTION_REFUSED && g_calls == 0, + "a permanently refused connection fails closed with no call"); +} + +static void test_retry_exhaustion(void) +{ + uint8_t buf[16]; + psa_status_t st; + + /* Every call fails: the client exhausts its bounded retries and returns + * the transport failure instead of looping without end. */ + mock_reset(); + g_call_fail = 1000000; + st = wt_crypto_native_random(buf, 16U); + check(st == PSA_ERROR_COMMUNICATION_FAILURE && g_calls > 1 && + g_calls <= 64, + "exhausted retries return the transport failure"); +} + +static void test_response_framing(void) +{ + wt_crypto_wire_req_t hdr; + uint8_t out[128]; + size_t got = 999U; + psa_status_t st; + + /* A response shorter than the status word is a framing failure. */ + mock_reset(); + g_resp_len_override = (int)sizeof(int32_t) - 1; + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.op = WT_CRYPTO_OP_HASH; + st = wt_crypto_native_call(&hdr, (const uint8_t*)"abc", 3U, out, + sizeof(out), &got); + check(st == PSA_ERROR_COMMUNICATION_FAILURE, + "a response shorter than the status word is refused"); + + /* A payload larger than the caller's buffer yields BUFFER_TOO_SMALL. */ + mock_reset(); + g_resp_payload = 40U; + got = 999U; + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.op = WT_CRYPTO_OP_KEY_EXPORT_PUBLIC; + hdr.uid = 7U; + st = wt_crypto_native_call(&hdr, NULL, 0U, out, 16U, &got); + check(st == PSA_ERROR_BUFFER_TOO_SMALL, + "a payload larger than the output buffer is refused"); + + /* A status-only success (no payload) reports zero output length. */ + mock_reset(); + got = 999U; + (void)memset(&hdr, 0, sizeof(hdr)); + hdr.op = WT_CRYPTO_OP_KEY_DESTROY; + hdr.uid = 7U; + st = wt_crypto_native_call(&hdr, NULL, 0U, NULL, 0U, &got); + check(st == PSA_SUCCESS && got == 0U, + "a status-only response reports no payload"); +} + +static void test_chunked_random(void) +{ + uint8_t buf[600]; + size_t i; + int nonzero = 0; + psa_status_t st; + + /* 600 bytes exceeds the 256-byte per-call cap, so the client must issue + * three calls and stitch the pieces together. */ + mock_reset(); + (void)memset(buf, 0, sizeof(buf)); + st = wt_crypto_native_random(buf, sizeof(buf)); + for (i = 0U; i < sizeof(buf); i++) { + if (buf[i] != 0U) { + nonzero = 1; + } + } + check(st == PSA_SUCCESS && g_calls == 3 && nonzero != 0, + "a request above the per-call cap is split across calls"); + + mock_reset(); + check(wt_crypto_native_random(NULL, 16U) == PSA_ERROR_INVALID_ARGUMENT && + wt_crypto_native_random(buf, 0U) == PSA_ERROR_INVALID_ARGUMENT, + "random refuses a NULL buffer or a zero length"); +} + +static void test_rng_stub(void) +{ + uint8_t buf[8]; + + mock_reset(); + check(wolftrust_guest_rng_stub(buf, sizeof(buf)) == 0, + "the wolfCrypt RNG hook returns success when the wire serves"); + + client_go_cold(); + mock_reset(); + g_connect_refuse = 1000000; + check(wolftrust_guest_rng_stub(buf, sizeof(buf)) != 0, + "the wolfCrypt RNG hook returns failure when the wire is down"); +} + +int main(void) +{ + test_happy_and_status(); + test_heal_after_restart(); + test_mutating_no_replay(); + test_connect_refused(); + test_retry_exhaustion(); + test_response_framing(); + test_chunked_random(); + test_rng_stub(); + + if (g_failures != 0) { + return 1; + } + (void)printf("PASS: native crypto wire client recovery and framing\n"); + return 0; +} diff --git a/tests/host/native_wire_client/user_settings.h b/tests/host/native_wire_client/user_settings.h new file mode 100644 index 00000000..2043f075 --- /dev/null +++ b/tests/host/native_wire_client/user_settings.h @@ -0,0 +1,31 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The native wire client is marshalling only; it pulls in no wolfCrypt. This + * settings header exists so the shared wolfTrust headers compile on host. */ + +#ifndef USER_SETTINGS_H +#define USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define WOLFSSL_USER_IO +#define NO_TLS + +#endif /* USER_SETTINGS_H */ diff --git a/tests/target/lib/engine.sh b/tests/target/lib/engine.sh new file mode 100644 index 00000000..c9d1df12 --- /dev/null +++ b/tests/target/lib/engine.sh @@ -0,0 +1,52 @@ +# engine.sh +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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, see . + +# Resolve the crypto engine exactly as mk/common.mk does, so the Secure image +# and every guest always agree. POSIX sh; source it, do not execute it. +# Legacy WT_ENGINE_HSM=0/1 maps onto WT_ENGINE; a conflict or an unsupported +# value stops the build instead of producing a hybrid image. + +wt_engine_legacy="" +case "${WT_ENGINE_HSM:-}" in + 1) wt_engine_legacy="hsm" ;; + 0) wt_engine_legacy="native" ;; + "") ;; + *) + echo "unsupported WT_ENGINE_HSM='${WT_ENGINE_HSM}' (want 0 or 1)" >&2 + exit 2 + ;; +esac + +if [ -n "${WT_ENGINE:-}" ] && [ -n "$wt_engine_legacy" ] && \ + [ "$WT_ENGINE" != "$wt_engine_legacy" ]; then + echo "conflicting engine selectors: WT_ENGINE=$WT_ENGINE but" \ + "WT_ENGINE_HSM=$WT_ENGINE_HSM selects $wt_engine_legacy" >&2 + exit 2 +fi + +WT_ENGINE="${WT_ENGINE:-${wt_engine_legacy:-native}}" +case "$WT_ENGINE" in + native|hsm) ;; + *) + echo "unsupported WT_ENGINE='$WT_ENGINE' (want native or hsm)" >&2 + exit 2 + ;; +esac +export WT_ENGINE +unset wt_engine_legacy diff --git a/tests/target/run_h5_suite.sh b/tests/target/run_h5_suite.sh index ea1ea7eb..e7885807 100755 --- a/tests/target/run_h5_suite.sh +++ b/tests/target/run_h5_suite.sh @@ -16,6 +16,7 @@ repo="$(cd "$(dirname "$0")/../.." && pwd)" runner="$repo/tests/target/run_h5_hardware.sh" img="${WT_H5_DOCKER_IMAGE:-}" scenarios="${WT_H5_SCENARIOS:-positive restart crossdomain confboot}" +. "$repo/tests/target/lib/engine.sh" if ! "$repo/tests/target/detect_h5.sh" >/dev/null 2>&1; then echo "SKIP: H5 hardware suite ($("$repo/tests/target/detect_h5.sh" 2>&1))" @@ -24,7 +25,8 @@ fi build_one() { if [ -n "$img" ]; then - docker run --rm -v "$repo":/workspace -w /workspace "$img" \ + docker run --rm -e WT_ENGINE="$WT_ENGINE" \ + -v "$repo":/work -w /work "$img" \ bash tests/target/run_h5_hardware.sh build "$1" else "$runner" build "$1" diff --git a/tests/target/run_m33mu_scenario.sh b/tests/target/run_m33mu_scenario.sh index 2b7ca1d9..40b76c36 100755 --- a/tests/target/run_m33mu_scenario.sh +++ b/tests/target/run_m33mu_scenario.sh @@ -67,6 +67,9 @@ git config --global --add safe.directory '*' git config --global --add safe.directory "$repo" # --- Workflow env (wolfboot-wolftrust-m33mu job). Keep in sync with the yml. --- +# Crypto engine under test: hsm (wolfHSM server) or native (direct wolfCrypt). +# Flows into the secure image build and both guest builds. +. "$repo/tests/target/lib/engine.sh" export CROSS_COMPILE=/usr/local/bin/arm-none-eabi- export ZEPHYR_TOOLCHAIN_VARIANT=cross-compile export WT_SECURE_FLASH_BASE=0x0C060000 @@ -162,6 +165,11 @@ elif [ "$scenario" = "remeasureneg" ]; then secure_flags="WT_REMEASURE_PROBE=1" elif [ "$scenario" = "bootupdate" ]; then secure_flags="WT_BOOTUPDATE_PROBE=1" +elif [ "$scenario" = "spbudgetneg" ]; then + # Must land in the FIRST secure build: the pre-patch stash taken right + # after it is what gets signed and flashed, so a probe assigned in the + # guest chain below never reaches the image. + secure_flags="WT_SP_FAULT_ALWAYS_PROBE=1" elif [ "$scenario" = "vnet" ]; then # Mediated virtual network (WT-FFM-0058): the production chain with the # SERVICE_VNET partition compiled in; guests are the bare-metal wolfIP pair. @@ -211,8 +219,6 @@ elif [ "$scenario" = "fwustage" ]; then guest_flags="WT_FWU_PROBE=1" elif [ "$scenario" = "gtzcneg" ]; then guest_flags="WT_MPU_BYPASS_PROBE=1" -elif [ "$scenario" = "spbudgetneg" ]; then - secure_flags="WT_SP_FAULT_ALWAYS_PROBE=1" fi # Guest images per scenario: the vnet scenario swaps the Zephyr/FreeRTOS pair