Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/build-and-test-clientonly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,18 @@ jobs:
config-hash: ${{ matrix.transport }}
read-only: ${{ github.event_name == 'pull_request' }}

# Build example server
# Build example server. The SHE client tests use the test-only
# preprogram/destroy key-management messages, so the CI server must be
# built with WOLFHSM_CFG_SHE_ENABLE_TEST_KEY_MGMT (never enable this in
# production builds).
- name: Build POSIX server
run: |
cd examples/posix/wh_posix_server
SHE_TEST_CFLAGS="-Wextra -DWOLFHSM_CFG_SHE_ENABLE_TEST_KEY_MGMT"
if [ "${{ matrix.transport }}" = "tcp" ]; then
make -j SHE=1 WOLFSSL_DIR=../../../wolfssl
make -j SHE=1 WOLFSSL_DIR=../../../wolfssl CFLAGS_EXTRA="$SHE_TEST_CFLAGS"
else
make -j TLS=1 SHE=1 WOLFSSL_DIR=../../../wolfssl
make -j TLS=1 SHE=1 WOLFSSL_DIR=../../../wolfssl CFLAGS_EXTRA="$SHE_TEST_CFLAGS"
fi

# Start the server in the background
Expand Down Expand Up @@ -126,16 +130,18 @@ jobs:
make -j CLIENT_ONLY=1 TLS=1 SHE=1 AUTH=1 WOLFSSL_DIR=../wolfssl && make run
fi

# Rebuild the server with AUTH support and restart
# Rebuild the server with AUTH support and restart. Keep the SHE test
# key-management define so the SHE client tests still work.
- name: Rebuild and restart POSIX server for AUTH
run: |
kill $SERVER_PID || true
cd examples/posix/wh_posix_server
make clean
SHE_TEST_CFLAGS="-Wextra -DWOLFHSM_CFG_SHE_ENABLE_TEST_KEY_MGMT"
if [ "${{ matrix.transport }}" = "tcp" ]; then
make -j SHE=1 AUTH=1 WOLFSSL_DIR=../../../wolfssl
make -j SHE=1 AUTH=1 WOLFSSL_DIR=../../../wolfssl CFLAGS_EXTRA="$SHE_TEST_CFLAGS"
else
make -j TLS=1 SHE=1 AUTH=1 WOLFSSL_DIR=../../../wolfssl
make -j TLS=1 SHE=1 AUTH=1 WOLFSSL_DIR=../../../wolfssl CFLAGS_EXTRA="$SHE_TEST_CFLAGS"
fi
rm -f *.bin || true
./Build/wh_posix_server.elf --type ${{ matrix.transport }} &
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/build-and-test-refactor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ jobs:
if: matrix.group == 'wolfcrypt'
run: cd test-refactor/posix && make clean && make -j SHE_GLOBAL=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Build and test with the global keys feature disabled (exercises the NVM
# id translation paths, including the DMA add path, with
# WOLFHSM_CFG_GLOBAL_KEYS undefined)
- name: Build and test refactor ASAN NOGLOBALKEYS DMA
if: matrix.group == 'wolfcrypt'
run: cd test-refactor/posix && make clean && make -j NOGLOBALKEYS=1 DMA=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Build and test with the legacy flat client NVM id space
# (WOLFHSM_CFG_LEGACY_CLIENT_NVM)
- name: Build and test refactor ASAN LEGACYNVM DMA
if: matrix.group == 'wolfcrypt'
run: cd test-refactor/posix && make clean && make -j LEGACYNVM=1 DMA=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Build and test with DEBUG=1
- name: Build and test refactor with DEBUG
if: matrix.group == 'base'
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/build-and-test-stress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ jobs:

if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
timeout-minutes: 10
# The stress suite runs every phase in three namespace variants
# (global/local/mixed), so give it headroom beyond the old 10 minutes
timeout-minutes: 20

steps:
- uses: actions/checkout@v4
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ jobs:
if: matrix.group == 'wolfcrypt'
run: cd test && make clean && make -j SHE_GLOBAL=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Build and test with the global keys feature disabled (exercises the NVM
# id translation paths, including the DMA add path, with
# WOLFHSM_CFG_GLOBAL_KEYS undefined)
- name: Build and test ASAN NOGLOBALKEYS DMA
if: matrix.group == 'wolfcrypt'
run: cd test && make clean && make -j NOGLOBALKEYS=1 DMA=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Build and test with the legacy flat client NVM id space
# (WOLFHSM_CFG_LEGACY_CLIENT_NVM)
- name: Build and test ASAN LEGACYNVM DMA
if: matrix.group == 'wolfcrypt'
run: cd test && make clean && make -j LEGACYNVM=1 DMA=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Build and test with per-client crypto affinity enabled (runs the crypto
# affinity unit test, gated behind WOLFHSM_CFG_CRYPTO_AFFINITY)
- name: Build and test CRYPTO_AFFINITY ASAN
Expand Down
2 changes: 1 addition & 1 deletion docs/draft/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ cfg.nvm = my_nvm_context; /* whNvmContext* -> persistent; NULL -> RAM-only */

Split object layout: sensitive credential material is deliberately kept out of the shared user index so that a single object read cannot leak PIN hashes or cert material.

- User index: one object at `WH_NVM_ID_AUTH_USER_INDEX` holding the serialized `whAuthUser` records (identity and permissions, with `is_active` cleared). It carries no credential material. `method` and `credentials_len` are not stored here; they live in the per-user credential object metadata and are rebuilt at load. Marked `SENSITIVE | NONEXPORTABLE | NONMODIFIABLE` so clients cannot read, overwrite, or destroy it through the NVM message group. Written with the unchecked NVM add so the backend itself is not blocked by those policy flags.
- User index: one object at `WH_NVM_ID_AUTH_USER_INDEX` holding the serialized `whAuthUser` records (identity and permissions, with `is_active` cleared). It carries no credential material. `method` and `credentials_len` are not stored here; they live in the per-user credential object metadata and are rebuilt at load. Marked `SENSITIVE | NONEXPORTABLE | NONMODIFIABLE` so clients cannot read, overwrite, or destroy it through the NVM message group. On default builds the reserved range is additionally outside the client NVM API's reachable id space entirely: client-supplied ids are translated into the per-client `WH_KEYTYPE_NVM` namespace, which cannot name these ids at all. Only with `WOLFHSM_CFG_LEGACY_CLIENT_NVM` (flat id space) do the policy flags become the sole protection. Written with the unchecked NVM add so the backend itself is not blocked by those policy flags.
- Per-user credentials: each user's credential blob is its own object at `WH_NVM_ID_AUTH_CRED_BASE + (user_id - 1)`. These are read into a static, force-zeroed scratch buffer only for the duration of an authentication or credential change, never held resident.
- The reserved id range therefore spans `WH_NVM_ID_AUTH_USER_INDEX` through `WH_NVM_ID_AUTH_CRED_BASE + (WH_AUTH_BASE_MAX_USERS - 1)`.

Expand Down
36 changes: 31 additions & 5 deletions docs/src/5-Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This chapter provides a detailed overview of the high level features that wolfHS
- [Non-Volatile Memory (NVM)](#non-volatile-memory-nvm)
- [High Level NVM Interface](#high-level-nvm-interface)
- [Object Metadata and Access Attributes](#object-metadata-and-access-attributes)
- [Client NVM Access and Per-Client Namespaces](#client-nvm-access-and-per-client-namespaces)
- [NVM Backends](#nvm-backends)
- [Flash Abstraction](#flash-abstraction)
- [Optional NVM Backing](#optional-nvm-backing)
Expand Down Expand Up @@ -178,7 +179,7 @@ The NVM library presents non-volatile storage as a collection of opaque, variabl
- A fixed-size **metadata** record describing the object (see [Object Metadata and Access Attributes](#object-metadata-and-access-attributes))
- A variable-length **payload** of arbitrary bytes

Applications and higher-level subsystems do not address NVM by byte offset; they create, read, enumerate, and destroy whole objects through the `wh_Nvm_*` API. This object orientation is what allows the keystore, certificate manager, and counter subsystems to share a single backing store without colliding: each subsystem owns a range of identifiers and a set of metadata flags, and the underlying NVM layer is unaware of what the objects mean.
Applications and higher-level subsystems do not address NVM by byte offset; they create, read, enumerate, and destroy whole objects through the `wh_Nvm_*` API. This object orientation is what allows the keystore, certificate manager, and counter subsystems to share a single backing store without colliding: each subsystem owns a range of identifiers and a set of metadata flags, and the underlying NVM layer is unaware of what the objects mean. Remote clients get the same object model through the `wh_Client_Nvm*` request API, with one additional layer on top: the ids they supply are namespaced per client, as described in [Client NVM Access and Per-Client Namespaces](#client-nvm-access-and-per-client-namespaces).

The core operations exposed by the interface are:

Expand Down Expand Up @@ -220,6 +221,28 @@ The NVM library exposes both a raw and a policy-checked variant of the mutating

The access field is used to express coarser-grained permissions (owner / other / user buckets, with read/write/exec/special bits) that higher layers may consult, and is the primary filter used by `wh_Nvm_List()` when enumerating objects.

### Client NVM Access and Per-Client Namespaces

Everything above describes the server's view of the object store. Clients reach it remotely through the `wh_Client_Nvm*` request API — `wh_Client_NvmAddObject`, `wh_Client_NvmRead`, `wh_Client_NvmList`, `wh_Client_NvmGetMetadata`, `wh_Client_NvmDestroyObjects`, `wh_Client_NvmGetAvailable`, and the [DMA variants](#dma-support) of add and read — which the server executes against the same `wh_Nvm_*` interface. The id a client supplies, however, is not the raw internal `whNvmId`: exactly as with [key ids](#key-cache-key-ids-and-nvm-backing-store), the server translates every client-supplied NVM id at the request boundary, giving each client a private namespace of objects.

A client-facing NVM id uses the same encoding as a client-facing `whKeyId`:

- **Bits 0–7**: the numeric object id, `1` through `255`. Zero is reserved as the erased sentinel and is rejected by `wh_Client_NvmAddObject` — unlike a key cache request, an NVM add never auto-assigns an id.
- **Bit 8** (`WH_KEYID_CLIENT_GLOBAL_FLAG`): selects the **shared global namespace** instead of the calling client's private one. This is the same flag-and-translation mechanism used by [global keys](#global-keys), and like global keys it is honored when `WOLFHSM_CFG_GLOBAL_KEYS` is defined. Without that define there is no global namespace: `wh_Client_NvmAddObject` rejects ids carrying the flag, and the remaining verbs (including `wh_Client_NvmList`) ignore it and operate on the caller's own namespace.
- **Bits 9–10** (the wrapped and hardware-only key flags): not meaningful for NVM objects; `wh_Client_NvmAddObject` rejects ids carrying either flag.

On each request the server expands the client's id into the full internal form with TYPE = `WH_KEYTYPE_NVM` and USER = the connection's client id (or `0` for the global namespace), and collapses it back in responses. The isolation consequences mirror the keystore's:

- Every client sees the same `[1, 255]` id range, but two clients that both write "object 5" create two distinct objects; neither can read, overwrite, enumerate, or destroy the other's.
- Because the translation always stamps TYPE = `WH_KEYTYPE_NVM` and the caller's own USER, the client NVM API cannot name keys, counters, SHE slots, another client's objects, or server-internal NVM-resident stores (such as the authentication manager's user database) at all.
- When `WOLFHSM_CFG_GLOBAL_KEYS` is defined, objects in the global namespace are visible to every client that sets the GLOBAL flag. Factory images produced by the [NVM provisioning tool](6-Utilities.md#nvm-provisioning-tool) place plain `obj` entries with ids up to 255 in exactly this namespace, so provisioned objects are reachable by all clients out of the box. On builds without the define, these USER=0 objects are not reachable through the client NVM API at all.

When `WOLFHSM_CFG_GLOBAL_KEYS` is defined, `wh_Client_NvmList` treats the GLOBAL flag on its `startId` argument as a namespace selector: pass `0` to enumerate the calling client's own objects from the beginning, or `WH_KEYID_CLIENT_GLOBAL_FLAG` to enumerate the global namespace. The returned count covers only the selected namespace, and each returned id carries the appropriate flag, so it can be passed back unchanged as the next `startId` to continue the walk. Without the define, the flag on `startId` is ignored and the walk always covers the caller's own namespace.

Access- and flags-based filtering and the policy-checked NVM variants apply unchanged after translation, and server-local code with direct access to the `wh_Nvm_*` API is unaffected — it continues to address objects by full internal id.

For integrations that depend on the historical behavior, defining `WOLFHSM_CFG_LEGACY_CLIENT_NVM` disables the translation entirely and restores the flat 16-bit id space shared by all clients — along with the cross-client reachability that comes with it. See [Configuration](9-Configuration.md#nvm-storage).

### NVM Backends

The `wh_Nvm_*` API is implemented against a backend callback table (`whNvmCb`) that abstracts the details of how objects are actually laid out on storage. The core library does not depend on any particular backend — selecting a backend is part of server configuration, and ports or applications can supply their own implementations against the same interface. wolfHSM ships with two reference backends, both built on top of the [flash abstraction](#flash-abstraction):
Expand Down Expand Up @@ -301,7 +324,7 @@ Keys are named by a 16-bit identifier (`whKeyId`), which has two forms — a sim

The server-side `whKeyId` packs three fields into its 16 bits:

- **TYPE** (top 4 bits): the kind of object — `WH_KEYTYPE_CRYPTO` for ordinary crypto keys, `WH_KEYTYPE_SHE` for AUTOSAR SHE keys, `WH_KEYTYPE_COUNTER` for monotonic counters, `WH_KEYTYPE_WRAPPED` for wrapped-key metadata, and `WH_KEYTYPE_NVM` for non-key NVM objects that share the same id space.
- **TYPE** (top 4 bits): the kind of object — `WH_KEYTYPE_CRYPTO` for ordinary crypto keys, `WH_KEYTYPE_SHE` for AUTOSAR SHE keys, `WH_KEYTYPE_COUNTER` for monotonic counters, `WH_KEYTYPE_WRAPPED` for wrapped-key metadata, and `WH_KEYTYPE_NVM` for non-key NVM objects that share the same id space (see [Client NVM Access and Per-Client Namespaces](#client-nvm-access-and-per-client-namespaces)).
- **USER** (middle 4 bits): the owning client. Value `0` is reserved for the global-key namespace when `WOLFHSM_CFG_GLOBAL_KEYS` is enabled.
- **ID** (low 8 bits): the number the client chose.

Expand Down Expand Up @@ -676,10 +699,13 @@ The SHE client API is declared in `wolfhsm/wh_client_she.h` and maps one-to-one
- **Status**: `wh_Client_SheGetStatus` (`CMD_GET_STATUS`) — reads the SHE status register (SREG)
- **Module identity**: `wh_Client_SheGetId` (`CMD_GET_ID`) — returns the ECU UID, the status register, and a CMAC over the caller's challenge, UID, and status register computed under the `MASTER_ECU_KEY`, letting a party that holds that key verify the module's identity. If the `MASTER_ECU_KEY` slot is empty the MAC is computed with an all-zero key.

In addition to the spec commands, wolfHSM exposes two non-standard helpers that fill gaps left by the spec's assumption of dedicated hardware:
In addition to the spec commands, wolfHSM exposes non-standard helpers that fill gaps left by the spec's assumption of dedicated hardware:

- `wh_Client_SheSetUid`: explicitly programs the 15-byte ECU UID that the key update protocol binds against. The AUTOSAR spec assumes this value is hardware-fused; wolfHSM needs a software path to install it, and rejects most SHE operations until it has been set. Where the UID really does live in hardware or in NVM, the server can be pointed at it instead with [UID storage callbacks](#she-uid-storage), in which case `CMD_SET_UID` returns `WH_SHE_ERC_WRITE_PROTECTED` on a read-only store.
- `wh_Client_ShePreProgramKey`: writes a key directly into a SHE NVM slot, bypassing the encrypted M1–M5 protocol. This exists to support initial provisioning on a blank device — once a `MASTER_ECU_KEY` exists, all subsequent updates can go through the spec-compliant protocol.
- `wh_Client_ShePreProgramKey`: writes a key, its protection flags, and its initial key counter directly into a SHE NVM slot over a dedicated SHE message, bypassing the encrypted M1–M5 protocol. This exists to support initial provisioning on a blank device — once a `MASTER_ECU_KEY` exists, all subsequent updates can go through the spec-compliant protocol.
- `wh_Client_SheDestroyKey`: removes a SHE key slot from the calling client's NVM namespace, a capability the spec omits because it treats key slots as fixed hardware.

Because `wh_Client_ShePreProgramKey` and `wh_Client_SheDestroyKey` bypass the SHE key-update and authorization protocols, both are compiled only when `WOLFHSM_CFG_SHE_ENABLE_TEST_KEY_MGMT` is defined (see [Configuration](9-Configuration.md#cryptography-features)) and are intended for provisioning and test builds, not production firmware.

All SHE commands return one of the spec's `WH_SHE_ERC_*` error codes (`SEQUENCE_ERROR`, `KEY_NOT_AVAILABLE`, `WRITE_PROTECTED`, `KEY_UPDATE_ERROR`, etc.) alongside the wolfHSM transport return code, so applications can distinguish protocol-level failures from communication failures.

Expand Down Expand Up @@ -796,7 +822,7 @@ The SHE extension is built on top of the same infrastructure as every other wolf
- **Wrapped keys**: SHE keys interoperate with the [wrapped keys](#wrapped-keys) feature by explicit type rather than by flag (the SHE keyId namespace does not interpret the wrapped flag): the client passes `WH_KEYTYPE_SHE` to *wrap-export* to receive a slot's key wrapped under a [trusted KEK](#trusted-keks), and presents the blob to *unwrap-and-cache* to prime a slot directly in the key cache — the provisioning path for servers with [no NVM](#optional-nvm-backing), guarded by the slot's counter rollback check. *Unwrap-and-export* refuses SHE blobs, so a wrapped SHE key can re-enter the keystore but its plaintext is never returned to a client.
- **Global keys**: off by default — every SHE keyId carries the connection's client ID in the USER field, giving each client its own set of slots. Defining `WOLFHSM_CFG_SHE_GLOBAL_KEYS` instead places all SHE slots in the [global](#global-keys) (`WH_KEYUSER_GLOBAL`) namespace so every client shares one SHE device view; see [Global SHE Keys](#global-she-keys). Without that option, applications that need to share a key across clients must provision it into each client's SHE namespace separately.

A typical automotive deployment uses the SHE extension end-to-end: the bootloader and `BOOT_MAC` are programmed into NVM at production using `wh_Client_ShePreProgramKey`, the device's UID is set on first boot with `wh_Client_SheSetUid`, secure boot is run on every reset via `wh_Client_SheSecureBoot`, in-field key updates flow through the encrypted `CMD_LOAD_KEY` protocol, and CAN message authentication uses `wh_Client_SheGenerateMac` / `wh_Client_SheVerifyMac` against pre-provisioned user-slot keys.
A typical automotive deployment uses the SHE extension end-to-end: the bootloader and `BOOT_MAC` are programmed into NVM at production via whnvmtool or via `wh_Client_ShePreProgramKey`, the device's UID is set on first boot with `wh_Client_SheSetUid`, secure boot is run on every reset via `wh_Client_SheSecureBoot`, in-field key updates flow through the encrypted `CMD_LOAD_KEY` protocol, and CAN message authentication uses `wh_Client_SheGenerateMac` / `wh_Client_SheVerifyMac` against pre-provisioned user-slot keys.

## Non-Volatile Monotonic Counters

Expand Down
Loading
Loading