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
8 changes: 8 additions & 0 deletions .github/workflows/build-and-test-refactor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ jobs:
- name: Build and test refactor CRYPTO_AFFINITY DMA ASAN
run: cd test-refactor/posix && make clean && make -j CRYPTO_AFFINITY=1 DMA=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Build and test with global SHE keys (all SHE slots shared across clients)
- name: Build and test refactor ASAN SHE_GLOBAL
run: cd test-refactor/posix && make clean && make -j SHE_GLOBAL=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Build and test with DEBUG=1
- name: Build and test refactor with DEBUG
run: cd test-refactor/posix && make clean && make -j DEBUG=1 WOLFSSL_DIR=../../wolfssl && make run
Expand All @@ -94,6 +98,10 @@ jobs:
- name: Build and test refactor with THREADSAFE and everything
run: cd test-refactor/posix && make clean && make -j THREADSAFE=1 DMA=1 SHE=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Same, with global SHE keys
- name: Build and test refactor with THREADSAFE and everything and SHE_GLOBAL
run: cd test-refactor/posix && make clean && make -j THREADSAFE=1 DMA=1 SHE_GLOBAL=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run

# Build and test in multithreaded mode with everything enabled and wolfCrypt tests
- name: Build and test refactor with THREADSAFE and TESTWOLFCRYPT and everything
run: cd test-refactor/posix && make clean && make -j THREADSAFE=1 TESTWOLFCRYPT=1 DMA=1 SHE=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ jobs:
- name: Build and test ASAN SHE
run: cd test && make clean && make -j SHE=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Build and test with global SHE keys (all SHE slots shared across clients)
- name: Build and test ASAN SHE_GLOBAL
run: cd test && make clean && make -j SHE_GLOBAL=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 All @@ -94,6 +98,10 @@ jobs:
- name: Build and test with THREADSAFE and everything
run: cd test && make clean && make -j THREADSAFE=1 DMA=1 SHE=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Same, with global SHE keys
- name: Build and test with THREADSAFE and everything and SHE_GLOBAL
run: cd test && make clean && make -j THREADSAFE=1 DMA=1 SHE_GLOBAL=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run

# Build and test in multithreaded mode with everything enabled and wolfCrypt tests
- name: Build and test with THREADSAFE and TESTWOLFCRYPT and everything
run: cd test && make clean && make -j THREADSAFE=1 TESTWOLFCRYPT=1 DMA=1 SHE=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run
Expand Down
25 changes: 24 additions & 1 deletion docs/src/5-Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This chapter provides a detailed overview of the high level features that wolfHS
- [AUTOSAR SHE Subsystem](#autosar-she-subsystem)
- [Client API and Command Set](#client-api-and-command-set)
- [SHE Key Slots and the wolfHSM Keystore](#she-key-slots-and-the-wolfhsm-keystore)
- [Global SHE Keys](#global-she-keys)
- [Encrypted Key Update Protocol (M1–M5)](#encrypted-key-update-protocol-m1m5)
- [Secure Boot](#secure-boot)
- [Deterministic PRNG](#deterministic-prng)
Expand Down Expand Up @@ -347,6 +348,8 @@ Global keys interact with the cache and NVM tiers in the same way as local keys,

> **Security note**: Because a global key is reachable by every client connected to the server, the security boundary it provides is the server itself, not any particular client. Global keys should be reserved for material that is genuinely shared across the trust domains of the connected clients — typically vendor-provisioned roots and shared symmetric keys for inter-client communication — and should not be used as a workaround for per-client key management. Per-key usage flags (see [Key Usage Policies](#key-usage-policies)) apply to global keys exactly as they do to local keys, and should be used to constrain how a shared key may be used regardless of which client invokes the operation.

The [AUTOSAR SHE subsystem](#autosar-she-subsystem) can be layered onto this feature with `WOLFHSM_CFG_SHE_GLOBAL_KEYS`, which places all SHE slots in the global namespace so every client shares one SHE device; see [Global SHE Keys](#global-she-keys).

### Wrapped Keys

A key that lives entirely inside the server is protected by its trust boundary: only the server can read its bytes, and policy is enforced before every use. Some workflows still need to move key material outside that boundary — to back keys up to off-device storage, to transport a key between systems during provisioning, or even to support wolfHSM on an HSM platform without dedicated NVM. wolfHSM's **wrapped keys** feature (`WOLFHSM_CFG_KEYWRAP`) does this safely, with the server mediating every step.
Expand Down Expand Up @@ -695,6 +698,26 @@ The SHE spec also requires every key to carry a 28-bit monotonic update counter

`RAM_KEY` is the one exception to NVM-backed storage. The spec defines it as volatile, so the server caches the loaded key in its [key cache](#key-cache-key-ids-and-nvm-backing-store) but never calls into the NVM layer for it; eviction or reset clears it. All other slots, including `PRNG_SEED`, persist.

### Global SHE Keys

By default every SHE keyId carries the connecting client's ID in its USER field, so each client gets its own private set of sixteen SHE slots. That is convenient when clients are mutually distrusting, but it does not match the AUTOSAR model, where SHE is a single physical device with one fixed set of slots shared by every host core. Defining `WOLFHSM_CFG_SHE_GLOBAL_KEYS` (which requires both `WOLFHSM_CFG_GLOBAL_KEYS` and `WOLFHSM_CFG_SHE_EXTENSION`) switches to that model: **all** SHE slots — `SECRET_KEY` through `RAM_KEY` and `PRNG_SEED` — are built in the [global-keys](#global-keys) namespace (USER = `WH_KEYUSER_GLOBAL`, 0) and are shared by every client.

Nothing changes on the wire. Clients still name slots by their raw 0–15 slot number in every SHE command; the server resolves each SHE keyId into the global namespace internally. Two clients that program and use slot 4 now see the same key, and one shared `RAM_KEY` is visible to all. This is a build-time choice rather than a per-request flag because most SHE operations cannot carry one: the `CMD_LOAD_KEY` target and authorization slots are 4-bit fields inside the authenticated M1 message, and secure boot, PRNG, and RAM-key export address fixed slots.

The mode composes with the [wrapped keys](#wrapped-keys) interop:

- **Wrap-export** of a SHE slot needs no special flag — the server routes any `WH_KEYTYPE_SHE` target into the global namespace, so `wh_Client_KeyWrapExport(..., slot, WH_KEYTYPE_SHE, kek, ...)` names the same slot the SHE commands do.
- **Unwrap-and-cache** normalizes a SHE blob's USER field to the global namespace after the ownership check, so a blob minted by a per-client build primes the slot the SHE commands actually read. The server-assigned id returned to the client carries `WH_KEYID_CLIENT_GLOBAL_FLAG`.
- The **counter rollback guard** compares a primed blob's counter against the globally committed slot, so a stale blob cannot shadow a newer shared key.

Some deployment limits follow from where the global cache lives:

- **Sessions are per server, keys are shared.** The per-connection SHE runtime state — the secure-boot state machine, the ECU UID, the plaintext `RAM_KEY` load flag, and the PRNG working state — stays in each server's `whServerSheContext`. Each server must therefore still set its UID and run secure boot on its own even though it reads the shared boot keys. Servers that share an NVM must be provisioned with the same UID, or their `CMD_LOAD_KEY` M1 validation will diverge.
- **NVM-less degrades to per-server.** The global cache is owned by the NVM context (`whNvmContext`), so a server started with `nvm == NULL` has no global cache and its global SHE keys fall back to that server's local cache. This is correct for a single NVM-less server but means multiple NVM-less servers do **not** share SHE state; sharing across servers requires a shared `whNvmContext`.
- **One shared cache, sized for the slots.** With global SHE keys on, every client's SHE keys plus any global crypto keys share the one global cache; the default `WOLFHSM_CFG_SERVER_KEYCACHE_COUNT` with SHE enabled matches the sixteen SHE slots (see [Configuration](9-Configuration.md#keystore-and-key-cache)). Keys primed via unwrap-and-cache are uncommitted and are not evicted under cache pressure, and a client `KeyEvict` cannot target SHE-typed cache entries, so provisioning flows should keep the number of live uncommitted SHE entries small.

When `WOLFHSM_CFG_SHE_GLOBAL_KEYS` is left undefined, SHE behaves exactly as described in the previous section, with per-client slots and no dependency on the global-keys feature.

### Encrypted Key Update Protocol (M1–M5)

The most intricate piece of the SHE spec is the encrypted key update protocol that runs underneath `CMD_LOAD_KEY`. wolfHSM implements it as specified: the client constructs three input messages (M1, M2, M3) by encrypting and CMACing the new key and its metadata under keys derived from a chosen authorization key, sends them to the server, and receives two response messages (M4, M5) that prove the server stored the new key correctly.
Expand Down Expand Up @@ -744,7 +767,7 @@ The SHE extension is built on top of the same infrastructure as every other wolf
- **Keystore**: SHE keys live in the same per-client `whKeyId` space as crypto keys, with the TYPE field disambiguating the two. SHE keys do not currently consume the per-key [usage flag policy](#key-usage-policies) machinery — usage constraints are expressed through the SHE-spec flag set in the label instead — but lifecycle flags like `NONMODIFIABLE` apply at the NVM layer just as they do for any other object.
- **Communication layer**: every SHE command is a packet under the `WH_MESSAGE_GROUP_SHE` group and is dispatched through the [comm layer](#communication-layer-and-transports) like any other request. SHE clients work over every available transport without modification.
- **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**: not supported for SHE keys — every SHE keyId carries the connection's client ID in the USER field, and the SHE command set has no way to name the [global](#global-keys) (`WH_KEYUSER_GLOBAL`) namespace. Applications that need to share a key across clients must provision it into each client's SHE namespace separately.
- **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.

Expand Down
3 changes: 2 additions & 1 deletion docs/src/9-Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ These macros enable or tune optional cryptographic subsystems built on top of wo
| `WOLFHSM_CFG_HWKEYSTORE` | Undefined | If defined, compile the hardware keystore front-end (`wh_HwKeystore_*`) and hardware-only key support (`WH_KEYTYPE_HW`, `WH_CLIENT_KEYID_MAKE_HW()`). Hardware-only keys are served on demand by a user-supplied callback and are usable only as keywrap KEKs; they never enter the key cache or NVM and are never exported. See [Hardware-Only Keys](5-Features.md#hardware-only-keys). |
| `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` | `32` | Maximum size, in bytes, of a key served by the hardware keystore backend; sizes the local buffer that holds a hardware KEK for the duration of a keywrap operation. Only consulted when `WOLFHSM_CFG_HWKEYSTORE` is defined. |
| `WOLFHSM_CFG_GLOBAL_KEYS` | Undefined | If defined, enable the global-keys feature, allowing keys to be cached so that they are visible to every client rather than scoped to the caching client. See [Global Keys](5-Features.md#global-keys) for a full discussion of the API and security implications. |
| `WOLFHSM_CFG_SHE_GLOBAL_KEYS` | Undefined | If defined, all sixteen AUTOSAR SHE key slots live in the global-keys namespace (USER=0) so every connected client shares a single SHE device view, matching AUTOSAR's one-physical-SHE model rather than wolfHSM's default per-client SHE slots. Requires `WOLFHSM_CFG_GLOBAL_KEYS` and `WOLFHSM_CFG_SHE_EXTENSION` (enforced by `#error`). See [Global SHE Keys](5-Features.md#global-she-keys). |
| `WH_DEV_ID` | `0x5748534D` (`"WHSM"`) | Value of the process-global crypto device ID registered by every `wh_Client_Init()` with the unified client crypto callback. Also the device ID bound to a client whose `whClientConfig.devId` is left `0`. Override it if the default collides with another crypto-callback device ID in the application. See [Transparent Offload via Crypto Callbacks](5-Features.md#transparent-offload-via-crypto-callbacks) for registration lifetime, multi-client rules, and wolfCrypt callback-table sizing (`MAX_CRYPTO_DEVID_CALLBACKS`, default 8). |
| `WH_DEV_ID_DMA` | `0x57444D41` (`"WDMA"`) | Value of the process-global DMA-only crypto device ID, registered by every `wh_Client_Init()` when `WOLFHSM_CFG_DMA` is defined. Reserved: not valid as a `whClientConfig.devId`. Override it if the default collides with another crypto-callback device ID in the application. |

Expand All @@ -87,7 +88,7 @@ These macros size the server-side key cache. The cache is split into "regular" s

| Macro | Default | Description |
|---|---|---|
| `WOLFHSM_CFG_SERVER_KEYCACHE_COUNT` | `8` | Number of regular RAM key-cache slots on the server. |
| `WOLFHSM_CFG_SERVER_KEYCACHE_COUNT` | `8` (`16` with `WOLFHSM_CFG_SHE_EXTENSION`) | Number of regular RAM key-cache slots on the server. The SHE default holds all sixteen SHE slots at once. |
| `WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE` | `256` | Size, in bytes, of each regular key-cache slot. |
| `WOLFHSM_CFG_SERVER_KEYCACHE_BIG_COUNT` | `1` | Number of "big" RAM key-cache slots on the server, used for large keys (e.g. RSA, ML-DSA). |
| `WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE` | `1200` | Size, in bytes, of each big key-cache slot. Should be at least the largest key the server is expected to hold (e.g. ~1024 bytes for an RSA-4096 private key). |
Expand Down
6 changes: 3 additions & 3 deletions src/wh_client_she.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ int wh_Client_ShePreProgramKey(whClientContext* c, whNvmId keyId,

/* Create a key with 0 counter */
wh_She_Meta2Label(0, flags, label);
ret = wh_Client_NvmAddObject(c,
WH_MAKE_KEYID(WH_KEYTYPE_SHE, c->comm->client_id, keyId),
0, 0, sizeof(label), label, keySz, key, (int32_t*)&outRc);
ret = wh_Client_NvmAddObject(
c, WH_SHE_MAKE_KEYID(c->comm->client_id, keyId), 0, 0, sizeof(label),
label, keySz, key, (int32_t*)&outRc);
if (ret == 0)
ret = outRc;
return ret;
Expand Down
30 changes: 30 additions & 0 deletions src/wh_server_keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,16 @@ _HandleKeyWrapExportRequest(whServerContext* server,
return WH_ERROR_BADARGS;
}

#ifdef WOLFHSM_CFG_SHE_GLOBAL_KEYS
/* Route SHE targets to the global namespace so wrap-export names slots
* the same way the SHE commands do, whether or not the client passed the
* global flag. */
if (targetKeyType == WH_KEYTYPE_SHE) {
targetKeyId = WH_MAKE_KEYID(WH_KEYTYPE_SHE, WH_KEYUSER_GLOBAL,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we not include this in the above swtich statement?

WH_KEYID_ID(targetKeyId));
}
#endif

/* Read the key and its real metadata, enforcing export policy
* (NONEXPORTABLE). For wrapped keys this is a cache-only probe. */
ret = wh_Server_KeystoreReadKeyChecked(server, targetKeyId, &metadata, key,
Expand Down Expand Up @@ -2256,6 +2266,26 @@ static int _HandleKeyUnwrapAndCacheRequest(
}
#endif /* WOLFHSM_CFG_GLOBAL_KEYS */

#ifdef WOLFHSM_CFG_SHE_GLOBAL_KEYS
/* Move the blob's id to the global namespace where the SHE commands look;
* blobs minted by per-client builds would otherwise cache under an id no
* SHE command uses and no client request can evict. Do this before the
* in-cache check and counter guard below so both see the final id. */
Comment on lines +2270 to +2273

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is all jargon and unintelligible. Simplify into something human readible.

if (wrappedKeyType == WH_KEYTYPE_SHE) {
metadata.id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, WH_KEYUSER_GLOBAL,
WH_KEYID_ID(metadata.id));
}
#elif defined(WOLFHSM_CFG_SHE_EXTENSION) && defined(WOLFHSM_CFG_GLOBAL_KEYS)
/* SHE slots are per-client on this build, so a blob minted by a global-SHE
* build would cache under an id no SHE command reads and no client request
* can evict, pinning the cache slot until reboot. Reject it instead. */
Comment on lines +2279 to +2281

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jargon, unintelligible. Simplify and make human readible

if (wrappedKeyType == WH_KEYTYPE_SHE &&
wrappedKeyUser == WH_KEYUSER_GLOBAL) {
ret = WH_ERROR_ACCESS;
goto out;
}
#endif

/* Ensure a key with the unwrapped ID does not already exist in cache */
if (_ExistsInCache(server, metadata.id)) {
ret = WH_ERROR_ABORTED;
Expand Down
Loading
Loading