From 66bf4a6e563fc77de844cbad2c738779da9e23a1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 1 Oct 2026 15:07:44 -0700 Subject: [PATCH 1/9] State the FF-M isolation level 3 claim and trim the isolation docs --- README.md | 3 +- docs/Architecture.md | 9 +- docs/Crypto-Engines.md | 5 +- docs/Home.md | 2 +- docs/Security-Model.md | 249 +++++++++++++++---------------------- docs/TF-M-Compatibility.md | 25 +--- docs/Testing.md | 107 +++++----------- docs/Threat-Model.md | 28 ++--- 8 files changed, 150 insertions(+), 278 deletions(-) diff --git a/README.md b/README.md index 4800307e..1c0699da 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,8 @@ On the STM32H563 reference chain, wolfBoot authenticates wolfTrust, Armv8-M TrustZone isolates the Secure runtime from Non-secure guests, and STM32 Global TrustZone Controller (GTZC) memory attribution isolates guest RAM. Zephyr and FreeRTOS reference guests use wolfPSA's PSA Crypto API through five Cortex-M -Security Extensions (CMSE) gateway veneers. Those mechanisms describe the +Security Extensions (CMSE) gateway veneers. The Secure side meets PSA FF-M +isolation level 3 ([Security Model](docs/Security-Model.md#ff-m-isolation-level-3)). Those mechanisms describe the current reference port, not a requirement imposed on every intended port. ## Ports diff --git a/docs/Architecture.md b/docs/Architecture.md index 697ace94..9cecac26 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -80,18 +80,15 @@ The reference port combines several mechanisms: - CMSE checks require every request pointer to be Non-secure and inside the active guest's declared readable or writable window. - The Secure MPU gives each Secure Partition thread shared read/execute image - text, read-only constants, its private stack and data, and only its declared - shared resources. + text, read-only constants, and its own stack and data band. Together these + meet FF-M isolation level 3; see + [Security Model](Security-Model.md#ff-m-isolation-level-3). All shipped service loops are scheduled as unprivileged Secure coroutines. Operations requiring wider Secure access, including flash programming, entropy, NVM locking, and platform reset, trap through privileged SVC handlers that verify the calling partition and operation. -The single-image design shares executable text among Secure Partitions. The -MPU isolates writable state, not code identity; this is an explicit difference -from separately linked partition images. - Both guest images share one Non-secure flash attribution window, so a privileged guest can read peer flash. WRP plus `WT_GUEST_FLASH_WRP=1` protects guest-flash integrity but not confidentiality. Peripheral and Non-secure NVIC attribution diff --git a/docs/Crypto-Engines.md b/docs/Crypto-Engines.md index c677aa3c..931b3834 100644 --- a/docs/Crypto-Engines.md +++ b/docs/Crypto-Engines.md @@ -10,9 +10,8 @@ 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. +recovery path. Both engines meet FF-M isolation level 3; see +[Security Model](Security-Model.md#ff-m-isolation-level-3). ## At a glance diff --git a/docs/Home.md b/docs/Home.md index 6916ccc9..2f5e03b2 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -92,7 +92,7 @@ port's five CMSE gateway veneers. | Authenticated chain | The boot port supplies authenticated measurement, lifecycle, and version data. The reference integration uses wolfBoot and signature-covered guest records. | | 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). | +| 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 and meets FF-M isolation level 3; see [Security Model](Security-Model.md#ff-m-isolation-level-3). | | 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. | diff --git a/docs/Security-Model.md b/docs/Security-Model.md index 66103458..9efd19c6 100644 --- a/docs/Security-Model.md +++ b/docs/Security-Model.md @@ -8,6 +8,49 @@ RAM isolation. Its security properties come from the authenticated boot chain, hardware attribution, SPM-owned identity, copied IPC, and service-specific policy. +## FF-M isolation level 3 + +On the STM32H563, wolfTrust implements isolation level 3 of the PSA Firmware +Framework for M 1.0 (Arm DEN 0063) and meets every mandatory isolation rule at +that level, with either crypto engine. The rules are cited by number below; +their text is in the specification. + +| Requirement | wolfTrust | +| --- | --- | +| I1, only Code is executable (section 3.1.2) | Partition tables map data execute-never, the SPM maps all writable Secure RAM execute-never, and the domain validator refuses a writable and executable resource. | +| I2, only Private data is writable (section 3.1.2) | Code and constant data are read-only in every partition table; a partition can write only its own stack and data band. | +| I3, NSPE to SPE (sections 3.1.3 and 3.1.4) | SAU/IDAU and GTZC attribution, five CMSE veneers, and copied IPC. HASH, RNG, and PKA are Secure-only and read back at boot, and Non-secure DMA cannot reach Secure memory. | +| I3, Secure Partition to Secure Partition and to the SPM (sections 3.1.3 and 3.1.4) | Each partition runs unprivileged with its own MPU table. The link check places every writable object in its owner's band or in SPM-private RAM, and boot refuses a composed table that can write another partition's memory or reach SPM-private RAM. | +| I3, indirect access (section 3.1.4) | A partition maps a peripheral only if the port assigns it one that is not a bus master and reads back Secure. | +| Private runtime state (section 4.2.1) | A partition's writable state is its own stack and, where it has one, its own data band, restored from the link image when the partition restarts. The image has no heap. | +| Violation handling (section 3.1.6) | A partition access that breaks a rule faults and terminates that partition. A fault in the SPM halts the platform. | + +### Deviations + +- Only level 3 is implemented. A manifest that declares level 1 or 2 is + refused. +- Manifest `mmio_regions` are not supported, and no port assigns a peripheral + to a partition. Partitions reach hardware through SPM operations pinned to + the calling partition. +- The PSA Root of Trust domain is the SPM and its privileged handlers. The + crypto, storage, attestation, and update services run as Secure Partitions + and are isolated like any other partition. +- A faulted partition is restarted under its manifest restart policy, with a + bounded budget, instead of staying terminated. +- Non-secure guests run privileged. Isolation between guests is a wolfTrust + hypervisor property outside FF-M; see [Guest isolation](#guest-isolation). +- The claim covers the STM32H563 port. The MIMXRT700 port does not make it yet. + +### Evidence + +- The Arm psa-arch-tests FF-M IPC suite, pinned in + [`tests/upstream/psa-arch-tests.rev`](../tests/upstream/psa-arch-tests.rev), + passes 85 tests with 4 heap tests skipped, recorded test by test in + [`tests/target/ffm_ipc_results.txt`](../tests/target/ffm_ipc_results.txt). +- The isolation negatives in [Testing](Testing.md#isolation-scenarios) run on + both engines under M33MU. The STM32H563 hardware suite runs the positive and + conformance scenarios and the negatives the emulator cannot show. + ## Trusted computing base The reference trusted computing base includes: @@ -87,9 +130,9 @@ windows are non-overlapping and hardware-isolated by GTZC. Guest flash windows are non-overlapping but share one Non-secure attribution window and remain mutually readable. WRP plus `WT_GUEST_FLASH_WRP=1` protects -their integrity, not confidentiality. A hostile guest can also reach peripherals -left Non-secure by the boot chain; manifest resource lists do not independently -enforce peripheral ownership in the current port. +their integrity, not confidentiality. A guest can also reach any peripheral the +boot chain leaves Non-secure; the port does not assign peripherals between +guests. On a guest fault, wolfTrust captures the reason, masks its interrupts, and applies the bounded restart policy. If restart is allowed, it clears RAM marked @@ -99,157 +142,61 @@ the guest quarantined. ## Secure Partition isolation -Every shipped service loop runs as a scheduled unprivileged Secure coroutine. -Its Secure MPU view contains: - -- shared read/execute Secure image text; -- read-only Secure image constants; and -- its private stack and its own writable data band. - -No two Secure Partitions share a writable byte (FF-M isolation level 3). The -vault partition owns the NVM object store, its flash context, the sealer, and -its DRBG; the crypto partition (`SERVICE_HSM`) owns the engine state and -reaches persistent key objects only through `SERVICE_VAULT`'s keystore object -door, an FF-M service that serves the registered crypto partition alone and -only the ids and labels the keystore owns; the attestation partition owns the -token state and holds no key material, obtaining every signature and the IAK -public key from `SERVICE_HSM`'s attestation door. Each door client is -confined to its door: the crypto partition's other vault requests and any -Secure Partition's call on `SERVICE_HSM`'s ordinary crypto wire return -`PSA_ERROR_NOT_PERMITTED`. The manifest generator and -the runtime domain validator both refuse a level 3 manifest that shares a -writable resource between partitions, so the split cannot regress silently. -The keystore door hands the crypto partition the key objects it asks for. -That is an IPC contract between two partitions, not shared memory. - -The tables the MPU is programmed from are checked again after they are -composed. Before any partition runs, the SPM refuses to boot if a composed -table grants write access to memory another partition can reach, or any access -to SPM-private RAM. A request from a Secure Partition is checked against that -partition's own composed table, and the SPM acts on one private copy of the -request block, so the memory references it validated are the ones it uses. +Each partition's MPU view holds the shared Secure text (read and execute), the +Secure constants (read-only), its own stack, and its own data band. The vault +owns the NVM store, the sealer, and its DRBG; the crypto partition +(`SERVICE_HSM`) owns the engine state; the attestation partition owns the token +state. Persistent keys cross between them only as FF-M IPC: + +- the crypto partition reads and writes key objects through the keystore door + on `SERVICE_VAULT`; and +- the attestation partition obtains signatures and the IAK public key through + the attestation door on `SERVICE_HSM`. + +Each door serves one registered client partition and refuses every other +caller. + +Mechanical checks: + +- [`tools/secure_owners.txt`](../tools/secure_owners.txt) gives every linked + object one owner. The post-link check fails the build if writable state + lands outside its owner's region, an object has no owner, a shared object + holds writable state, or a production image carries conformance code. + Objects that hold band state are built without LTO so the map names them. +- The generator and the domain validator refuse writable memory shared between + partitions. +- Before any partition runs, boot checks every composed MPU table against the + other partitions' bands and SPM-private RAM. +- A request from a partition is copied once into SPM memory and checked against + that partition's own table. + +Privileged handlers keep the SPM view. Flash, entropy, the NVM lock, and reset +are SVC operations that check the calling partition, and the state they use +lives in SPM-private RAM. ### Partition restart -A restarted partition keeps nothing its previous instance held. The SPM: - -- fails every request the partition was serving and drops every connection to - it to the error state; -- releases every connection and request the partition held as a client, so a - handle from the old instance is refused; -- clears its asserted signals and masks its interrupt lines until the new - instance enables them; -- clears its stack and returns its data band to its link-time image; and -- rebuilds the band with the setup boot ran, from inputs the SPM holds: the - boot handoff, the attestation public key, and the contents of the store. - -Persistent state is the NVM store alone. Nothing in a partition's RAM survives -its restart. - -Privileged handlers retain the SPM view. Flash, entropy, NVM lock, and reset -operations are available only through narrow SVC operations that check the -originating partition: flash and the NVM lock answer only the vault, entropy -only the vault and crypto partitions. The state those handlers consume (the -NVM lock, the flash driver's state, the lifecycle latch, the rollback floors, -and the privileged tasklet stacks) lives in SPM-private RAM, outside every -partition band. - -This is writable-state isolation inside one linked image. Shared executable -text is not per-partition code isolation. - -### Processor state - -Secure floating point is unsupported. Every Armv8-M port retires any FP -context the loader left active (`CONTROL.FPCA`, `SFPA`, `FPCCR.LSPACT`), then -clears CP10/CP11 access in -`CPACR_S`, `CPACR_NS`, and `NSACR`, clears automatic and lazy FP state -preservation in `FPCCR_S`, sets `LSPENS`, `CLRONRET`, and `CLRONRETS`, and -halts unless every one of those bits reads back as programmed. The post-link -check rejects FP instructions and soft-float runtime helpers in the Secure -image, so an FP instruction in a partition raises a UsageFault instead of -creating FP state another context could read. - -The top of the Secure main stack and of every Secure coroutine stack carries -two seal words (`0xFEF5EDA5`), written when the stack is created; boot -refuses to continue unless the main-stack seal is in place with MSP below it. -The SPM checks both words of a coroutine stack when the coroutine yields or is -preempted, and again when it is dispatched. A partition that damaged its own -seal, or whose exception frame was stacked over it, is resumed on a trap -instruction below the seal, so it alone faults and restarts under -its recovery policy with a rebuilt stack; a seal found damaged at dispatch, -while its owner was suspended, halts the platform. The post-link check fails -the build if an allocated section reaches the main-stack seal. - -The seal words mark the fixed top of each stack. Secure stack pointers are not -moved onto a seal while another context runs, so this check does not replace -the integrity signature the processor places in, and checks on, the Secure -frames it stacks itself. - -### SPM stack limit and fault attribution - -The Secure main stack has a fixed size (`WT_SPM_STACK_SIZE`, 16 KiB), reserved -by the linker below `_estack`; the link fails if `.bss` reaches it. Reset sets -`MSPLIM_S` to its bottom right after MSP is placed under the seal words, and -boot refuses to continue unless the limit reads back. An SPM stack overflow -raises `UsageFault.STKOF` on the main stack; the handler halts the platform on -the production panic without touching the stack, and a real `HardFault` -handler does the same for any escalated fault, latching `CFSR`, `HFSR`, -`MMFAR`, `BFAR`, the stacked PC and `EXC_RETURN` for a debugger instead of -spinning silently. - -`BusFault` is enabled alongside `MemManage` and `UsageFault`. All three take one -dispatcher, which attributes the fault by the frame it finds: a Secure Thread -frame on the process stack with a scheduled partition or wolfHSM tasklet -current is that partition's fault (precise bus errors carry `BFAR`; an -imprecise one is drained by the barrier every context switch issues, so it is -still pending against the partition that issued the write), and the partition -restarts under its manifest policy. A frame from a privileged handler, the -bootstrap thread, or no current coroutine is the SPM's own fault and halts the -platform. A Non-secure bus error targets the Secure `BusFault` as well -(`BFHFNMINS` is 0); it is routed to the guest fault path and restarts the -guest. A partition that issues the scheduler's internal guest-return `SVC` is -resumed on the PROGRAMMER ERROR trap and restarts alone. - -### Execute-never Secure RAM - -The SPM whitelist maps every writable Secure RAM region execute-never. The one -executable Secure RAM window is the MIMXRT700's RAMFUNC band, which holds the -NSC gateway and NOR routines and is mapped read-only. While an -unprivileged partition thread runs, its MPU table keeps `PRIVDEFENA` so the -privileged SVC gate and its deputies can reach SPM state, and the default map -would let privileged code execute from SRAM; one extra region therefore covers -the SPM's own RAM (the boot-handoff scratch, `.data`, `.bss`, the main stack) -privileged-only and execute-never on every partition dispatch, and a domain -region inside that window fails the dispatch closed. A production partition -table must leave the region free; the Arm conformance client partition fills -an 8-region MPU with its window grants, and the conformance image counts those -dispatches in `g_wt_xn_denied` instead (the STM32H563 implements 12 regions, -so it is covered there). - -### Link-time optimization - -The Secure image enables GCC link-time optimization by default. LTO can replace -the original input-object names with generated objects, so every object that -places state in a partition data band is compiled without LTO and claimed by -name. CMSE veneers, exception handlers, hand-written assembly, and other -assembly-referenced objects are compiled without LTO so their symbols and -calling conventions stay stable. State from an optimized link unit can only -land in SPM-private RAM. - -[`tools/secure_owners.txt`](../tools/secure_owners.txt) gives every linked -object one owner: a partition, the SPM, or `shared` for code that runs in more -than one domain. The post-link layout check reads the linker map and rejects: - -- a writable input section outside its owner's region; -- an object with no owner; -- a `shared` object that holds writable state; -- a missing exception entry, a linked heap allocator, or a privileged tasklet - stack outside SPM-private RAM; and -- in a production image, any conformance object, symbol, or data. - -The check runs at every link, with and without LTO, so the optimization cannot -weaken the boundaries described above. `WT_LTO=0` disables the optimization -without changing the memory policy. +A restarted partition keeps nothing from its previous instance. Requests it was +serving fail, the connections and handles it held are released, its signals and +interrupt lines are cleared and masked, its stack is cleared, and its band is +restored from the link image. Only the NVM store persists. + +### Processor state and the SPM + +- Secure floating point is disabled and locked at boot (`CPACR`, `NSACR`, and + `FPCCR`, each read back), and the post-link check rejects FP instructions in + the Secure image. +- Every Secure stack carries a two-word seal (`0xFEF5EDA5`) at its top. Boot + checks the main-stack seal, and the SPM checks each coroutine's seal at every + switch; a damaged seal faults that partition, or halts if found at dispatch. +- `MSPLIM_S` bounds the 16 KiB SPM stack, and the link fails if `.bss` reaches + it. +- MemManage, BusFault, and UsageFault share one dispatcher. A fault from a + partition thread restarts that partition, and a fault in the SPM halts the + platform after latching the fault registers. A partition that issues the + scheduler's guest-return SVC is panicked. +- While a partition runs, one MPU region keeps the SPM's own RAM + privileged-only and execute-never. ## Per-guest cryptographic keys diff --git a/docs/TF-M-Compatibility.md b/docs/TF-M-Compatibility.md index 80b30aab..02d23738 100644 --- a/docs/TF-M-Compatibility.md +++ b/docs/TF-M-Compatibility.md @@ -145,25 +145,12 @@ where the platform and feature set are held constant. | Secure Partition entry functions are bound at build time instead of being selected by each manifest's `entry_point` field. | Integration difference | The numeric field validates an executable window, but adding a service also requires a compiled entry wrapper and an explicit start call in `wt_ffm_boot_start_sched()`. | | Service IDs are generated from the selected manifest. | Integration difference | Applications should include generated `psa_manifest/sid.h` instead of hard-coding target-specific values. | -## Isolation-profile interpretation - -The STM32H563 manifest sets `isolation_profile` to -`WT_ISOLATION_PROFILE_LEVEL_3` and declares the capabilities checked -by the manifest validator. Only level 3 is implemented, so the generator and -the validator refuse levels 1 and 2. The manifest declares its Non-secure guests -privileged, matching the runtime, which initializes `CONTROL_NS.nPRIV` to zero. -It declares every Secure Partition unprivileged, and the generator and the -validator refuse a privileged one. TrustZone protects Secure state, GTZC isolates peer -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; every -partition's writable state is its own, and the crypto, vault, and attestation -partitions interact only through FF-M IPC. 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 -actual boundary. +## Isolation level + +wolfTrust implements FF-M isolation level 3 only, and refuses a manifest that +declares level 1 or 2 or a privileged Secure Partition. The rule-by-rule +mapping and the deviations are in +[Security Model](Security-Model.md#ff-m-isolation-level-3). ## Migrating an application diff --git a/docs/Testing.md b/docs/Testing.md index c35de758..c7e5dbf4 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -122,78 +122,34 @@ authenticated-boot failure, rollback, runtime remeasurement, Secure Partition recovery, key and vault isolation, storage recovery, attestation negatives, firmware update, manifest rejection, GTZC behavior, and VNET paths. -Five scenarios cover processor-state isolation, and the emulator proves less -than their names suggest: - -- `fpneg` proves containment only. A floating-point instruction in the - SERVICE_HSM partition takes the NOCP UsageFault and does not escalate. - M33MU ends the run when it raises NOCP, so partition restart and guest - survival are not shown here; the STM32H563 `fpneg` run checks them. -- `sealneg` and `sealhaltneg` prove only the SPM's software check. In - `sealneg` a partition overwrites its own stack-top seal, that partition alone - faults at its next resume, and the guests keep running. In `sealhaltneg` a - partition's seal is overwritten before its first dispatch (the same check - runs on every dispatch) and the platform halts before that partition runs. - No architectural unstack fault is exercised, on the emulator or by the - STM32H563 `sealneg` run; M33MU models neither the seal nor the function - return integrity check. -- `sealbootneg` damages one main-stack seal word in the reset path; the boot - halts on the production panic before any partition or guest runs. -- `sealpivotneg` issues a partition's blocking wait with the stack pointer - parked on its stack top, so the exception frame lands on the seal words; - that partition alone faults and restarts, and the guests keep running. - -`bandneg1` through `bandneg6` prove the vault, attestation, and crypto -partitions cannot reach each other's data band: - -| Scenario | Prober | Band touched | -| --- | --- | --- | -| `bandneg1` | crypto | vault | -| `bandneg2` | crypto | attestation | -| `bandneg3` | attestation | vault | -| `bandneg4` | attestation | crypto | -| `bandneg5` | vault | attestation | -| `bandneg6` | vault | crypto | - -The prober reads the band, is restarted, writes the band, and is restarted -again. Both accesses must fault on the prober's own stack, and the full -positive lifecycle must still complete. The crypto and attestation probers -also confirm the keystore services they do not own are refused. - -`restartneg1`, `restartneg2`, and `restartneg3` prove a restarted crypto, -attestation, or vault partition starts from its band's link-time image. The -partition changes initialized and zero-initialized state in its own band and -faults; its restarted instance faults again if either value survived. - -`manifestneg` removes a required feature, `manifestneg2` declares isolation -level 2, and `manifestneg3` composes a partition table that reaches another -partition's band. Each must halt the boot before anything is scheduled. - -Each numbered probe variant is its own matrix row; CI packs each family -into one job. - -Three more cover the SPM's own fault handling: - -- `mspovfneg` pushes on the Secure main stack in the reset path until - `MSPLIM_S` raises STKOF. M33MU escalates the entry-time STKOF to HardFault - and ends the run there without executing the handler, so the emulator - proves only the limit; the STM32H563 run reads the SPM fault latch and - checks that no guest ran. -- `xnneg` makes the privileged SVC gate call a thunk copied into SPM `.bss` - while a partition thread domain is installed; the execute-never cover faults - the fetch. M33MU pends that synchronous fault instead of escalating it past - the active SVC, so the emulator proves only the denied fetch; the STM32H563 - run checks the SPM fault latch and the halt. -- `svcneg` has the ITS partition issue the scheduler's internal guest-return - SVC; the partition alone is panicked and restarted, and the lifecycle - completes. - -`busfaultneg` (the SERVICE_HSM partition reads an MPU-permitted window past -the end of physical SRAM) and `nsbusfaultneg` (guest0 turns off its own MPU and -reads an unmapped Non-secure peripheral hole; the monitor restarts it to its -limit while guest1 runs) run only on the STM32H563: M33MU turns an unmapped -data access into a MemManage fault and never vectors a data BusFault, so these -scenarios have no emulator row until the pinned emulator models it. +### Isolation scenarios + +These scenarios back the [level 3 claim](Security-Model.md#ff-m-isolation-level-3). +Each runs on both engines unless marked. + +| Scenario | Proves | +| --- | --- | +| `crossdomain` | A partition read of SPM-private RAM faults. | +| `keystoreneg` | A partition without a keystore band cannot read the vault's band. | +| `bandneg1`-`bandneg6` | Each pair of the vault, attestation, and crypto partitions can neither read nor write the other's band. | +| `restartneg1`-`restartneg3` | A restarted partition starts from its band's link image. | +| `deputyneg` | The privileged flash handler ignores a partition-rewritten flash context. | +| `hsmpinneg` (hsm) | wolfHSM server pointers are pinned before the relay runs. | +| `manifestneg`, `manifestneg2`, `manifestneg3` | Boot refuses a manifest with a missing feature, at level 2, or with a composed table that reaches another band. | +| `periphneg` | Non-secure access to the SPM's RNG and Non-secure DMA out of Secure memory are blocked. | +| `periphspneg` (M33MU) | A partition read of an SPM peripheral faults. | +| `fpneg` | A partition FP instruction faults without escalating. | +| `sealneg`, `sealpivotneg` | A damaged partition stack seal faults only that partition. | +| `sealhaltneg`, `sealbootneg` | A seal damaged before dispatch, or the main-stack seal, halts the platform. | +| `mspovfneg` | An SPM stack overflow hits `MSPLIM_S` and halts. | +| `xnneg` | Privileged execution from SPM RAM is denied. | +| `svcneg` | A partition that issues the guest-return SVC is panicked alone. | +| `busfaultneg`, `nsbusfaultneg` (STM32H563 only) | A partition or guest bus error is contained to its owner. | + +Under M33MU, `fpneg`, `mspovfneg`, and `xnneg` prove the fault only, because +the emulator ends the run there; the STM32H563 runs also check the restart or +halt. M33MU does not model the processor's own stack-seal check or data +BusFaults. VNET has convenience targets: @@ -394,13 +350,6 @@ The optional `gtzcneg` scenario must be selected explicitly. It verifies the GTZC peer-RAM curtain after a Non-secure MPU bypass; it does not establish peer flash confidentiality or adversarial peripheral and Non-secure NVIC ownership. -`periphneg` has a privileged Non-secure guest read and clear the SPM's RNG -through its Non-secure alias and run Non-secure GPDMA copies out of the Secure -image and Secure SRAM. Nothing may be read or changed, and Secure entropy must -still work before the normal lifecycle completes. `periphspneg` (M33MU only) has -the storage partition read the SPM's RNG registers; no partition domain maps a -peripheral the port does not assign, so the read must MemManage-fault. - For the hardened guest-flash configuration, explicitly forward the build flag into the container and repeat it for the host flash run: diff --git a/docs/Threat-Model.md b/docs/Threat-Model.md index d5ed3192..0432606a 100644 --- a/docs/Threat-Model.md +++ b/docs/Threat-Model.md @@ -78,6 +78,7 @@ images. | Caller-ID spoofing | The gateway derives identity from the monitor's active guest; service payloads cannot override it. | | Pointer substitution or overflow | The vector descriptor is copied once; counts, arithmetic, CMSE attributes, and active-guest windows are checked before copying bytes. | | Stale or stolen handles | Handle ownership, type, generation, and state transitions are checked by the SPM. | +| Cross-partition or partition-to-SPM access | FF-M isolation level 3; see [Security Model](Security-Model.md#ff-m-isolation-level-3). | | 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 SPM-stamped identity selects the native vault sub-owner; the hsm relay maps guest `N` to forced wolfHSM client ID `N + 1`. | @@ -112,13 +113,6 @@ claim resistance to a physical adversary that can restore a mutually consistent historical snapshot of the entire NVM pool. A target requiring that property needs rollback-resistant monotonic storage in its port. -### Secure code is shared - -Secure Partition writable state is narrowed by the Secure MPU to one private -band per partition, but all service threads execute shared read/execute text -from one linked image. A defect in trusted shared code can therefore affect -more than one service. - ### Privileged handlers remain security-critical Unprivileged service threads request flash, entropy, locking, and reset through @@ -132,17 +126,15 @@ cause its own quarantine. Shared CPU, flash, and service queues still create availability coupling. Recovery limits stop infinite restart loops but may leave a guest or service unavailable. -### Non-secure peripheral and interrupt attribution is inherited - -The STM32H563 reference port exposes the `0x40000000-0x4FFFFFFF` peripheral -aperture as Non-secure and explicitly leaves USART2 and USART3 Non-secure. It -does not program RCC security attribution. A privileged Non-secure guest can -reprogram `MPU_NS`, alter Non-secure NVIC state, and access any peripheral left -Non-secure by the boot chain. Manifest resource lists therefore do not enforce -adversarial peripheral or interrupt ownership in the current port. Deployments -must configure and validate device-specific peripheral security and privilege -attribution before claiming those boundaries. In particular, access to shared -clock controls can disrupt Secure execution or deny service to peer guests. +### Non-secure peripherals are shared between guests + +The STM32H563 port makes HASH, RNG, and PKA Secure-only and leaves the rest of +the `0x40000000-0x4FFFFFFF` aperture, including USART2, USART3, and RCC, +Non-secure. A privileged guest can reach any of those peripherals and its own +Non-secure NVIC state; the port does not assign peripherals or interrupts +between guests. A deployment that needs that separation must attribute the +device's peripherals itself. Shared clock controls in particular can deny +service to the Secure side or to the other guest. ### Development lifecycle permits recovery From 1361e298a9692e8a32db9a78a0798ee98984fa5d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 12:03:47 -0700 Subject: [PATCH 2/9] Prepare wolfTrust documentation for public release --- .github/workflows/README.md | 5 + .github/workflows/docs-site.yml | 75 +++++++++ .github/workflows/publish-docs-image.yml | 42 +++++ .github/workflows/wiki-sync.yml | 26 ---- .gitmodules | 6 +- DOCS-BUILD.md | 72 +++++++++ README.md | 55 +++---- docker/docs/Dockerfile | 15 ++ docker/docs/requirements.txt | 1 + docs/API-Reference.md | 16 +- docs/Architecture.md | 7 +- docs/Building.md | 4 +- docs/Crypto-Engines.md | 10 +- docs/FF-M-Compatibility.md | 70 +++++++++ docs/Footprint-Comparison.md | 113 ++++++++++++++ docs/Getting-Started.md | 6 +- docs/Macros.md | 2 +- docs/PSA-Compatibility.md | 100 ++++++++++++ docs/Porting.md | 16 +- docs/Project-Structure.md | 4 +- docs/Security-Model.md | 20 +-- docs/Services.md | 12 +- docs/Standards.md | 33 ++++ docs/TF-M-Compatibility.md | 190 ----------------------- docs/Targets.md | 16 ++ docs/_Sidebar.md | 19 --- docs/assets/architecture.config.json | 5 + docs/assets/architecture.mmd | 22 +++ docs/assets/architecture.png | Bin 0 -> 72561 bytes docs/assets/logo.png | Bin 0 -> 12440 bytes docs/assets/skin.css | 25 +++ docs/{Home.md => index.md} | 79 ++-------- mkdocs.yml | 62 ++++++++ tools/check-docs-no-internal-links.sh | 2 +- tools/docs-manual/Makefile | 15 ++ tools/docs-manual/documentation-rev | 1 + tools/docs_manual.py | 173 +++++++++++++++++++++ 37 files changed, 940 insertions(+), 379 deletions(-) create mode 100644 .github/workflows/docs-site.yml create mode 100644 .github/workflows/publish-docs-image.yml delete mode 100644 .github/workflows/wiki-sync.yml create mode 100644 DOCS-BUILD.md create mode 100644 docker/docs/Dockerfile create mode 100644 docker/docs/requirements.txt create mode 100644 docs/FF-M-Compatibility.md create mode 100644 docs/Footprint-Comparison.md create mode 100644 docs/PSA-Compatibility.md create mode 100644 docs/Standards.md delete mode 100644 docs/TF-M-Compatibility.md create mode 100644 docs/Targets.md delete mode 100644 docs/_Sidebar.md create mode 100644 docs/assets/architecture.config.json create mode 100644 docs/assets/architecture.mmd create mode 100644 docs/assets/architecture.png create mode 100644 docs/assets/logo.png create mode 100644 docs/assets/skin.css rename docs/{Home.md => index.md} (60%) create mode 100644 mkdocs.yml create mode 100644 tools/docs-manual/Makefile create mode 100644 tools/docs-manual/documentation-rev create mode 100644 tools/docs_manual.py diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 368cdf15..ad51c6d3 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -16,6 +16,11 @@ and rejects an invalid capture. Keep submodules under `lib/` and enable An accepted upload queues analysis; results appear after Coverity processes it. +`docs-site.yml` builds the HTML and PDF manual with the shared +`wolfSSL/documentation` tooling for documentation pull requests and merges to +`main`. The website's existing documentation update picks up merged changes +through the documentation repository. See [DOCS-BUILD.md](../../DOCS-BUILD.md). + ## At a glance | Tier | Trigger | Purpose | diff --git a/.github/workflows/docs-site.yml b/.github/workflows/docs-site.yml new file mode 100644 index 00000000..9a51f564 --- /dev/null +++ b/.github/workflows/docs-site.yml @@ -0,0 +1,75 @@ +name: Build manual with documentation tooling + +on: + pull_request: + paths: + - 'docs/**' + - 'DOCS-BUILD.md' + - 'mkdocs.yml' + - 'tools/docs_manual.py' + - 'tools/docs-manual/**' + - 'docker/docs/**' + - '.github/workflows/docs-site.yml' + push: + branches: [main] + paths: + - 'docs/**' + - 'DOCS-BUILD.md' + - 'mkdocs.yml' + - 'tools/docs_manual.py' + - 'tools/docs-manual/**' + - 'docker/docs/**' + - '.github/workflows/docs-site.yml' + workflow_dispatch: + +permissions: + contents: read + packages: read + +jobs: + build: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Select documentation revision + id: documentation + run: echo "sha=$(cat tools/docs-manual/documentation-rev)" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v4 + with: + repository: wolfSSL/documentation + ref: ${{ steps.documentation.outputs.sha }} + path: build/documentation + submodules: recursive + persist-credentials: false + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Pull documentation builder + run: | + set -euo pipefail + image_sha=$(sha256sum docker/docs/Dockerfile docker/docs/requirements.txt | sha256sum | cut -c1-16) + image="ghcr.io/wolfssl/wolftrust-docs:sha-$image_sha" + if ! docker pull "$image"; then + docker build --pull -t "$image" docker/docs + fi + echo "DOCS_IMAGE=$image" >> "$GITHUB_ENV" + - name: Build and validate HTML and PDF + run: | + tools/check-docs-no-internal-links.sh + docker run --rm --user "$(id -u):$(id -g)" --env HOME=/tmp \ + --mount "type=bind,source=$GITHUB_WORKSPACE,target=/work/wolfTrust" \ + --workdir /work/wolfTrust "$DOCS_IMAGE" \ + sh -c 'python3 tools/docs_manual.py build --documentation-root /work/wolfTrust/build/documentation --source-root /work/wolfTrust --target all && cd build/documentation/wolfTrust && mkdocs build --strict -f mkdocs.yml' + test -s build/documentation/wolfTrust/html/index.html + test -s build/documentation/wolfTrust/wolfTrust-Manual.pdf + - uses: actions/upload-artifact@v4 + with: + name: wolftrust-manual + path: | + build/documentation/wolfTrust/html/ + build/documentation/wolfTrust/wolfTrust-Manual.pdf + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/publish-docs-image.yml b/.github/workflows/publish-docs-image.yml new file mode 100644 index 00000000..cc16abb2 --- /dev/null +++ b/.github/workflows/publish-docs-image.yml @@ -0,0 +1,42 @@ +name: Publish documentation image + +on: + push: + branches: [main] + paths: + - 'docker/docs/**' + - '.github/workflows/publish-docs-image.yml' + workflow_dispatch: + +permissions: + contents: read + packages: write + +concurrency: + group: publish-wolftrust-docs-image + cancel-in-progress: false + +jobs: + publish: + if: github.repository == 'wolfSSL/wolfTrust' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Select image tag + id: image + run: | + image_sha=$(sha256sum docker/docs/Dockerfile docker/docs/requirements.txt | sha256sum | cut -c1-16) + echo "tag=ghcr.io/wolfssl/wolftrust-docs:sha-$image_sha" >> "$GITHUB_OUTPUT" + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v6 + with: + context: docker/docs + file: docker/docs/Dockerfile + push: true + tags: ${{ steps.image.outputs.tag }} diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml deleted file mode 100644 index 9938adbc..00000000 --- a/.github/workflows/wiki-sync.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Sync docs to wiki - -# The wiki is a generated mirror of docs/. Edit docs/ via pull requests; -# this publishes them to the GitHub wiki on every merge to main. -on: - push: - branches: [main] - paths: ['docs/**'] - workflow_dispatch: - -permissions: - contents: write - -concurrency: - group: wiki-sync - cancel-in-progress: true - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Publish docs/ to wiki - uses: Andrew-Chen-Wang/github-wiki-action@50650fccf3a10f741995523cf9708c53cec8912a # v4 - with: - path: docs/ diff --git a/.gitmodules b/.gitmodules index 1ac32c54..9423c6ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,16 +3,16 @@ url = https://github.com/wolfSSL/wolfHSM.git [submodule "lib/wolfSSL"] path = lib/wolfSSL - url = git@github.com:wolfSSL/wolfSSL.git + url = https://github.com/wolfSSL/wolfSSL.git [submodule "lib/wolfhal"] path = lib/wolfhal - url = git@github.com:wolfSSL/wolfhal.git + url = https://github.com/wolfSSL/wolfhal.git [submodule "lib/wolfPSA"] path = lib/wolfPSA url = https://github.com/wolfSSL/wolfPSA.git [submodule "lib/wolfIP"] path = lib/wolfIP - url = git@github.com:wolfSSL/wolfip.git + url = https://github.com/wolfSSL/wolfip.git [submodule "lib/wolfCOSE"] path = lib/wolfCOSE url = https://github.com/wolfSSL/wolfCOSE.git diff --git a/DOCS-BUILD.md b/DOCS-BUILD.md new file mode 100644 index 00000000..e514de5d --- /dev/null +++ b/DOCS-BUILD.md @@ -0,0 +1,72 @@ +# Building and publishing the wolfTrust manual + +`docs/` is the manual source. `mkdocs.yml` defines the page order and +navigation. The adapter in `tools/docs_manual.py` passes those pages to the +shared MkDocs theme and Pandoc/LaTeX PDF rules in +[`wolfSSL/documentation`](https://github.com/wolfSSL/documentation). Keep manual +content and its navigation in wolfTrust; generated files stay under `build/`. + +## Local build + +Clone the documentation build tools at the revision used by wolfTrust CI: + +```sh +git clone --recurse-submodules https://github.com/wolfSSL/documentation.git build/documentation +git -C build/documentation checkout "$(cat tools/docs-manual/documentation-rev)" +docker build --pull -t wolftrust-docs:local docker/docs +docker run --rm --user "$(id -u):$(id -g)" --env HOME=/tmp \ + --mount "type=bind,source=$PWD,target=/work/wolfTrust" \ + --workdir /work/wolfTrust wolftrust-docs:local \ + python3 tools/docs_manual.py build \ + --documentation-root /work/wolfTrust/build/documentation \ + --source-root /work/wolfTrust --target all +``` + +The outputs are `build/documentation/wolfTrust/html/` and +`build/documentation/wolfTrust/wolfTrust-Manual.pdf`. The container includes +MkDocs, Pandoc, LaTeX, and the fonts used by the shared build rules. On a +wolfTrust pull request, `docs-site.yml` checks out the pinned documentation +revision, builds both outputs, runs MkDocs in strict mode, and uploads them as +one artifact. It also runs after documentation changes merge to `main`. +CI pulls the versioned builder image from GHCR, or builds it from +`docker/docs/` if that image is unavailable. No host package installation or +website credentials are needed for this check. + +For an HTML preview, first run the build above, then run: + +```sh +docker run --rm --user "$(id -u):$(id -g)" --env HOME=/tmp -p 8000:8000 \ + --mount "type=bind,source=$PWD,target=/work/wolfTrust" \ + --workdir /work/wolfTrust/build/documentation/wolfTrust \ + wolftrust-docs:local mkdocs serve -a 0.0.0.0:8000 -f mkdocs.yml +``` + +The architecture diagram is maintained in `docs/assets/architecture.mmd`. +Regenerate its static image after editing it so HTML and PDF show the same +labels: + +```sh +npx --yes @mermaid-js/mermaid-cli@12.0.0 \ + -i docs/assets/architecture.mmd \ + -o docs/assets/architecture.png \ + -b white -c docs/assets/architecture.config.json -s 2 +``` + +## Website update + +The one-time integration in `wolfSSL/documentation` adds a `wolfTrust` manual +target beside wolfBoot and wolfHSM. It clones `wolfSSL/wolfTrust` **main**, runs +the adapter from that checkout, and exports `wolfTrust-html/` and +`wolfTrust-Manual.pdf` through the documentation repository's existing Docker +build. Merge the wolfTrust adapter before that integration so its source is +available on `main`. + +After both changes are merged, the existing nightly documentation build should +pick up `wolftrust` through the documentation repository's `all` target. No +manual trigger is needed for the normal update. Check the first nightly result +at `https://www.wolfssl.com/documentation/manuals/wolftrust/` and its +`wolfTrust-Manual.pdf`. The website upload rules are outside these public +repositories; if the nightly publishes an explicit list of manuals instead of +all build output, that list will need a one-time addition. Subsequent merged +wolfTrust documentation changes are read from wolfTrust `main` on the next +nightly run. Pull requests only build review artifacts. diff --git a/README.md b/README.md index 1c0699da..bb075c3b 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,9 @@ common runtime provides interprocess communication (IPC) through the Arm Platform Security Architecture (PSA) Firmware Framework for M (FF-M), manifest policy, lifecycle management, scheduling, fault recovery, and PSA services. -The only currently supported and validated reference implementation combines -the Armv8-M adapter with the STM32H563 Cortex-M33 port. Support for additional -Cortex-M ports is an intended extension point. Such ports may reuse the common -runtime and, where applicable, the Armv8-M layer. Cortex-A support is an -architectural goal, not a current capability. It will require a new adapter and -changes to current internal execution and protection contracts; the design goal -is to preserve the public manifest, service, IPC, and PSA API contracts. +The Armv8-M adapter has two target ports: STM32H563 and NXP MIMXRT700. They +share the core runtime and architecture layer, with different device security +controls and validation coverage. See [Ports and supported targets](docs/Targets.md). ## Architecture @@ -29,7 +25,7 @@ flowchart TB subgraph PORT[Architecture and target ports] GW[Client gateway
current: five Armv8-M CMSE veneers] ARCH[Architecture adapter
current: Armv8-M] - TARGET[Target and board port
current: STM32H563] + TARGET[Target and board port
STM32H563 or MIMXRT700] GW --- ARCH ARCH --- TARGET end @@ -37,7 +33,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["Secure crypto and optional
wolfHSM server"] ST["Internal Trusted Storage (ITS),
Protected Storage, and vault"] AT[Initial Attestation] FW[Firmware Update] @@ -52,7 +48,7 @@ flowchart TB subgraph LIBS[wolfSSL ecosystem components] PSA[wolfPSA
guest wolfCrypt and wolfHSM client] - WC[Secure wolfCrypt and wolfHSM] + WC[Secure wolfCrypt
optional wolfHSM server] COSE[wolfCOSE] HAL[wolfHAL] IP[wolfIP
optional bare-metal reference networking] @@ -64,8 +60,8 @@ flowchart TB GA -->|PSA Crypto| PSA GB -->|PSA Crypto| PSA PSA -->|protected operations over FF-M| GW - GA -->|optional networking| IP - GB -->|optional networking| IP + GA -->|bare-metal VNET guest only| IP + GB -->|bare-metal VNET guest only| IP IP -->|VNet service over FF-M| GW GW -->|validated requests| SPM SPM -->|Secure execution operations| ARCH @@ -77,6 +73,9 @@ flowchart TB TARGET -->|current register and RNG access| HAL ``` +The optional wolfIP/VNET reference uses separate bare-metal guests, not the +Zephyr and FreeRTOS PSA guest pair. + ### Current reference port On the STM32H563 reference chain, wolfBoot authenticates wolfTrust, Armv8-M @@ -95,25 +94,22 @@ first-stage loader contract, and the runners: - STM32H563 (NUCLEO-H563ZI): [STM32H5 Guide](docs/STM32H5-Guide.md) - NXP MIMXRT700 (MIMXRT700-EVK): [MIMXRT700 Guide](docs/MIMXRT700-Guide.md) -Both build with `TARGET=` (`stm32h563` is the default) and run the -same scenario set under the M33MU emulator with `make test-target -TARGET=`. +Both build with `TARGET=` (`stm32h563` is the default). Each runs its +own smoke scenario set under M33MU with `make test-target TARGET=`. ## Quick start For the initial Secure build and host tests, install GNU Make, Python 3, Git, a -native C compiler, and an `arm-none-eabi-` toolchain. Configure GitHub SSH -access before cloning because three configured submodule URLs use SSH. Guest -setup, conformance tests, emulator runs, and fresh hardware builds require -network access. See [Getting Started](docs/Getting-Started.md) for additional -emulator and hardware prerequisites. - -The repository currently requires authorized GitHub access. +native C compiler, and an `arm-none-eabi-` toolchain. The repository and its +submodules use public HTTPS URLs; GitHub SSH setup is not required. Guest +builds also need CMake and Ninja; target tests need M33MU or a provisioned +NUCLEO-H563ZI with ST-Link, and the published hardware workflow uses Docker. +These workflows may fetch dependencies on first use. See +[Getting Started](docs/Getting-Started.md) for the full prerequisites. ```sh git clone --recurse-submodules https://github.com/wolfSSL/wolfTrust.git cd wolfTrust -git submodule update --init --recursive make make test ``` @@ -160,12 +156,17 @@ The version-controlled documentation in [`docs/`](docs/) is the primary documentation source: - [Getting Started](docs/Getting-Started.md) +- [Ports and supported targets](docs/Targets.md) +- [MIMXRT700 Guide](docs/MIMXRT700-Guide.md) - [Architecture](docs/Architecture.md) - [Security Model](docs/Security-Model.md) - [Threat Model](docs/Threat-Model.md) - [API Reference](docs/API-Reference.md) - [Services](docs/Services.md) -- [TF-M Compatibility](docs/TF-M-Compatibility.md) +- [Standards and Claims](docs/Standards.md) +- [FF-M Compatibility](docs/FF-M-Compatibility.md) +- [PSA Compatibility](docs/PSA-Compatibility.md) +- [Footprint Comparison](docs/Footprint-Comparison.md) - [Macros](docs/Macros.md) - [Porting](docs/Porting.md) - [Building](docs/Building.md) @@ -182,8 +183,10 @@ The source tree is authoritative: | `port/` | Target policy, memory layout, flash, entropy, and hardware enforcement | | `mk/` | Build configuration and linked-image checks | -When enabled, the GitHub wiki is generated from the Markdown sources in -`docs/`. +`mkdocs.yml` defines the manual navigation. The shared +[`wolfSSL/documentation`](https://github.com/wolfSSL/documentation) tooling +builds HTML and PDF from these pages. [DOCS-BUILD.md](DOCS-BUILD.md) explains +local previews and how merged changes reach the website. ## License diff --git a/docker/docs/Dockerfile b/docker/docs/Dockerfile new file mode 100644 index 00000000..319d8dd7 --- /dev/null +++ b/docker/docs/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim-bookworm + +LABEL org.opencontainers.image.source="https://github.com/wolfSSL/wolfTrust" + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + bash git make patch perl \ + pandoc texlive-xetex texlive-latex-extra texlive-fonts-recommended \ + fonts-noto-core fonts-noto-mono lmodern \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt /tmp/requirements-docs.txt +RUN python -m pip install --no-cache-dir -r /tmp/requirements-docs.txt + +WORKDIR /work diff --git a/docker/docs/requirements.txt b/docker/docs/requirements.txt new file mode 100644 index 00000000..f2caded5 --- /dev/null +++ b/docker/docs/requirements.txt @@ -0,0 +1 @@ +mkdocs==1.6.1 diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 66cda8b0..d664e4b3 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -31,7 +31,7 @@ configuration. | Internal Trusted Storage | `include/psa/internal_trusted_storage.h` | 1.0 | | Protected Storage | `include/psa/protected_storage.h` | 1.0 | | Firmware Update | `include/psa/update.h` | 1.0 | -| Initial Attestation | `lib/wolfPSA/wolfpsa/psa/initial_attestation.h` | 1.0 API operations; see [TF-M Compatibility](TF-M-Compatibility.md) for API and token-profile deviations | +| Initial Attestation | `lib/wolfPSA/wolfpsa/psa/initial_attestation.h` | 1.0 API operations; see [PSA Compatibility](PSA-Compatibility.md) for API and token-profile deviations | | Lifecycle | `include/psa/lifecycle.h` | FF-M 1.0, Secure Partition only | | Gateway vector ABI | `include/wolftrust/ffm_veneer.h` | wolfTrust ABI | @@ -406,10 +406,12 @@ buffer, and its 24-byte request header caps one copied vault response at 1000 bytes. `data_offset` remains useful when the caller supplies an output buffer smaller than the object. UID zero is invalid. -The current ITS and Protected Storage paths always enforce -`PSA_STORAGE_FLAG_WRITE_ONCE`, including during -`PSA_ROT_PROVISIONING`. PSA Secure Storage 1.0 requires that flag not to be -enforced in the provisioning lifecycle, so this is a known lifecycle deviation. +The ITS path enforces `PSA_STORAGE_FLAG_WRITE_ONCE` when the caller sets it, +including during `PSA_ROT_PROVISIONING`. Objects created without the flag can +be updated or removed. PSA Secure Storage 1.0 §3.2 requires ITS not to enforce +the flag in the provisioning lifecycle, so this is a known lifecycle deviation. +Protected Storage also enforces caller-selected `WRITE_ONCE`; §3.2's lifecycle +exception applies to ITS. ## Protected Storage @@ -501,7 +503,7 @@ returns `PSA_ERROR_INVALID_ARGUMENT`; other undersized buffers return `PSA_ERROR_BUFFER_TOO_SMALL`. The token advertises `tag:psacertified.org,2023:psa#tfm` but has the -token-profile deviations listed in [TF-M Compatibility](TF-M-Compatibility.md) and must not be +token-profile deviations listed in [PSA Compatibility](PSA-Compatibility.md) and must not be represented as conformant with that profile. The only working Non-secure attestation adapter currently resides at `tests/firmware/zephyr-stm32h5/module/wolftrust-tee/src/wolftrust_attestation_client.c`. @@ -591,4 +593,4 @@ vector layout is in `include/wolftrust/ffm_veneer.h`, and | `PSA_ERROR_INSUFFICIENT_POWER` | -161 | Power is insufficient for the operation | For service availability and deviations, see [Services](Services.md) and -[TF-M Compatibility](TF-M-Compatibility.md). +[PSA Compatibility](PSA-Compatibility.md). diff --git a/docs/Architecture.md b/docs/Architecture.md index 9cecac26..704ab13f 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -37,11 +37,8 @@ paths, context handling, GTZC attribution, guest and Secure MPU programming, interrupt routing, flash, entropy, timers, and boot handoff. Additional Cortex-M ports may reuse the existing interfaces when their -execution and protection models match. Cortex-A support is an architectural -goal, not a current capability. It will require a new adapter and changes to -current internal execution and protection contracts; the design goal is to -preserve the public manifest, service, IPC, and PSA API contracts. See -[Porting](Porting.md) for the current boundary. +execution and protection models match. See [Porting](Porting.md) for the +current boundary. ## Boot flow diff --git a/docs/Building.md b/docs/Building.md index 65c7dfd2..00198f40 100644 --- a/docs/Building.md +++ b/docs/Building.md @@ -13,8 +13,8 @@ cross-compiles a freestanding Cortex-M33 image. - GNU Arm Embedded tools with the `arm-none-eabi-` prefix - a native C compiler for host tests -Some submodule URLs use GitHub SSH. Configure GitHub SSH access or an -equivalent Git URL rewrite before initializing them. +Submodules use public HTTPS URLs. An existing checkout with older URLs should +run `git submodule sync --recursive` first. ```sh git submodule update --init --recursive diff --git a/docs/Crypto-Engines.md b/docs/Crypto-Engines.md index 931b3834..5e6ec6f2 100644 --- a/docs/Crypto-Engines.md +++ b/docs/Crypto-Engines.md @@ -225,10 +225,10 @@ The Secure image and both guest images must use the same engine. See ## 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 +These Secure-image measurements were reproduced on 2026-09-18. They are a +dated snapshot, and later commits may produce different sizes. The dependency +revisions and versions used for the measurements are listed in +[Footprint Comparison](Footprint-Comparison.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: @@ -275,7 +275,7 @@ 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 +See [Footprint Comparison](Footprint-Comparison.md) for the complete local footprint comparison and methodology. ## Build invariants diff --git a/docs/FF-M-Compatibility.md b/docs/FF-M-Compatibility.md new file mode 100644 index 00000000..ae488ffd --- /dev/null +++ b/docs/FF-M-Compatibility.md @@ -0,0 +1,70 @@ +# FF-M Compatibility + +wolfTrust targets conformance with the [Arm Firmware Framework for M (FF-M) +1.0 specification](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132). +It is an alternative Secure Partition Manager and services runtime for supported +Cortex-M systems, with its own image, manifest, build, and port integration. +The currently supported reference port is STM32H563 Cortex-M33. + +This page records the implemented FF-M 1.0 surface and known differences. It is +not a full conformance statement. The [PSA Compatibility](PSA-Compatibility.md) +page covers Crypto, Storage, Attestation, and Firmware Update APIs separately. + +## Arm specification sections + +| Contract | Normative section | wolfTrust scope | +| --- | --- | --- | +| Isolation and protection domains | [FF-M §§3.1.1–3.1.6](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=21) | STM32H563 TrustZone, Secure MPU, and Global TrustZone Controller (GTZC) enforcement, meeting isolation level 3; see [Security Model](Security-Model.md#ff-m-isolation-level-3) | +| Secure Partition identity, manifest, and execution | [FF-M §§3.2.1–3.2.4](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=26) and [§4.1](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=49) | Generated wolfTrust manifest policy, scheduled Secure Partition entry, and lifecycle handling | +| IPC, handles, and copied vectors | [FF-M §§3.3.1–3.3.5](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=31) | Connection-based services with fixed buffer and vector limits | +| Client and Secure Partition APIs | [FF-M §4.4](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=64) and [§4.5](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=70) | Implemented functions and limitations are listed in the register below | + +## Implemented framework interfaces + +| 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](https://developer.arm.com/documentation/aes0039/latest) | Supported for scheduled IPC partitions. The public client contract reports at most `0x0100`. The manifest validator accepts 1.1 partition metadata, but the current runtime rejects 1.1 partitions during activation. | `include/psa/service.h`, `include/psa/client.h`, `src/arch/common/spm_sp_api.c`, `src/manifest.c`, and `src/ffm.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` | +| Root of Trust (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](https://developer.arm.com/documentation/aes0039/latest), 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 supervisor-call (SVC) implementation | +| Guest identity | FF-M convention | Non-secure guest `N` is client `-(N + 1)` | `src/arch/armv8m/ffm_nsc.c` | + +## Framework differences + +| Difference | Classification | Reason and impact | +| --- | --- | --- | +| wolfTrust exposes the FF-M Non-secure client API through one five-function Cortex-M Security Extensions (CMSE) gateway. | Implementation detail | The gateway exports framework version, service version, connect, call, and close; Secure Partition entry points are a separate manifest concern. | +| Only connection-based IPC services are enabled in the reference build. | Scoped | The STM32H563 manifest requests only `WT_MANIFEST_FEATURE_IPC`. It does not enable stateless-function (SFN) partitions, stateless services, or memory-mapped I/O vectors. The common manifest validator recognizes their 1.1 feature bits, but the runtime activates only IPC partitions with a framework version no newer than `0x0100`. | +| IPC uses fixed copied buffers. | Scoped safety bound | Calls are limited to four vectors and a 1024-byte transfer budget. Services apply smaller bounds. Applications must chunk larger data. | +| A Non-secure connect, call, or close cannot remain pending after the scheduler reaches quiescence for that dispatch. | Scoped deviation | An incomplete message becomes a client error. If already claimed, the service retains its message until reply or partition fault; late output is discarded. A late call reply releases the message, but the connection remains in error until the client requests close. If close was already requested, the late reply allows deferred disconnection to proceed. Shipped Non-secure-facing services reply within the dispatch; Secure Partition callers use the begin/finish path when scheduling another partition is required. | +| Secure services are linked in one image. | Scoped isolation difference | Service writable state is isolated by unprivileged threads and Secure MPU domains, but executable text is shared rather than separately linked. | +| All shipped service loops run as scheduled coroutines. | Implementation difference | Service code uses the standard Secure Partition API; privileged hardware access goes through identity-pinned SVC gates. | +| 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. | +| Secure memory uses no dynamic allocation. | Stronger resource policy | Fixed pools and buffers can reject excess work rather than expanding at runtime. | +| Manifests use wolfTrust JSON and generated C. | Integration difference | Security resources and services must be represented in the wolfTrust schema. | +| Secure Partition entry functions are bound at build time instead of being selected by each manifest's `entry_point` field. | Integration difference | The numeric field validates an executable window, but adding a service also requires a compiled entry wrapper and an explicit start call in `wt_ffm_boot_start_sched()`. | +| Service IDs are generated from the selected manifest. | Integration difference | Applications should include generated `psa_manifest/sid.h` instead of hard-coding target-specific values. | + +## Isolation level + +wolfTrust implements FF-M isolation level 3 only, and refuses a manifest that +declares level 1 or 2 or a privileged Secure Partition. The rule-by-rule +mapping and the deviations are in +[Security Model](Security-Model.md#ff-m-isolation-level-3). + +## Validation and claim boundary + +`make test` exercises native framework policy, IPC, and negative inputs. +`make test-conformance` runs the pinned PSA architecture tests: with M33MU it +exercises target FF-M IPC; without M33MU it runs only a host subset. The +STM32H563 hardware runner tests the reference boot chain and selected behavior +on a provisioned board. See [Testing](Testing.md) for commands and the limits +of each environment. The differences above prevent a full FF-M conformance +claim today. + +See [API Reference](API-Reference.md) for the client and Secure Partition APIs, +[PSA Compatibility](PSA-Compatibility.md) for service APIs, and +[Footprint Comparison](Footprint-Comparison.md) for dated size measurements. diff --git a/docs/Footprint-Comparison.md b/docs/Footprint-Comparison.md new file mode 100644 index 00000000..5dc69b0a --- /dev/null +++ b/docs/Footprint-Comparison.md @@ -0,0 +1,113 @@ +# Historical Secure-image Footprint Comparison + +This is a dated local comparison, not a standards-compatibility claim. +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 CBOR Object Signing and +Encryption (COSE) attestation. The TF-M rows are the standard Small, Medium, +and Large profiles built for AN521. The table +lists selected services, not every service in those profiles. + +| Secure image | Profile and selected 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`. The wolfTrust builds +used `-Os` without link-time optimization (LTO); the cited historical commit +predates the later `WT_LTO=1` default. The TF-M `MinSizeRel` builds also did +not use LTO. Footprint was read from the linked Secure Executable and Linkable +Format (ELF) with `arm-none-eabi-size` and calculated as: + +```text +Flash = text + data +Static RAM = data + bss +``` + +Reproduction requires Git, GNU Make, CMake, Python 3, the GNU Arm toolchain, +and network access for source checkouts. The commands use POSIX shell syntax; +see the [TF-M build instructions](https://tf-m.docs.trustedfirmware.org/en/latest/building/tfm_build_instruction.html) +for its remaining host prerequisites. + +The wolfTrust rows were measured on 2026-09-18 from wolfTrust commit +`c8baa2681b8b1e2ec57e5720cba28519bb306ab3`, which records the dependency +revisions below. Later commits may produce different sizes. + +| 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 | + +To reproduce the wolfTrust snapshot, use a separate checkout at that commit. +Its historical `.gitmodules` has SSH URLs for three submodules; the one-time +Git URL rewrite below fetches them over HTTPS. All other build variables +retain their repository defaults: + +```sh +git clone https://github.com/wolfSSL/wolfTrust.git wolftrust-footprint +cd wolftrust-footprint +git checkout c8baa2681b8b1e2ec57e5720cba28519bb306ab3 +git -c 'url.https://github.com/.insteadOf=git@github.com:' \ + 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`. In a separate directory, +clone and check out that revision; run the remaining commands from its source +root. These AN521 GNU Arm builds enable BL2 and no regression tests: + +```sh +git clone https://github.com/TrustedFirmware-M/trusted-firmware-m.git tf-m +cd tf-m +git checkout 02bf279913439a07082dd581df033f370a8fbb92 +git submodule update --init --recursive +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 historical local +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. diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index d9521a5e..4ccf02dc 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -34,9 +34,13 @@ access. ```sh git clone --recurse-submodules https://github.com/wolfSSL/wolfTrust.git cd wolfTrust -git submodule update --init --recursive ``` +The repository and its six pinned submodules are public and use HTTPS URLs. +No GitHub account or SSH key is needed to clone them. If you already have a +checkout with older submodule URLs, run `git submodule sync --recursive` before +`git submodule update --init --recursive`. + ## Build the Secure image ```sh diff --git a/docs/Macros.md b/docs/Macros.md index 9a8eeb6f..5e57b286 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -13,7 +13,7 @@ selected values into C preprocessor defines. Defaults below come from | `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_LTO` | Enable Secure-image link-time optimization; default `1`. | Set to `0` for diagnostics or a non-LTO size comparison. The GNU Arm compiler must support `-flto=auto`. | -| `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). | +| `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 same FF-M surface (5 veneers, SIDs, manifest, and isolation policy) 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 diff --git a/docs/PSA-Compatibility.md b/docs/PSA-Compatibility.md new file mode 100644 index 00000000..cdf86fbe --- /dev/null +++ b/docs/PSA-Compatibility.md @@ -0,0 +1,100 @@ +# PSA Compatibility + +wolfTrust exposes selected [PSA APIs](https://arm-software.github.io/psa-api/) +through its STM32H563 reference applications and Secure services. This page +records the implemented Crypto, Secure Storage, Initial Attestation, and +Firmware Update surface against the cited Arm specifications. Algorithms and +features depend on the selected build. The known deviations below prevent a +claim of full PSA API conformance or certification. + +The Secure Partition Manager and IPC contract are documented separately in +[FF-M Compatibility](FF-M-Compatibility.md). + +## PSA specification sections + +| Contract | Normative section | wolfTrust scope | +| --- | --- | --- | +| PSA Crypto | [Crypto API 1.4, §6.1.1](https://arm-software.github.io/psa-api/crypto/1.4/overview/implementation.html) | wolfPSA API; enabled algorithms depend on the selected guest and crypto-engine configuration | +| Internal Trusted Storage and Protected Storage | [Secure Storage API 1.0, §§5.3–5.4](https://arm-software.github.io/psa-api/storage/1.0/api/api.html) | Core operations; storage deviations are listed below | +| Initial Attestation | [Attestation API 1.0, §3](https://arm-software.github.io/psa-api/attestation/1.0/overview/report.html) and [§4](https://arm-software.github.io/psa-api/attestation/1.0/api/api.html) | Token generation and token-size query operations with documented header, status, and token-profile deviations | +| Firmware Update | [Firmware Update API 1.0, §4](https://arm-software.github.io/psa-api/fwu/1.0/overview/programming-model.html) and [§5](https://arm-software.github.io/psa-api/fwu/1.0/api/api.html) | Single-component staging and authenticated reboot with documented limits | + +## Implemented service APIs + +| API or behavior | Version | Status | Repository evidence | +| --- | --- | --- | --- | +| PSA Crypto | 1.4 header | Supported through wolfPSA; algorithms depend on the guest profile | `lib/wolfPSA/wolfpsa/psa/crypto.h` and both reference guest settings | +| Internal Trusted Storage | 1.0 | Core set/get/get-info/remove subset; ordinary objects can be updated, while caller-selected `WRITE_ONCE` is enforced without a provisioning exception | `include/psa/internal_trusted_storage.h` and `src/services/storage_service.c` | +| Protected Storage | 1.0 | Core set/get/get-info/remove subset; optional create/set-extended absent | `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 generation and token-size query 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 status deviation below | `include/psa/update.h` and `src/services/fwu_service.c` | + +## Service API differences + +| Difference | Classification | Reason and impact | +| --- | --- | --- | +| PSA Crypto mechanisms are build-selected. | Implementation-profile behavior | The [PSA Crypto implementation profile](https://arm-software.github.io/psa-api/crypto/1.4/overview/implementation.html) may select an API and algorithm subset. 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 requirement. | +| Internal Trusted Storage enforces caller-selected `PSA_STORAGE_FLAG_WRITE_ONCE` during provisioning. | Known lifecycle deviation | Ordinary objects can be updated or removed. The ITS request path does not consult lifecycle state, so an object created with the flag cannot be changed during `PSA_ROT_PROVISIONING`, contrary to [PSA Secure Storage 1.0 §3.2](https://arm-software.github.io/psa-api/storage/1.0/overview/requirements.html). The §3.2 lifecycle exception is specific to ITS. | +| 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`. | +| The attestation token advertises `tag:psacertified.org,2023:psa#tfm` but does not implement that profile's claim semantics. | Known token-profile deviation | The boot seed is deterministic across equivalent boots; software-component measurement type and description values are reversed; signer ID hashes the literal name `wolfBoot` rather than identifying the signing key; and implementation ID hashes a software label rather than identifying the immutable PSA RoT hardware assembly. A distinct derived profile identifier should be used until these claims conform to [RFC 9783 §5.2](https://www.rfc-editor.org/rfc/rfc9783.html#section-5.2). | +| Firmware Update has no persistent trial-accept flow. | Scoped | Installation commits only after wolfBoot authenticates the swapped image at reboot; `psa_fwu_accept()` returns `PSA_ERROR_NOT_SUPPORTED`. | +| wolfTrust's Firmware Update adapter accepts a detached manifest only as a 32-bit version word. | Scoped integration | Passing `NULL, 0` instead binds the version from the staged wolfBoot header. Other manifest encodings require an adapter. | +| Firmware Update reports unknown component IDs as `PSA_ERROR_INVALID_ARGUMENT`. | Known API deviation | PSA Firmware Update 1.0 specifies `PSA_ERROR_DOES_NOT_EXIST` for unknown component IDs. Unaligned block sizes are padded to the backend write alignment. | + +The attestation comparison uses the claim definitions for +[Implementation ID](https://www.rfc-editor.org/rfc/rfc9783.html#section-4.2.2), +[Boot Seed](https://www.rfc-editor.org/rfc/rfc9783.html#section-4.3.2), and +[Software Components](https://www.rfc-editor.org/rfc/rfc9783.html#section-4.4.1) +in RFC 9783. Its [derived-profile rules](https://www.rfc-editor.org/rfc/rfc9783.html#section-4.5.2.1) +describe how to identify a different token profile. + +## Validation and claim boundary + +`make test` exercises native service behavior and negative inputs. Dedicated +guest scenarios under M33MU exercise selected PSA Crypto, Secure Storage, and +Initial Attestation APIs. The STM32H563 hardware runner checks selected +end-to-end behavior on a provisioned board. See [Testing](Testing.md) for the +commands and the scope of each result. These tests do not eliminate the API +and token-profile deviations listed above. + +## Porting an existing PSA application + +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. `psa_rot_lifecycle_state()` is available only + to scheduled Secure Partitions; there is no Non-secure client adapter for it. +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 wolfHSM 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. +3. Initial Attestation currently has only the Zephyr-specific adapter at + `tests/firmware/zephyr-stm32h5/module/wolftrust-tee/src/wolftrust_attestation_client.c`. + The vendored `lib/wolfPSA/src/psa_attestation.c` is a stub that returns + `PSA_ERROR_NOT_SUPPORTED` and must not be linked instead of or alongside that + adapter. +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`. + For wolfTrust, the image offset must be aligned to + `1 << PSA_FWU_LOG2_WRITE_ALIGN`. An unaligned final block is padded by the + service to the backend write alignment. +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, + guest launch requirements, and minimum versions in the wolfTrust manifest. +8. Use the wolfBoot and wolfTrust image assembly flow. Patch guest measurement + records before signing wolfTrust. +9. Run host tests, target conformance under M33MU, and the hardware suite + separately. An emulator pass does not establish silicon attribution. + +See [API Reference](API-Reference.md), [FF-M Compatibility](FF-M-Compatibility.md), +[Porting](Porting.md), and [Testing](Testing.md) for integration details. diff --git a/docs/Porting.md b/docs/Porting.md index 789fe0a4..f7c3cc79 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -10,11 +10,8 @@ additional Cortex-M ports is an intended extension point. Such ports may reuse common policy and service code and an existing architecture adapter when their execution and protection models match. -Cortex-A support is an architectural goal, not a current capability. It will -require a new adapter and changes to current internal execution and protection -contracts. The design goal is to preserve the public manifest, service, IPC, -and PSA API contracts. Every new port must report its actual capabilities and -must not claim security properties until they are tested on that target. +Every new port must report its actual capabilities and must not claim security +properties until they are tested on that target. ## Port layers @@ -59,8 +56,7 @@ operations together with the SoC's SAU and MPU region tables. A port declares what its hardware can do through the capability bits in `include/wolftrust/partition.h`; the core refuses a manifest that assumes a -capability the port does not provide, so an A-profile port that has no -Non-secure MPU says so instead of faking it. +capability the port does not provide. ## MCU and board contract @@ -201,14 +197,14 @@ worked examples above give a concrete map for each board. - Run `make test` for common policy and service behavior. - Run `WT_SPLIT_STRICT=1 tools/check-core-port-split.sh` and resolve hard core-to-architecture leaks (arch or port headers, CMSE, inline - assembly, retired names, M-profile or A-profile register vocabulary in + assembly, retired names, architecture-specific register vocabulary in core code, and `wt_arch_*` definitions inside a port). - Run `tools/check-port-only-diff.sh ` on a port change and confirm it touches nothing outside `src/arch/common/`, `src/arch//`, `include/wolftrust/arch//`, `port//`, the two build fragments, tests, docs, and workflows. -- Run `tools/check-docs-no-internal-links.sh`; `docs/` is published to the - wiki and must not reference internal ledgers or developer paths. +- Run `tools/check-docs-no-internal-links.sh`; `docs/` is published as the + manual and must not reference internal ledgers or developer paths. - Cross-build the Secure image with warnings enabled. - On the current Armv8-M port, inspect `nm` output and confirm only the five FF-M veneers are Non-secure-callable. diff --git a/docs/Project-Structure.md b/docs/Project-Structure.md index dd149382..bd6f5d60 100644 --- a/docs/Project-Structure.md +++ b/docs/Project-Structure.md @@ -3,7 +3,7 @@ | Path | Contents | | --- | --- | | `README.md` | Repository overview and quick start | -| `docs/` | Source pages published to the GitHub wiki | +| `docs/` | Source pages for the HTML and PDF manual | | `include/psa/` | FF-M client/service, status, storage, update, and lifecycle headers implemented by wolfTrust | | `include/wolftrust/` | Domain, manifest, monitor, port, scheduler, IPC, service, and VNET contracts | | `src/` | Architecture-neutral boot sequence, monitor, FF-M runtime, domains, manifests, verification, rollback, recovery, and the Secure Partition entry bodies | @@ -24,7 +24,7 @@ | `tests/firmware/` | Bare-metal, Zephyr, FreeRTOS, conformance, and VNET guest images | | `tests/upstream/` | Fetch and integration helpers for pinned external validation suites | | `lib/` | Git submodules for wolfSSL, wolfPSA, wolfHSM, wolfCOSE, wolfHAL, and wolfIP | -| `.github/workflows/` | Build, test, dependency, fuzz, and wiki synchronization workflows | +| `.github/workflows/` | Build, test, dependency, fuzz, and manual publishing workflows | Generated files belong under `build/`, guest build directories, ignored workspaces, or `logs/`. Public APIs are declared in `include/`; diff --git a/docs/Security-Model.md b/docs/Security-Model.md index 9efd19c6..4945ff89 100644 --- a/docs/Security-Model.md +++ b/docs/Security-Model.md @@ -307,15 +307,15 @@ engine images. ## Source anchors -- [FF-M gateway](../src/arch/armv8m/ffm_nsc.c) -- [Secure Partition scheduler and SVC gates](../src/arch/armv8m/spm_svc.c) -- [Secure stack sealing and context switch](../src/arch/armv8m/coroutine_armv8m.c) -- [Secure fault attribution and SPM halt](../src/arch/armv8m/sp_fault_armv8m.c) -- [Secure MPU tables and the SPM RAM cover](../src/arch/armv8m/mpu_armv8m.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) +- [FF-M gateway](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/ffm_nsc.c) +- [Secure Partition scheduler and SVC gates](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/spm_svc.c) +- [Secure stack sealing and context switch](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/coroutine_armv8m.c) +- [Secure fault attribution and SPM halt](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/sp_fault_armv8m.c) +- [Secure MPU tables and the SPM RAM cover](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/mpu_armv8m.c) +- [Guest verification](https://github.com/wolfSSL/wolfTrust/blob/main/src/guest_verify.c) +- [HSM relay binding](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/wolfhsm/wt_hsm.c) +- [Native crypto dispatch](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/native/crypto_native.c) +- [Native vault key backend](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/native/keyvault.c) +- [Vault storage](https://github.com/wolfSSL/wolfTrust/blob/main/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 a2fc88e4..8cc8f7fd 100644 --- a/docs/Services.md +++ b/docs/Services.md @@ -98,10 +98,12 @@ output buffer is smaller than the object. The write-once flag is enforced by both the front end and checked NVM metadata. ITS data is held in Secure-only storage, but ITS does not add the Protected Storage sealing flag. -The current storage path always enforces `PSA_STORAGE_FLAG_WRITE_ONCE`, -including during `PSA_ROT_PROVISIONING`. This differs from PSA Secure Storage -1.0, which requires the flag not to be enforced in that lifecycle state. The -same deviation applies to Protected Storage objects created with the flag. +The ITS path enforces `PSA_STORAGE_FLAG_WRITE_ONCE` on objects created with +that flag, including during `PSA_ROT_PROVISIONING`. Objects created without it +can be updated or removed. This differs from PSA Secure Storage 1.0 §3.2, +which requires ITS not to enforce the flag in that lifecycle state. Protected +Storage also enforces caller-selected `WRITE_ONCE`; §3.2's lifecycle exception +applies to ITS. ## Protected Storage @@ -139,7 +141,7 @@ across equivalent boots, its software-component measurement type and description values are reversed, its signer ID hashes the literal name `wolfBoot` rather than identifying the signing key, and its implementation ID hashes a software label rather than identifying the immutable PSA RoT hardware -assembly. [TF-M Compatibility](TF-M-Compatibility.md) records these token-profile and API deviations. +assembly. [PSA Compatibility](PSA-Compatibility.md) records these token-profile and API deviations. The EAT/PSA claim set binds: diff --git a/docs/Standards.md b/docs/Standards.md new file mode 100644 index 00000000..ac6e66ae --- /dev/null +++ b/docs/Standards.md @@ -0,0 +1,33 @@ +# Standards and Claims + +wolfTrust targets the Arm Firmware Framework for M (FF-M) 1.0 contract and +selected Platform Security Architecture (PSA) service APIs. The reference +implementation is the STM32H563 Cortex-M33 port. Compatibility is documented +per interface, with its limits and test evidence; no full FF-M or PSA +certification is claimed. + +| Standard or profile | Implemented scope and deviations | +| --- | --- | +| [Arm FF-M 1.0](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132) | [FF-M Compatibility](FF-M-Compatibility.md) covers the client and Secure Partition APIs, manifests, IPC, isolation policy, and differences from the specification. One `psa_irq_enable()` operation is backported from an FF-M 1.1 beta extension. | +| [PSA Crypto API 1.4](https://arm-software.github.io/psa-api/crypto/1.4/) | [PSA Compatibility](PSA-Compatibility.md) records the wolfPSA surface and configuration-dependent algorithm support. [Crypto Engines](Crypto-Engines.md) explains where keys and operations execute. | +| [PSA Secure Storage API 1.0](https://arm-software.github.io/psa-api/storage/1.0/) | [PSA Compatibility](PSA-Compatibility.md) records the implemented ITS and Protected Storage operations and their flag and access-policy differences. | +| [PSA Initial Attestation API 1.0](https://arm-software.github.io/psa-api/attestation/1.0/) and [RFC 9783](https://www.rfc-editor.org/rfc/rfc9783.html) | [PSA Compatibility](PSA-Compatibility.md) records the token API subset and token-profile deviations, including the currently advertised `tag:psacertified.org,2023:psa#tfm` profile. | +| [PSA Firmware Update API 1.0](https://arm-software.github.io/psa-api/fwu/1.0/) | [PSA Compatibility](PSA-Compatibility.md) records the single-component update flow and unsupported trial-accept behavior. | + +The manifest's requested isolation profile is not a certificate of FF-M +isolation-level-3 conformance. [Security Model](Security-Model.md) describes +the enforced boundaries; [Threat Model](Threat-Model.md) describes assumptions +and residual risks. [Testing](Testing.md) separates host, emulator, and +physical-board evidence. + +## Deviation register + +- [FF-M framework differences](FF-M-Compatibility.md#framework-differences) + lists the IPC, manifest, lifecycle, and integration differences. +- [FF-M isolation-profile interpretation](FF-M-Compatibility.md#isolation-profile-interpretation) + separates requested manifest policy from enforced isolation. +- [PSA service API differences](PSA-Compatibility.md#service-api-differences) + lists storage, attestation, and firmware-update deviations. +- [Validation and claim boundaries](FF-M-Compatibility.md#validation-and-claim-boundary) + and [PSA validation](PSA-Compatibility.md#validation-and-claim-boundary) + identify the evidence behind each claim. diff --git a/docs/TF-M-Compatibility.md b/docs/TF-M-Compatibility.md deleted file mode 100644 index 02d23738..00000000 --- a/docs/TF-M-Compatibility.md +++ /dev/null @@ -1,190 +0,0 @@ -# TF-M Compatibility - -wolfTrust implements the PSA interfaces needed by its reference applications -without linking the Trusted Firmware-M runtime. Compatibility is at the C API -and service-behavior level; image layout, manifests, build integration, and -the SPM implementation are wolfTrust-specific. - -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/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` | -| PSA Crypto | 1.4 header | Supported through wolfPSA; algorithms depend on the guest profile | `lib/wolfPSA/wolfpsa/psa/crypto.h` and both reference guest settings | -| Internal Trusted Storage | 1.0 | Core set/get/get-info/remove subset with the `WRITE_ONCE` lifecycle deviation below | `include/psa/internal_trusted_storage.h` and `src/services/wolfhsm/wt_hsm_vault.c` | -| 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 status deviation 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/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` | - -## Intentional differences - -| Difference | Classification | Reason and impact | -| --- | --- | --- | -| wolfTrust exposes the FF-M Non-secure client API through one five-function CMSE gateway. | Implementation detail | The gateway exports framework version, service version, connect, call, and close; Secure Partition entry points are a separate manifest concern. | -| Only connection-based IPC services are enabled. | Scoped | The production manifest requests only `WT_MANIFEST_FEATURE_IPC`. SFN, stateless services, and memory-mapped I/O vectors are rejected by the runtime. | -| IPC uses fixed copied buffers. | Scoped safety bound | Calls are limited to four vectors and a 1024-byte transfer budget. Services apply smaller bounds. Applications must chunk larger data. | -| A Non-secure connect, call, or close cannot remain pending after the scheduler reaches quiescence for that dispatch. | Scoped deviation | An incomplete message becomes a client error. If already claimed, the service retains its message until reply or partition fault; late output is discarded. A late call reply releases the message, but the connection remains in error until the client requests close. If close was already requested, the late reply allows deferred disconnection to proceed. Shipped Non-secure-facing services reply within the dispatch; Secure Partition callers use the begin/finish path when scheduling another partition is required. | -| Secure services are linked in one image. | Scoped isolation difference | Service writable state is isolated by unprivileged threads and Secure MPU domains, but executable text is shared rather than separately linked. | -| All shipped service loops run as scheduled coroutines. | Implementation difference | Service code uses the standard Secure Partition API; privileged hardware access goes through identity-pinned SVC gates. | -| 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 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`. | -| The attestation token advertises `tag:psacertified.org,2023:psa#tfm` but does not implement that profile's claim semantics. | Known token-profile deviation | The boot seed is deterministic across equivalent boots; software-component measurement type and description values are reversed; signer ID hashes the literal name `wolfBoot` rather than identifying the signing key; and implementation ID hashes a software label rather than identifying the immutable PSA RoT hardware assembly. A distinct derived profile identifier is required until these claims conform to [RFC 9783](https://www.rfc-editor.org/rfc/rfc9783.html). | -| Firmware Update has no persistent trial-accept flow. | Scoped | Installation commits only after wolfBoot authenticates the swapped image at reboot; `psa_fwu_accept()` returns `PSA_ERROR_NOT_SUPPORTED`. | -| The firmware-update detached manifest is a 32-bit version word. | Scoped integration | Passing `NULL, 0` instead binds the version from the staged wolfBoot header. Other manifest encodings require an adapter. | -| Firmware Update reports unknown component IDs as `PSA_ERROR_INVALID_ARGUMENT`. | Known API deviation | PSA Firmware Update 1.0 specifies `PSA_ERROR_DOES_NOT_EXIST` for unknown component IDs. Unaligned block sizes are padded to the backend write alignment. | -| Secure memory uses no dynamic allocation. | Stronger resource policy | Fixed pools and buffers can reject excess work rather than expanding at runtime. | -| Manifests use wolfTrust JSON and generated C. | Integration difference | Existing TF-M manifests are not consumed directly. Security resources and services must be represented in the wolfTrust schema. | -| Secure Partition entry functions are bound at build time instead of being selected by each manifest's `entry_point` field. | Integration difference | The numeric field validates an executable window, but adding a service also requires a compiled entry wrapper and an explicit start call in `wt_ffm_boot_start_sched()`. | -| Service IDs are generated from the selected manifest. | Integration difference | Applications should include generated `psa_manifest/sid.h` instead of hard-coding target-specific values. | - -## Isolation level - -wolfTrust implements FF-M isolation level 3 only, and refuses a manifest that -declares level 1 or 2 or a privileged Secure Partition. The rule-by-rule -mapping and the deviations are in -[Security Model](Security-Model.md#ff-m-isolation-level-3). - -## Migrating an application - -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 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. -3. Initial Attestation currently has only the Zephyr-specific adapter at - `tests/firmware/zephyr-stm32h5/module/wolftrust-tee/src/wolftrust_attestation_client.c`. - The vendored `lib/wolfPSA/src/psa_attestation.c` is a stub that returns - `PSA_ERROR_NOT_SUPPORTED` and must not be linked instead of or alongside that - adapter. -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`. - For wolfTrust, the image offset must be aligned to - `1 << PSA_FWU_LOG2_WRITE_ALIGN`. An unaligned final block is padded by the - service to the backend write alignment. -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, - guest launch requirements, and minimum versions in the wolfTrust manifest. -8. Replace TF-M build and image assembly with the wolfBoot and wolfTrust flow. - Patch guest measurement records before signing wolfTrust. -9. Run host tests, target conformance under M33MU, and the hardware suite - separately. An emulator pass does not establish silicon attribution. - -See [API Reference](API-Reference.md), [Porting](Porting.md), and [Testing](Testing.md) for integration details. diff --git a/docs/Targets.md b/docs/Targets.md new file mode 100644 index 00000000..ac71ea27 --- /dev/null +++ b/docs/Targets.md @@ -0,0 +1,16 @@ +# Supported Targets + +The validated reference target is the STM32H563 Cortex-M33. wolfTrust uses an +Armv8-M architecture adapter and an STM32H563 target port. Other devices need +their own port, memory layout, manifest, guest integration, and validation. + +| Environment | Secure image and guests | Guide | +| --- | --- | --- | +| NUCLEO-H563ZI hardware | wolfBoot authenticates wolfTrust; Zephyr and FreeRTOS reference guests use the FF-M gateway. Board provisioning and flash protection are required for the hardened path. | [STM32H563 Board Guide](STM32H5-Guide.md) | +| M33MU Cortex-M33 emulator | Runs the authenticated chain and target scenarios without a physical board. Emulator results do not establish physical flash or debug-policy enforcement. | [Getting Started](Getting-Started.md#run-under-m33mu) and [Testing](Testing.md) | + +Start with [Getting Started](Getting-Started.md) for prerequisites and a first +build. See [Building](Building.md) for build controls and guest images, and +[Porting](Porting.md) for a new device's required architecture and target +contracts. [Security Model](Security-Model.md) describes the protection +mechanisms of the STM32H563 reference implementation. diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md deleted file mode 100644 index eaf8a7e2..00000000 --- a/docs/_Sidebar.md +++ /dev/null @@ -1,19 +0,0 @@ -**wolfTrust** - -- [[Home]] -- [[Getting Started]] -- [[Architecture]] -- [[Crypto Engines]] -- [[Security Model]] -- [[Threat Model]] -- [[API Reference]] -- [[Services]] -- [[TF-M Compatibility]] -- [[Macros]] -- [[Porting]] -- [[Building]] -- [[Testing]] -- [[Coding Standard]] -- [[Project Structure]] -- [[STM32H5 Guide]] -- [[MIMXRT700 Guide]] diff --git a/docs/assets/architecture.config.json b/docs/assets/architecture.config.json new file mode 100644 index 00000000..9dffb4c8 --- /dev/null +++ b/docs/assets/architecture.config.json @@ -0,0 +1,5 @@ +{ + "flowchart": { + "htmlLabels": false + } +} diff --git a/docs/assets/architecture.mmd b/docs/assets/architecture.mmd new file mode 100644 index 00000000..f6af5da8 --- /dev/null +++ b/docs/assets/architecture.mmd @@ -0,0 +1,22 @@ +flowchart TB + BOOT["wolfBoot authenticated loader"] + ZEPHYR["Zephyr guest"] + FREERTOS["FreeRTOS guest"] + CLIENT["wolfPSA client and selected crypto engine"] + GATE["FF-M client gateway"] + SPM["wolfTrust SPM: identity, policy, IPC"] + SERVICES["Secure crypto, storage, attestation, update"] + PORT["Armv8-M and STM32H563 port"] + + ZEPHYR --> CLIENT + FREERTOS --> CLIENT + CLIENT --> GATE + GATE --> SPM + BOOT --> SPM + SPM --> SERVICES + SPM --> PORT + + classDef secure fill:#eefbfc,stroke:#1fbeca,color:#111; + classDef nonsecure fill:#fff6ed,stroke:#c46715,color:#111; + class SPM,GATE,SERVICES,PORT secure; + class ZEPHYR,FREERTOS,CLIENT nonsecure; diff --git a/docs/assets/architecture.png b/docs/assets/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..e22e05d12a597dd7fb48863c9b5ade48fc2e6bce GIT binary patch literal 72561 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 CMaP@~ literal 0 HcmV?d00001 diff --git a/docs/assets/skin.css b/docs/assets/skin.css new file mode 100644 index 00000000..3ad8a7ef --- /dev/null +++ b/docs/assets/skin.css @@ -0,0 +1,25 @@ +/* Match the public wolfSSL manual colors without depending on its build repo. */ +.md-header { + background-color: #fff; + color: #1fbeca; +} + +.md-header__title, +.md-nav__link--active { + color: #1fbeca; +} + +.md-typeset a, +.md-nav__link { + color: #c46715; +} + +.md-typeset a:hover, +.md-nav__link:hover { + color: #1fbeca; +} + +.md-header__button.md-logo img { + height: 2.5rem; + width: auto; +} diff --git a/docs/Home.md b/docs/index.md similarity index 60% rename from docs/Home.md rename to docs/index.md index 2f5e03b2..05f074d3 100644 --- a/docs/Home.md +++ b/docs/index.md @@ -7,75 +7,18 @@ common runtime implements Arm Platform Security Architecture (PSA) Firmware Framework for M (FF-M) interprocess communication (IPC), manifest policy, scheduling, lifecycle management, fault recovery, and services. -The only currently supported and validated reference implementation combines -the Armv8-M adapter with the STM32H563 Cortex-M33 port. Support for additional -Cortex-M ports is an intended extension point. Such ports may reuse the common -runtime and, where applicable, the Armv8-M layer. Cortex-A support is an -architectural goal, not a current capability. It will require a new adapter and -changes to current internal execution and protection contracts; the design goal -is to preserve the public manifest, service, IPC, and PSA API contracts. +The currently supported and validated reference implementation combines +the Armv8-M adapter with the STM32H563 Cortex-M33 port. Additional Cortex-M +ports may reuse the common runtime and, where applicable, the Armv8-M layer. -## Architecture - -```mermaid -flowchart TB - BOOT[Trusted first-stage loader
current reference: wolfBoot] - - subgraph APP[Application domains] - GA[Cortex-M client A
Zephyr, FreeRTOS, or bare metal] - GB[Cortex-M client B
Zephyr, FreeRTOS, or bare metal] - end +[Download the wolfTrust Manual (PDF)](https://www.wolfssl.com/documentation/manuals/wolftrust/wolfTrust-Manual.pdf) - subgraph PORT[Architecture and target ports] - GW[Client gateway
current: five Armv8-M CMSE veneers] - ARCH[Architecture adapter
current: Armv8-M] - TARGET[Target and board port
current: STM32H563] - GW --- ARCH - ARCH --- TARGET - end - - subgraph WT[wolfTrust policy and service runtime] - SPM[Secure Partition Manager
policy, identity, IPC, scheduling, lifecycle, recovery] - subgraph SP[Secure services] - CR["Selected crypto engine
native or wolfHSM"] - ST["Internal Trusted Storage (ITS),
Protected Storage, and vault"] - AT[Initial Attestation] - FW[Firmware Update] - VN[Optional virtual networking] - end - SPM --> CR - SPM --> ST - SPM --> AT - SPM --> FW - SPM --> VN - end +## Architecture - subgraph LIBS[wolfSSL ecosystem components] - PSA[wolfPSA
guest wolfCrypt; optional wolfHSM client] - WC[Secure wolfCrypt
optional wolfHSM server] - COSE[wolfCOSE] - HAL[wolfHAL] - IP[wolfIP
optional bare-metal reference networking] - end +![wolfTrust architecture from authenticated boot through guest clients, FF-M services, and the STM32H563 port](assets/architecture.png) - BOOT -->|authenticated measurement, lifecycle, and version handoff| SPM - GA -->|generic FF-M client API| GW - GB -->|generic FF-M client API| GW - GA -->|PSA Crypto| PSA - GB -->|PSA Crypto| PSA - PSA -->|protected operations over FF-M| GW - GA -->|optional networking| IP - GB -->|optional networking| IP - IP -->|VNet service over FF-M| GW - GW -->|validated requests| SPM - SPM -->|Secure execution operations| ARCH - SPM -->|platform callbacks| TARGET - CR --> WC - ST --> WC - AT --> COSE - AT --> WC - TARGET -->|current register and RNG access| HAL -``` +The optional wolfIP/VNET reference uses separate bare-metal guests, not the +Zephyr and FreeRTOS PSA guest pair. ### Current reference port @@ -103,13 +46,17 @@ port's five CMSE gateway veneers. | Page | Contents | | --- | --- | | [Getting Started](Getting-Started.md) | Prerequisites, checkout, first builds, emulator use, and hardware entry points | +| [Supported Targets](Targets.md) | Validated hardware and emulator environments, with setup paths | | [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 | | [Services](Services.md) | Behavior and access policy for each Secure service | -| [TF-M Compatibility](TF-M-Compatibility.md) | Supported interfaces, intentional differences, and migration guidance | +| [Standards and Claims](Standards.md) | Arm specifications, implemented scope, deviations, and claim boundaries | +| [FF-M Compatibility](FF-M-Compatibility.md) | Arm FF-M requirements, implemented framework interfaces, and known deviations | +| [PSA Compatibility](PSA-Compatibility.md) | PSA service APIs, implemented subsets, known deviations, and porting guidance | +| [Footprint Comparison](Footprint-Comparison.md) | Dated local Secure-image measurements and methodology | | [Macros](Macros.md) | Supported build and manifest configuration | | [Porting](Porting.md) | Architecture and target port contracts | | [Building](Building.md) | Build targets, outputs, and cross-build options | diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..223afef8 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,62 @@ +site_name: wolfTrust Manual +site_description: Secure Partition Manager and services runtime for Cortex-M +site_url: https://www.wolfssl.com/documentation/manuals/wolftrust/ +repo_url: https://github.com/wolfSSL/wolfTrust +edit_uri: edit/main/docs/ +docs_dir: docs +site_dir: build/docs-site +copyright: Copyright © wolfSSL Inc. +use_directory_urls: false + +nav: + - Overview: + - wolfTrust: index.md + - Architecture: Architecture.md + - Security Model: Security-Model.md + - Threat Model: Threat-Model.md + - Get started: + - Getting Started: Getting-Started.md + - Supported Targets: Targets.md + - STM32H563 Board Guide: STM32H5-Guide.md + - Building: Building.md + - Testing: Testing.md + - Use wolfTrust: + - Services: Services.md + - API Reference: API-Reference.md + - Crypto Engines: Crypto-Engines.md + - Build Options: Macros.md + - Specifications and deviations: + - Standards and Claims: Standards.md + - FF-M Compatibility: FF-M-Compatibility.md + - PSA Compatibility: PSA-Compatibility.md + - Footprint Comparison: Footprint-Comparison.md + - Porting and development: + - Porting: Porting.md + - Project Structure: Project-Structure.md + - C Coding Standard: Coding-Standard.md + +theme: + name: material + logo: assets/logo.png + favicon: assets/logo.png + features: + - navigation.sections + - navigation.top + - content.code.copy + palette: + primary: white + accent: deep orange + +extra_css: + - assets/skin.css + +markdown_extensions: + - tables + - admonition + - pymdownx.details + - pymdownx.superfences + - toc: + permalink: true + +plugins: + - search diff --git a/tools/check-docs-no-internal-links.sh b/tools/check-docs-no-internal-links.sh index 1ba4a50d..773390f7 100755 --- a/tools/check-docs-no-internal-links.sh +++ b/tools/check-docs-no-internal-links.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Docs guard. docs/ is published to the wiki, so the public tree must never +# Docs guard. docs/ is published as the manual, so the public tree must never # point at the internal ledger or at a developer's home directory. # # tools/check-docs-no-internal-links.sh [path...] diff --git a/tools/docs-manual/Makefile b/tools/docs-manual/Makefile new file mode 100644 index 00000000..93eb012a --- /dev/null +++ b/tools/docs-manual/Makefile @@ -0,0 +1,15 @@ +.DEFAULT_GOAL := all +include ../common/common.am +include build/order.mk + +SRC := src +PDF := wolfTrust-Manual.pdf + +.PHONY: all html-prep pdf-prep +all: pdf html + +html-prep: + @true + +pdf-prep: + python3 "$(WT_SOURCE)/tools/docs_manual.py" pdf-links --documentation-root .. diff --git a/tools/docs-manual/documentation-rev b/tools/docs-manual/documentation-rev new file mode 100644 index 00000000..66273b3c --- /dev/null +++ b/tools/docs-manual/documentation-rev @@ -0,0 +1 @@ +9455a759732a45c2ec1d408956105019ec9ced21 diff --git a/tools/docs_manual.py b/tools/docs_manual.py new file mode 100644 index 00000000..25aa97ff --- /dev/null +++ b/tools/docs_manual.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Build the wolfTrust manual with wolfSSL/documentation's shared tooling.""" + +import argparse +import json +import re +import shutil +import subprocess +from pathlib import Path + +import yaml +from markdown.extensions.toc import slugify + + +MANUAL = "wolfTrust" +PDF = "wolfTrust-Manual.pdf" +HEADING = re.compile(r"^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$") +LINK = re.compile(r"\]\(([^)]+\.md)(?:#([^)]+))?\)") + + +def pages_from_nav(value): + if isinstance(value, str): + yield value + elif isinstance(value, list): + for item in value: + yield from pages_from_nav(item) + elif isinstance(value, dict): + for item in value.values(): + yield from pages_from_nav(item) + + +def page_key(filename): + return "wt-" + slugify(Path(filename).stem, "-") + + +def heading_slug(title): + title = re.sub(r"[`*_]", "", title) + title = re.sub(r"\[([^]]+)\]\([^)]+\)", r"\1", title) + return slugify(title, "-") + + +def stage(documentation_root, source_root): + manual = documentation_root / MANUAL + source_docs = source_root / "docs" + shared = documentation_root / "common" / "common.am" + if not source_docs.is_dir() or not shared.is_file(): + raise RuntimeError("wolfTrust docs or documentation/common.am is missing") + + config = yaml.safe_load((source_root / "mkdocs.yml").read_text()) + pages = list(pages_from_nav(config["nav"])) + if not pages or pages[0] != "index.md" or len(set(pages)) != len(pages): + raise RuntimeError("manual navigation must start at index.md and contain unique pages") + source_pages = {path.name for path in source_docs.glob("*.md")} + if set(pages) != source_pages: + raise RuntimeError( + f"manual navigation mismatch: missing={sorted(source_pages - set(pages))}, " + f"unknown={sorted(set(pages) - source_pages)}" + ) + + manual.mkdir(exist_ok=True) + for name in ("src", "build", "html"): + path = manual / name + if path.exists(): + shutil.rmtree(path) + shutil.copytree(source_docs, manual / "src") + shutil.copyfile(manual / "src" / "index.md", manual / "src" / "Home.md") + (manual / "build").mkdir() + ordered_sources = ["Home.md" if page == "index.md" else page for page in pages] + (manual / "build" / "order.mk").write_text( + "SOURCES := " + " ".join(ordered_sources) + "\nAPPENDIX :=\n" + ) + (manual / "build" / "order.json").write_text(json.dumps(pages) + "\n") + shutil.copyfile(source_root / "tools" / "docs-manual" / "Makefile", manual / "manual.generated.mk") + + config["docs_dir"] = "build/html" + config["site_dir"] = "html" + config["theme"] = { + "name": None, + "custom_dir": "../mkdocs-material/material", + "language": "en", + "palette": {"primary": "indigo", "accent": "indigo"}, + "font": {"text": "Roboto", "code": "Roboto Mono"}, + "icon": "logo.png", + "logo": "logo.png", + "favicon": "logo.png", + "feature": {"tabs": True}, + } + config["extra_css"] = ["skin.css"] + config["markdown_extensions"] = ["tables", "fenced_code", "toc"] + config["plugins"] = ["search"] + config["use_directory_urls"] = False + (manual / "mkdocs.yml").write_text(yaml.safe_dump(config, sort_keys=False)) + + header = (documentation_root / "wolfBoot" / "header.txt").read_text() + header = header.replace("wolfBoot Documentation", "wolfTrust Manual") + (manual / "header.txt").write_text(header) + return manual + + +def prepare_pdf(documentation_root): + manual = documentation_root / MANUAL + pdf_dir = manual / "build" / "pdf" + pages = json.loads((manual / "build" / "order.json").read_text()) + headers = {} + for page in pages: + staged = pdf_dir / ("Home.md" if page == "index.md" else page) + found = set() + in_fence = False + for line in staged.read_text().splitlines(): + if line.lstrip().startswith(("```", "~~~")): + in_fence = not in_fence + if not in_fence: + match = HEADING.match(line) + if match: + found.add(heading_slug(match.group(2))) + headers[page] = found + + for page in pages: + staged = pdf_dir / ("Home.md" if page == "index.md" else page) + output = [f"[]{{#{page_key(page)}}}\n\n"] + in_fence = False + seen = set() + for line in staged.read_text().splitlines(keepends=True): + if line.lstrip().startswith(("```", "~~~")): + in_fence = not in_fence + if not in_fence: + match = HEADING.match(line.rstrip("\n")) + if match: + slug = heading_slug(match.group(2)) + if slug in seen: + raise RuntimeError(f"duplicate PDF heading anchor in {page}: {slug}") + seen.add(slug) + line = line.rstrip("\n") + f" {{#{page_key(page)}-{slug}}}\n" + + def rewrite(link): + target = Path(link.group(1)).name + if target not in headers: + return link.group(0) + fragment = link.group(2) + if fragment and fragment not in headers[target]: + raise RuntimeError(f"unresolved PDF link: {page} -> {target}#{fragment}") + anchor = page_key(target) + (f"-{fragment}" if fragment else "") + return f"](#{anchor})" + + line = LINK.sub(rewrite, line) + output.append(line) + staged.write_text("".join(output)) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=("build", "pdf-links")) + parser.add_argument("--documentation-root", required=True, type=Path) + parser.add_argument("--source-root", type=Path) + parser.add_argument("--target", choices=("all", "html", "pdf"), default="all") + args = parser.parse_args() + documentation_root = args.documentation_root.resolve() + if args.command == "pdf-links": + prepare_pdf(documentation_root) + return + if args.source_root is None: + parser.error("build requires --source-root") + source_root = args.source_root.resolve() + manual = stage(documentation_root, source_root) + subprocess.run( + ["make", "-C", str(manual), "-f", "manual.generated.mk", args.target, + f"WT_SOURCE={source_root}"], + check=True, + ) + + +if __name__ == "__main__": + main() From ca3be08089bdad3c08fa3350218a73a71dee09b1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 17:40:40 -0700 Subject: [PATCH 3/9] Refine wolfTrust manual and port navigation --- docs/API-Reference.md | 12 ++++--- docs/Coding-Standard.md | 4 +-- docs/Crypto-Engines.md | 13 ++++--- docs/Macros.md | 76 ++++++++++++++++++++++++++--------------- docs/Porting.md | 10 ++++++ docs/Security-Model.md | 8 ++--- docs/Targets.md | 36 +++++++++++++++---- docs/Testing.md | 8 +++-- docs/index.md | 2 +- mkdocs.yml | 7 ++-- tools/docs_manual.py | 1 + 11 files changed, 123 insertions(+), 54 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index d664e4b3..67c9fb88 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -88,10 +88,14 @@ struct psa_storage_info_t { }; ``` -Defined flags are `PSA_STORAGE_FLAG_NONE`, -`PSA_STORAGE_FLAG_WRITE_ONCE`, -`PSA_STORAGE_FLAG_NO_CONFIDENTIALITY`, and -`PSA_STORAGE_FLAG_NO_REPLAY_PROTECTION`. +Defined flags are: + +```c +PSA_STORAGE_FLAG_NONE +PSA_STORAGE_FLAG_WRITE_ONCE +PSA_STORAGE_FLAG_NO_CONFIDENTIALITY +PSA_STORAGE_FLAG_NO_REPLAY_PROTECTION +``` ### Firmware Update types diff --git a/docs/Coding-Standard.md b/docs/Coding-Standard.md index c394c0d6..19092046 100644 --- a/docs/Coding-Standard.md +++ b/docs/Coding-Standard.md @@ -1,8 +1,8 @@ # C Coding Standard wolfTrust adheres to the wolfSSL coding standards and targets ISO C99, with -project-specific no-`goto` and no-standalone-scope rules. The aim is to keep -the code straightforward to assess in a future MISRA C:2023 or DO-178 process. +project-specific no-`goto` and no-standalone-scope rules. These rules make the +code easier to assess in a future MISRA C:2023 or DO-178 process. These checks improve readiness; they are not a claim of MISRA compliance or certification evidence. diff --git a/docs/Crypto-Engines.md b/docs/Crypto-Engines.md index 5e6ec6f2..42a74b67 100644 --- a/docs/Crypto-Engines.md +++ b/docs/Crypto-Engines.md @@ -285,10 +285,15 @@ 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`. + set: + + ```text + __acle_se_WolfTrust_FFM_FrameworkVersion + __acle_se_WolfTrust_FFM_ServiceVersion + __acle_se_WolfTrust_FFM_Connect + __acle_se_WolfTrust_FFM_Call + __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`, diff --git a/docs/Macros.md b/docs/Macros.md index 5e57b286..32f250fd 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -1,15 +1,17 @@ # Macros -The STM32H563 build is configured through GNU Make variables. The build turns -selected values into C preprocessor defines. Defaults below come from -`mk/target-stm32h563.mk`, `mk/arch-armv8m.mk`, and `mk/common.mk`. +Both Armv8-M builds are configured through GNU Make variables. The build turns +selected values into C preprocessor defines. STM32H563 is the default; +target-specific values come from `mk/target-stm32h563.mk` or +`mk/target-mimxrt700.mk`, with architecture and shared values from +`mk/arch-armv8m.mk` and `mk/common.mk`. ## Build selection | Define | Description | Requirement | | --- | --- | --- | | `ARCH` | Architecture build selector; default `armv8m`. | Must match an `mk/arch-.mk` fragment; `armv8m` is the only architecture today. | -| `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`. | +| `TARGET` | Target build selector; default `stm32h563`, with `mimxrt700` also available. | 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_LTO` | Enable Secure-image link-time optimization; default `1`. | Set to `0` for diagnostics or a non-LTO size comparison. The GNU Arm compiler must support `-flto=auto`. | @@ -22,33 +24,45 @@ 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 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. | +| `WT_SHARED_UART` | Reference guest UART selection; STM32H563 default `3`, MIMXRT700 default `0`. | Guest and Secure builds must use a consistent value. | +| `WT_GUEST_CORE_CLOCK_HZ` | Guest core-clock value; STM32H563 default `240000000`, MIMXRT700 default `237500000`. | Must match the configured target clock. | +| `WT_GUEST_UART_CLOCK_HZ` | Guest UART-clock value; STM32H563 default `120000000`, MIMXRT700 default `24000000`. | Must match the selected UART clock source. | ## Security and service options | 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_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; MIMXRT700 has no guest NOR write-protection check and refuses guest launch with this option. | | `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. | | `WT_WOLFCRYPT_STM32_HASH` | STM32 HASH acceleration selector; default `0`. | Must remain `0`: the Makefile rejects other values because wolfHSM SHA state is not compatible with the peripheral representation. | -| `WT_CONFORMANCE` | When `1`, select the manifest and sources used by Arm PSA API validation. | Use only for conformance builds; the default production manifest is selected at `0`. | +| `WT_CONFORMANCE` | When `1`, select the manifest and sources used by Arm PSA API validation. | STM32H563 only; MIMXRT700 rejects this selector because it has no conformance manifest. | ## Image layout -| Define | Description | Requirement | -| --- | --- | --- | -| `WT_SECURE_FLASH_BASE` | Secure image link base; standalone default `0x0C000000`. | Must match the bootloader slot and linker layout. | -| `WT_SECURE_FLASH_SIZE` | Available Secure image bytes; standalone default `0x00020000`. | Must cover the linked image without overlapping another flash region. | -| `WT_SECURE_IMAGE_HEADER_SIZE` | Bytes reserved before linked code; default `0` (`0x400` for `TARGET=mimxrt700`, wolfBoot's RT700 header). | Set to match the header layout of an image signed for wolfBoot. | -| `WT_GUEST0_FLASH_BASE` | Guest 0 flash base; standalone default `0x08020000`. | Must match the guest link address and manifest executable window. | -| `WT_GUEST1_FLASH_BASE` | Guest 1 flash base; standalone default `0x08040000`. | Must match the guest link address and manifest executable window. | -| `WT_GUEST0_FLASH_SIZE` | Guest 0 flash window; default `0x00020000`. | Must contain the signed record's image size and use valid target alignment. | -| `WT_GUEST1_FLASH_SIZE` | Guest 1 flash window; default `0x00020000`. | Must contain the signed record's image size and use valid target alignment. | +- `WT_SECURE_FLASH_BASE`: Secure image link base; standalone default + `0x0C000000` on STM32H563 or `0x38040000` on MIMXRT700. Match the bootloader + slot and linker layout. +- `WT_SECURE_FLASH_SIZE`: Available Secure image bytes; standalone default + `0x00020000` on STM32H563 or `0x00040000` on MIMXRT700. Cover the linked image + without overlapping another flash region. +- `WT_SECURE_IMAGE_HEADER_SIZE`: Bytes reserved before linked code; default + `0` on STM32H563 or `0x400` on MIMXRT700. Match the header layout of an image + signed for wolfBoot. +- `WT_GUEST0_FLASH_BASE`: Guest 0 flash base; standalone default `0x08020000`. + MIMXRT700 defaults to `0x28080000`. Match the guest link address and manifest + executable window. +- `WT_GUEST1_FLASH_BASE`: Guest 1 flash base; standalone default `0x08040000`. + MIMXRT700 defaults to `0x28100000`. Match the guest link address and manifest + executable window. +- `WT_GUEST0_FLASH_SIZE`: Guest 0 flash window; default `0x00020000`. + MIMXRT700 defaults to `0x00080000`. Contain the signed record's image size and + use valid target alignment. +- `WT_GUEST1_FLASH_SIZE`: Guest 1 flash window; default `0x00020000` on + STM32H563 or `0x00040000` on MIMXRT700. Contain the signed record's image + size and use valid target alignment. The target runners used for authenticated boot override standalone addresses for the wolfBoot partition layout. Keep bootloader, Secure image, manifest, @@ -56,15 +70,23 @@ guest linker files, measurement records, and flash commands consistent. ## Virtual network options -| Define | Description | Requirement | -| --- | --- | --- | -| `CONFIG_VNET` | Select the VNET manifest and link the Secure switch when set to `y`; default `n`. | Both guests must use the matching VNET transport and memory layout. | -| `WT_VNET_POOL_SLOTS` | Secure frame-pool slot count; default `8`. | Static VNET data must fit its manifest region. | -| `WT_VNET_FRAME_MAX` | Internal maximum Ethernet frame bytes; default `1536`. | Must be large enough for the selected link frame; PSA transport still limits the exposed MTU to 1000. | -| `WT_VNET_RX_QUEUE_DEPTH` | Per-port receive queue depth; default `8`. | Static queue storage must fit the VNET data region. | -| `WT_VNET_RX_IRQ` | Synthetic Non-secure receive interrupt; default `130`. | Must be representable and assigned consistently in the target IRQ policy. | -| `WT_VNET_TIMEOUT_TICKS` | Reserved frame-expiration threshold; default `500`. | The value is compiled, but production code does not currently call `vnet_switch_drop_expired()`, so changing it has no runtime effect. | -| `WT_VNET_UNKNOWN_UCAST_FLOOD` | Flood unknown unicast frames when set to `1`; default `0`. | Enable only when the guest-network policy permits it. | +- `CONFIG_VNET`: Select the VNET manifest and link the Secure switch when set + to `y`; default `n`. STM32H563 only; MIMXRT700 rejects this selector. Both + guests need the matching transport and memory layout. +- `WT_VNET_POOL_SLOTS`: Secure frame-pool slot count; default `8`. Static VNET + data must fit its manifest region. +- `WT_VNET_FRAME_MAX`: Internal maximum Ethernet frame bytes; default `1536`. + It must fit the selected link frame; PSA transport limits the exposed MTU to + 1000. +- `WT_VNET_RX_QUEUE_DEPTH`: Per-port receive queue depth; default `8`. Static + queue storage must fit the VNET data region. +- `WT_VNET_RX_IRQ`: Synthetic Non-secure receive interrupt; default `130`. + Assign it consistently in the target IRQ policy. +- `WT_VNET_TIMEOUT_TICKS`: Reserved frame-expiration threshold; default `500`. + Production code does not currently call `vnet_switch_drop_expired()`, so + changing this value has no runtime effect. +- `WT_VNET_UNKNOWN_UCAST_FLOOD`: Flood unknown unicast frames when set to `1`; + default `0`. Enable only when the guest-network policy permits it. ## Fixed Secure wolfCrypt defines diff --git a/docs/Porting.md b/docs/Porting.md index f7c3cc79..e8b66a57 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -10,6 +10,11 @@ additional Cortex-M ports is an intended extension point. Such ports may reuse common policy and service code and an existing architecture adapter when their execution and protection models match. +No AArch64 architecture adapter or target is implemented or validated in the +current release. An AArch64 port needs a separate architecture layer and device +port; the common contracts describe its intended boundary, not an available +build. + Every new port must report its actual capabilities and must not claim security properties until they are tested on that target. @@ -191,6 +196,11 @@ worked examples above give a concrete map for each board. before signing wolfTrust. 8. Add safe provisioning tooling for the target's security attribution, application-image write protection, debug policy, and product lifecycle. +9. Add the target to [Ports and supported targets](Targets.md) with its actual + validation status. Keep board-specific setup in a separate guide, add that + guide to `mkdocs.yml`, and document which results came from an emulator + versus physical hardware. Do not label a port supported until its build, + deployment, and target security checks have been validated. ## Validation checklist diff --git a/docs/Security-Model.md b/docs/Security-Model.md index 4945ff89..3df67be9 100644 --- a/docs/Security-Model.md +++ b/docs/Security-Model.md @@ -307,14 +307,14 @@ engine images. ## Source anchors -- [FF-M gateway](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/ffm_nsc.c) -- [Secure Partition scheduler and SVC gates](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/spm_svc.c) +- FF-M gateway: [copied request handling](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/common/ffm_gateway.c) and [Armv8-M veneers](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/ffm_nsc.c) +- Secure Partition scheduling and SVC gates: [common gate](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/common/spm_gate_core.c) and [Armv8-M SVC](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/spm_svc.c) - [Secure stack sealing and context switch](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/coroutine_armv8m.c) - [Secure fault attribution and SPM halt](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/sp_fault_armv8m.c) - [Secure MPU tables and the SPM RAM cover](https://github.com/wolfSSL/wolfTrust/blob/main/src/arch/armv8m/mpu_armv8m.c) - [Guest verification](https://github.com/wolfSSL/wolfTrust/blob/main/src/guest_verify.c) -- [HSM relay binding](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/wolfhsm/wt_hsm.c) -- [Native crypto dispatch](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/native/crypto_native.c) +- [HSM relay binding](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/hsm_relay_service.c) +- Native crypto dispatch: [service](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/native/crypto_native.c) and [wire protocol](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/native/native_wire.c) - [Native vault key backend](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/native/keyvault.c) - [Vault storage](https://github.com/wolfSSL/wolfTrust/blob/main/src/services/wolfhsm/wt_hsm_vault.c) diff --git a/docs/Targets.md b/docs/Targets.md index ac71ea27..3b8f2718 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -1,13 +1,35 @@ -# Supported Targets +# Ports and supported targets -The validated reference target is the STM32H563 Cortex-M33. wolfTrust uses an -Armv8-M architecture adapter and an STM32H563 target port. Other devices need -their own port, memory layout, manifest, guest integration, and validation. +wolfTrust separates its common runtime, architecture adapter, and device port. +The only currently supported and validated build tuple is Armv8-M on +STM32H563. The entries below distinguish available builds from future ports. -| Environment | Secure image and guests | Guide | +## Armv8-M + +### STM32H563 (supported reference port) + +The `armv8m-stm32h563` tuple has two validation environments: + +| Environment | What it exercises | Guide | | --- | --- | --- | -| NUCLEO-H563ZI hardware | wolfBoot authenticates wolfTrust; Zephyr and FreeRTOS reference guests use the FF-M gateway. Board provisioning and flash protection are required for the hardened path. | [STM32H563 Board Guide](STM32H5-Guide.md) | -| M33MU Cortex-M33 emulator | Runs the authenticated chain and target scenarios without a physical board. Emulator results do not establish physical flash or debug-policy enforcement. | [Getting Started](Getting-Started.md#run-under-m33mu) and [Testing](Testing.md) | +| NUCLEO-H563ZI hardware | wolfBoot authenticates wolfTrust; Zephyr and FreeRTOS reference guests use the FF-M gateway. Board provisioning and flash protection are required for the hardened path. | [STM32H563 board guide](STM32H5-Guide.md) | +| M33MU Cortex-M33 emulator | The authenticated chain and target scenarios without a physical board. Emulator results do not establish physical flash or debug-policy enforcement. | [Getting Started](Getting-Started.md#run-under-m33mu) and [Testing](Testing.md) | + +### Additional Armv8-M devices + +An additional Cortex-M33 device, such as i.MX RT700, needs its own device port, +memory layout, manifest, guest integration, and target validation before it +can be listed as supported. It may reuse the Armv8-M adapter if its execution +and protection model fit the adapter contract. Add a board guide alongside +this page when that port has a validated build and deployment path. + +## AArch64 + +An AArch64 device, such as a future Versal target, requires an AArch64 +architecture adapter and a device port. No AArch64 build or hardware +validation is currently claimed. Keep platform-specific setup in a separate +board guide once the port exists; the common [Porting](Porting.md) page defines +the contracts shared across architectures. Start with [Getting Started](Getting-Started.md) for prerequisites and a first build. See [Building](Building.md) for build controls and guest images, and diff --git a/docs/Testing.md b/docs/Testing.md index c7e5dbf4..ff0d5cc8 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -184,18 +184,22 @@ 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 engine split is exercised under both engines by: - the applicable M33MU scenario matrix; - the Arm FF-M IPC suite at 85 passed, 4 heap-dependent tests skipped, and 0 failed, test for test as recorded in - [`tests/target/ffm_ipc_results.txt`](../tests/target/ffm_ipc_results.txt); + [`tests/target/ffm_ipc_results.txt`](https://github.com/wolfSSL/wolfTrust/blob/main/tests/target/ffm_ipc_results.txt); - 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. +Pass and skip counts depend on the build and test revisions. Use the logs from +the selected CI run for exact results; the runner checks the scheduled test +total and treats failures as failures. + The engine dimension changes crypto dispatch, not what M33MU proves. Emulator results still do not establish STM32 attribution or physical flash behavior. diff --git a/docs/index.md b/docs/index.md index 05f074d3..d039e11b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,7 +46,7 @@ port's five CMSE gateway veneers. | Page | Contents | | --- | --- | | [Getting Started](Getting-Started.md) | Prerequisites, checkout, first builds, emulator use, and hardware entry points | -| [Supported Targets](Targets.md) | Validated hardware and emulator environments, with setup paths | +| [Ports and supported targets](Targets.md) | Supported Armv8-M environments and architecture groups for future ports | | [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 | diff --git a/mkdocs.yml b/mkdocs.yml index 223afef8..01f5632f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,7 +5,7 @@ repo_url: https://github.com/wolfSSL/wolfTrust edit_uri: edit/main/docs/ docs_dir: docs site_dir: build/docs-site -copyright: Copyright © wolfSSL Inc. +copyright: Copyright © 2026 wolfSSL Inc. use_directory_urls: false nav: @@ -16,10 +16,11 @@ nav: - Threat Model: Threat-Model.md - Get started: - Getting Started: Getting-Started.md - - Supported Targets: Targets.md - - STM32H563 Board Guide: STM32H5-Guide.md - Building: Building.md - Testing: Testing.md + - Ports: + - Supported targets: Targets.md + - STM32H563 board guide: STM32H5-Guide.md - Use wolfTrust: - Services: Services.md - API Reference: API-Reference.md diff --git a/tools/docs_manual.py b/tools/docs_manual.py index 25aa97ff..0bc4e722 100644 --- a/tools/docs_manual.py +++ b/tools/docs_manual.py @@ -93,6 +93,7 @@ def stage(documentation_root, source_root): header = (documentation_root / "wolfBoot" / "header.txt").read_text() header = header.replace("wolfBoot Documentation", "wolfTrust Manual") + header = re.sub(r"(\\copyright\s+)\d{4}", r"\g<1>2026", header) (manual / "header.txt").write_text(header) return manual From 7d479aa1cb17c7a063e00db67292651d5ca58eba Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 19:14:04 -0700 Subject: [PATCH 4/9] Make footprint revisions readable in PDF --- docs/Footprint-Comparison.md | 46 +++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/docs/Footprint-Comparison.md b/docs/Footprint-Comparison.md index 5dc69b0a..559d2bef 100644 --- a/docs/Footprint-Comparison.md +++ b/docs/Footprint-Comparison.md @@ -41,18 +41,27 @@ and network access for source checkouts. The commands use POSIX shell syntax; see the [TF-M build instructions](https://tf-m.docs.trustedfirmware.org/en/latest/building/tfm_build_instruction.html) for its remaining host prerequisites. -The wolfTrust rows were measured on 2026-09-18 from wolfTrust commit -`c8baa2681b8b1e2ec57e5720cba28519bb306ab3`, which records the dependency -revisions below. Later commits may produce different sizes. - -| 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 rows were measured on 2026-09-18 from this wolfTrust commit: + +```text +c8baa2681b8b1e2ec57e5720cba28519bb306ab3 +``` + +It records the dependency revisions below. Later commits may produce +different sizes. + +- **wolfCOSE** (`v2.0.0`): + `f907071b10127f3ae2dd7719749a91b039ff04a1` +- **wolfHSM** (`wolfHSM-v1.4.0-171-ga032315`): + `a0323156606282448f00473a3fcb7aaa69361921` +- **wolfIP** (`v1.0-91-g146de4b`): + `146de4b6362c3a076787e27332f50daa0a445cf5` +- **wolfPSA** (`v5.9.1-129-g1b9ec29`): + `1b9ec29706bc63f785682ad688350195a33b22e8` +- **wolfSSL** (`v5.9.1-stable-1088-g22e505bcf`): + `22e505bcfad8ce21067ee4232128728543767a95` +- **wolfHAL** (no reachable tag): + `2bc2938b0bbcc977177153a7f38393710702bf70` To reproduce the wolfTrust snapshot, use a separate checkout at that commit. Its historical `.gitmodules` has SSH URLs for three submodules; the one-time @@ -75,10 +84,15 @@ 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`. In a separate directory, -clone and check out that revision; run the remaining commands from its source -root. These AN521 GNU Arm builds enable BL2 and no regression tests: +The TF-M source was the `TF-Mv2.1.1-LTS` tag at this commit: + +```text +02bf279913439a07082dd581df033f370a8fbb92 +``` + +In a separate directory, clone and check out that revision; run the remaining +commands from its source root. These AN521 GNU Arm builds enable BL2 and no +regression tests: ```sh git clone https://github.com/TrustedFirmware-M/trusted-firmware-m.git tf-m From 980ad84b2190b012b66f330bc3be558cde56febc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 19:17:09 -0700 Subject: [PATCH 5/9] Keep full dependency hashes inside PDF margins --- docs/Footprint-Comparison.md | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/Footprint-Comparison.md b/docs/Footprint-Comparison.md index 559d2bef..876a2c3f 100644 --- a/docs/Footprint-Comparison.md +++ b/docs/Footprint-Comparison.md @@ -51,17 +51,40 @@ It records the dependency revisions below. Later commits may produce different sizes. - **wolfCOSE** (`v2.0.0`): - `f907071b10127f3ae2dd7719749a91b039ff04a1` + + ```text + f907071b10127f3ae2dd7719749a91b039ff04a1 + ``` + - **wolfHSM** (`wolfHSM-v1.4.0-171-ga032315`): - `a0323156606282448f00473a3fcb7aaa69361921` + + ```text + a0323156606282448f00473a3fcb7aaa69361921 + ``` + - **wolfIP** (`v1.0-91-g146de4b`): - `146de4b6362c3a076787e27332f50daa0a445cf5` + + ```text + 146de4b6362c3a076787e27332f50daa0a445cf5 + ``` + - **wolfPSA** (`v5.9.1-129-g1b9ec29`): - `1b9ec29706bc63f785682ad688350195a33b22e8` + + ```text + 1b9ec29706bc63f785682ad688350195a33b22e8 + ``` + - **wolfSSL** (`v5.9.1-stable-1088-g22e505bcf`): - `22e505bcfad8ce21067ee4232128728543767a95` + + ```text + 22e505bcfad8ce21067ee4232128728543767a95 + ``` + - **wolfHAL** (no reachable tag): - `2bc2938b0bbcc977177153a7f38393710702bf70` + + ```text + 2bc2938b0bbcc977177153a7f38393710702bf70 + ``` To reproduce the wolfTrust snapshot, use a separate checkout at that commit. Its historical `.gitmodules` has SSH URLs for three submodules; the one-time From 91050ece348ff00da8ba60e843d9fe79689c697c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 19:26:44 -0700 Subject: [PATCH 6/9] Align H5 recovery flash address with hardware runner --- docs/STM32H5-Guide.md | 5 +++-- tests/target/provisioning_ctrl.sh | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/STM32H5-Guide.md b/docs/STM32H5-Guide.md index 94fbb5f7..5f48de71 100644 --- a/docs/STM32H5-Guide.md +++ b/docs/STM32H5-Guide.md @@ -200,8 +200,9 @@ recoverable test. Regression performs a full mass-erase back to Open. After regression, rerun `set-perimeter`, rebuild and flash the complete chain with the current hardware runner, reapply WRP, and rerun the positive checks. -Do not use `provisioning_ctrl.sh flash` or `restore` until its Guest 1 address is -changed from the stale `0x080C0000` value to the current `0x080E0000` layout. +The `provisioning_ctrl.sh flash` and `restore` commands use the same Guest 1 +address, `0x080E0000`, as the hardware runner. Prefer the runner for the full +build, flash, and verification sequence described above. Every board-writing control command requires `WT_LOCK_CONFIRM=1`. Review the exact current command in diff --git a/tests/target/provisioning_ctrl.sh b/tests/target/provisioning_ctrl.sh index 61b46b9b..53743df9 100755 --- a/tests/target/provisioning_ctrl.sh +++ b/tests/target/provisioning_ctrl.sh @@ -67,7 +67,7 @@ WRP_GUEST=0x000FFFFF; WRP_OPEN=0xFFFFFFFF PS_OPEN=0xED; PS_PROVISIONING=0x17; PS_TZCLOSED=0xC6; PS_CLOSED=0x72; PS_LOCKED=0x5C # Flash layout (matches run_h5_hardware.sh). -WOLFBOOT=0x0C000000; WOLFTRUST=0x0C060000; GUEST0=0x080A0000; GUEST1=0x080C0000 +WOLFBOOT=0x0C000000; WOLFTRUST=0x0C060000; GUEST0=0x080A0000; GUEST1=0x080E0000 wb="$repo/wolfBoot/wolfboot.bin" wt="$repo/build/wolftrust_v1_signed.bin" g0="$repo/tests/firmware/zephyr-stm32h5/build/guest0_psa/zephyr/zephyr.bin" From bb2f6a0e5353700f119a922a71e1bad2567880bb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 11:26:47 -0700 Subject: [PATCH 7/9] Use plain section references and absolute source links in the manual --- docs/API-Reference.md | 4 ++-- docs/FF-M-Compatibility.md | 8 ++++---- docs/PSA-Compatibility.md | 12 ++++++------ docs/Security-Model.md | 6 +++--- docs/Services.md | 4 ++-- mkdocs.yml | 1 + 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 67c9fb88..15bff78f 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -412,9 +412,9 @@ smaller than the object. UID zero is invalid. The ITS path enforces `PSA_STORAGE_FLAG_WRITE_ONCE` when the caller sets it, including during `PSA_ROT_PROVISIONING`. Objects created without the flag can -be updated or removed. PSA Secure Storage 1.0 §3.2 requires ITS not to enforce +be updated or removed. PSA Secure Storage 1.0 section 3.2 requires ITS not to enforce the flag in the provisioning lifecycle, so this is a known lifecycle deviation. -Protected Storage also enforces caller-selected `WRITE_ONCE`; §3.2's lifecycle +Protected Storage also enforces caller-selected `WRITE_ONCE`; section 3.2's lifecycle exception applies to ITS. ## Protected Storage diff --git a/docs/FF-M-Compatibility.md b/docs/FF-M-Compatibility.md index ae488ffd..11311289 100644 --- a/docs/FF-M-Compatibility.md +++ b/docs/FF-M-Compatibility.md @@ -14,10 +14,10 @@ page covers Crypto, Storage, Attestation, and Firmware Update APIs separately. | Contract | Normative section | wolfTrust scope | | --- | --- | --- | -| Isolation and protection domains | [FF-M §§3.1.1–3.1.6](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=21) | STM32H563 TrustZone, Secure MPU, and Global TrustZone Controller (GTZC) enforcement, meeting isolation level 3; see [Security Model](Security-Model.md#ff-m-isolation-level-3) | -| Secure Partition identity, manifest, and execution | [FF-M §§3.2.1–3.2.4](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=26) and [§4.1](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=49) | Generated wolfTrust manifest policy, scheduled Secure Partition entry, and lifecycle handling | -| IPC, handles, and copied vectors | [FF-M §§3.3.1–3.3.5](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=31) | Connection-based services with fixed buffer and vector limits | -| Client and Secure Partition APIs | [FF-M §4.4](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=64) and [§4.5](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=70) | Implemented functions and limitations are listed in the register below | +| Isolation and protection domains | [FF-M sections 3.1.1 to 3.1.6](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=21) | STM32H563 TrustZone, Secure MPU, and Global TrustZone Controller (GTZC) enforcement, meeting isolation level 3; see [Security Model](Security-Model.md#ff-m-isolation-level-3) | +| Secure Partition identity, manifest, and execution | [FF-M sections 3.2.1 to 3.2.4](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=26) and [section 4.1](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=49) | Generated wolfTrust manifest policy, scheduled Secure Partition entry, and lifecycle handling | +| IPC, handles, and copied vectors | [FF-M sections 3.3.1 to 3.3.5](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=31) | Connection-based services with fixed buffer and vector limits | +| Client and Secure Partition APIs | [FF-M section 4.4](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=64) and [section 4.5](https://documentation-service.arm.com/static/64a2ed35df6cd61d528c4132#page=70) | Implemented functions and limitations are listed in the register below | ## Implemented framework interfaces diff --git a/docs/PSA-Compatibility.md b/docs/PSA-Compatibility.md index cdf86fbe..cc0117f6 100644 --- a/docs/PSA-Compatibility.md +++ b/docs/PSA-Compatibility.md @@ -14,10 +14,10 @@ The Secure Partition Manager and IPC contract are documented separately in | Contract | Normative section | wolfTrust scope | | --- | --- | --- | -| PSA Crypto | [Crypto API 1.4, §6.1.1](https://arm-software.github.io/psa-api/crypto/1.4/overview/implementation.html) | wolfPSA API; enabled algorithms depend on the selected guest and crypto-engine configuration | -| Internal Trusted Storage and Protected Storage | [Secure Storage API 1.0, §§5.3–5.4](https://arm-software.github.io/psa-api/storage/1.0/api/api.html) | Core operations; storage deviations are listed below | -| Initial Attestation | [Attestation API 1.0, §3](https://arm-software.github.io/psa-api/attestation/1.0/overview/report.html) and [§4](https://arm-software.github.io/psa-api/attestation/1.0/api/api.html) | Token generation and token-size query operations with documented header, status, and token-profile deviations | -| Firmware Update | [Firmware Update API 1.0, §4](https://arm-software.github.io/psa-api/fwu/1.0/overview/programming-model.html) and [§5](https://arm-software.github.io/psa-api/fwu/1.0/api/api.html) | Single-component staging and authenticated reboot with documented limits | +| PSA Crypto | [Crypto API 1.4, section 6.1.1](https://arm-software.github.io/psa-api/crypto/1.4/overview/implementation.html) | wolfPSA API; enabled algorithms depend on the selected guest and crypto-engine configuration | +| Internal Trusted Storage and Protected Storage | [Secure Storage API 1.0, sections 5.3 to 5.4](https://arm-software.github.io/psa-api/storage/1.0/api/api.html) | Core operations; storage deviations are listed below | +| Initial Attestation | [Attestation API 1.0, section 3](https://arm-software.github.io/psa-api/attestation/1.0/overview/report.html) and [section 4](https://arm-software.github.io/psa-api/attestation/1.0/api/api.html) | Token generation and token-size query operations with documented header, status, and token-profile deviations | +| Firmware Update | [Firmware Update API 1.0, section 4](https://arm-software.github.io/psa-api/fwu/1.0/overview/programming-model.html) and [section 5](https://arm-software.github.io/psa-api/fwu/1.0/api/api.html) | Single-component staging and authenticated reboot with documented limits | ## Implemented service APIs @@ -36,10 +36,10 @@ The Secure Partition Manager and IPC contract are documented separately in | PSA Crypto mechanisms are build-selected. | Implementation-profile behavior | The [PSA Crypto implementation profile](https://arm-software.github.io/psa-api/crypto/1.4/overview/implementation.html) may select an API and algorithm subset. 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 requirement. | -| Internal Trusted Storage enforces caller-selected `PSA_STORAGE_FLAG_WRITE_ONCE` during provisioning. | Known lifecycle deviation | Ordinary objects can be updated or removed. The ITS request path does not consult lifecycle state, so an object created with the flag cannot be changed during `PSA_ROT_PROVISIONING`, contrary to [PSA Secure Storage 1.0 §3.2](https://arm-software.github.io/psa-api/storage/1.0/overview/requirements.html). The §3.2 lifecycle exception is specific to ITS. | +| Internal Trusted Storage enforces caller-selected `PSA_STORAGE_FLAG_WRITE_ONCE` during provisioning. | Known lifecycle deviation | Ordinary objects can be updated or removed. The ITS request path does not consult lifecycle state, so an object created with the flag cannot be changed during `PSA_ROT_PROVISIONING`, contrary to [PSA Secure Storage 1.0 section 3.2](https://arm-software.github.io/psa-api/storage/1.0/overview/requirements.html). The section 3.2 lifecycle exception is specific to ITS. | | 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`. | -| The attestation token advertises `tag:psacertified.org,2023:psa#tfm` but does not implement that profile's claim semantics. | Known token-profile deviation | The boot seed is deterministic across equivalent boots; software-component measurement type and description values are reversed; signer ID hashes the literal name `wolfBoot` rather than identifying the signing key; and implementation ID hashes a software label rather than identifying the immutable PSA RoT hardware assembly. A distinct derived profile identifier should be used until these claims conform to [RFC 9783 §5.2](https://www.rfc-editor.org/rfc/rfc9783.html#section-5.2). | +| The attestation token advertises `tag:psacertified.org,2023:psa#tfm` but does not implement that profile's claim semantics. | Known token-profile deviation | The boot seed is deterministic across equivalent boots; software-component measurement type and description values are reversed; signer ID hashes the literal name `wolfBoot` rather than identifying the signing key; and implementation ID hashes a software label rather than identifying the immutable PSA RoT hardware assembly. A distinct derived profile identifier should be used until these claims conform to [RFC 9783 section 5.2](https://www.rfc-editor.org/rfc/rfc9783.html#section-5.2). | | Firmware Update has no persistent trial-accept flow. | Scoped | Installation commits only after wolfBoot authenticates the swapped image at reboot; `psa_fwu_accept()` returns `PSA_ERROR_NOT_SUPPORTED`. | | wolfTrust's Firmware Update adapter accepts a detached manifest only as a 32-bit version word. | Scoped integration | Passing `NULL, 0` instead binds the version from the staged wolfBoot header. Other manifest encodings require an adapter. | | Firmware Update reports unknown component IDs as `PSA_ERROR_INVALID_ARGUMENT`. | Known API deviation | PSA Firmware Update 1.0 specifies `PSA_ERROR_DOES_NOT_EXIST` for unknown component IDs. Unaligned block sizes are padded to the backend write alignment. | diff --git a/docs/Security-Model.md b/docs/Security-Model.md index 3df67be9..656af57f 100644 --- a/docs/Security-Model.md +++ b/docs/Security-Model.md @@ -44,9 +44,9 @@ their text is in the specification. ### Evidence - The Arm psa-arch-tests FF-M IPC suite, pinned in - [`tests/upstream/psa-arch-tests.rev`](../tests/upstream/psa-arch-tests.rev), + [`tests/upstream/psa-arch-tests.rev`](https://github.com/wolfSSL/wolfTrust/blob/main/tests/upstream/psa-arch-tests.rev), passes 85 tests with 4 heap tests skipped, recorded test by test in - [`tests/target/ffm_ipc_results.txt`](../tests/target/ffm_ipc_results.txt). + [`tests/target/ffm_ipc_results.txt`](https://github.com/wolfSSL/wolfTrust/blob/main/tests/target/ffm_ipc_results.txt). - The isolation negatives in [Testing](Testing.md#isolation-scenarios) run on both engines under M33MU. The STM32H563 hardware suite runs the positive and conformance scenarios and the negatives the emulator cannot show. @@ -158,7 +158,7 @@ caller. Mechanical checks: -- [`tools/secure_owners.txt`](../tools/secure_owners.txt) gives every linked +- [`tools/secure_owners.txt`](https://github.com/wolfSSL/wolfTrust/blob/main/tools/secure_owners.txt) gives every linked object one owner. The post-link check fails the build if writable state lands outside its owner's region, an object has no owner, a shared object holds writable state, or a production image carries conformance code. diff --git a/docs/Services.md b/docs/Services.md index 8cc8f7fd..247905e8 100644 --- a/docs/Services.md +++ b/docs/Services.md @@ -100,9 +100,9 @@ storage, but ITS does not add the Protected Storage sealing flag. The ITS path enforces `PSA_STORAGE_FLAG_WRITE_ONCE` on objects created with that flag, including during `PSA_ROT_PROVISIONING`. Objects created without it -can be updated or removed. This differs from PSA Secure Storage 1.0 §3.2, +can be updated or removed. This differs from PSA Secure Storage 1.0 section 3.2, which requires ITS not to enforce the flag in that lifecycle state. Protected -Storage also enforces caller-selected `WRITE_ONCE`; §3.2's lifecycle exception +Storage also enforces caller-selected `WRITE_ONCE`; section 3.2's lifecycle exception applies to ITS. ## Protected Storage diff --git a/mkdocs.yml b/mkdocs.yml index 01f5632f..bcf4bf5e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ nav: - Ports: - Supported targets: Targets.md - STM32H563 board guide: STM32H5-Guide.md + - MIMXRT700 board guide: MIMXRT700-Guide.md - Use wolfTrust: - Services: Services.md - API Reference: API-Reference.md From 281eb86d8f8f65630ac7a388f5527d51f4de3881 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 11:35:54 -0700 Subject: [PATCH 8/9] Point the standards page at the STM32H563 isolation level 3 claim --- docs/Standards.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Standards.md b/docs/Standards.md index ac6e66ae..af3132d5 100644 --- a/docs/Standards.md +++ b/docs/Standards.md @@ -14,9 +14,9 @@ certification is claimed. | [PSA Initial Attestation API 1.0](https://arm-software.github.io/psa-api/attestation/1.0/) and [RFC 9783](https://www.rfc-editor.org/rfc/rfc9783.html) | [PSA Compatibility](PSA-Compatibility.md) records the token API subset and token-profile deviations, including the currently advertised `tag:psacertified.org,2023:psa#tfm` profile. | | [PSA Firmware Update API 1.0](https://arm-software.github.io/psa-api/fwu/1.0/) | [PSA Compatibility](PSA-Compatibility.md) records the single-component update flow and unsupported trial-accept behavior. | -The manifest's requested isolation profile is not a certificate of FF-M -isolation-level-3 conformance. [Security Model](Security-Model.md) describes -the enforced boundaries; [Threat Model](Threat-Model.md) describes assumptions +On the STM32H563, wolfTrust meets FF-M isolation level 3; +[Security Model](Security-Model.md#ff-m-isolation-level-3) maps each isolation +rule and lists the deviations; [Threat Model](Threat-Model.md) describes assumptions and residual risks. [Testing](Testing.md) separates host, emulator, and physical-board evidence. @@ -24,8 +24,8 @@ physical-board evidence. - [FF-M framework differences](FF-M-Compatibility.md#framework-differences) lists the IPC, manifest, lifecycle, and integration differences. -- [FF-M isolation-profile interpretation](FF-M-Compatibility.md#isolation-profile-interpretation) - separates requested manifest policy from enforced isolation. +- [FF-M isolation level](FF-M-Compatibility.md#isolation-level) states the + implemented level and points to the rule mapping. - [PSA service API differences](PSA-Compatibility.md#service-api-differences) lists storage, attestation, and firmware-update deviations. - [Validation and claim boundaries](FF-M-Compatibility.md#validation-and-claim-boundary) From 031220f0eadd6221c350b8ec09be45b05066b3db Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 11:42:40 -0700 Subject: [PATCH 9/9] Point the MIMXRT700 guide at the Security Model for guest privilege --- docs/MIMXRT700-Guide.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/MIMXRT700-Guide.md b/docs/MIMXRT700-Guide.md index e507e8c3..0c1857ec 100644 --- a/docs/MIMXRT700-Guide.md +++ b/docs/MIMXRT700-Guide.md @@ -149,10 +149,9 @@ on), so the CPU's own attribution is the barrier: with the peer window Secure, a guest's store into it faults even after the guest disables its own Non-secure MPU, and the monitor contains the fault. `ahbscneg` shows exactly that on the EVK and under M33MU. -As on the STM32H563 (see [TF-M Compatibility](TF-M-Compatibility.md)), the -manifest declares the guests unprivileged but the runtime launches them with -`CONTROL_NS.nPRIV` clear. A guest's Non-secure MPU is therefore scheduling -policy, not a boundary; the SAU window is the boundary. Fencing other bus masters (the sense M33, the DSPs, the NPU, and DMA) per +As on the STM32H563 (see [Security Model](Security-Model.md)), the guests run +privileged, with `CONTROL_NS.nPRIV` clear, and the manifest declares them so. A +guest's Non-secure MPU is therefore scheduling policy, not a boundary; the SAU window is the boundary. Fencing other bus masters (the sense M33, the DSPs, the NPU, and DMA) per master is not implemented. ## Silicon constraints for this port