diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index 15253f618..c1ecfae68 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -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 @@ -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 diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d1af2729b..c00c58506 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -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 @@ -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 diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 34e3aabc0..c5caba62c 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -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) @@ -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. @@ -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. @@ -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. diff --git a/docs/src/9-Configuration.md b/docs/src/9-Configuration.md index e1cd9563a..d2bc867d7 100644 --- a/docs/src/9-Configuration.md +++ b/docs/src/9-Configuration.md @@ -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. | @@ -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). | diff --git a/src/wh_client_she.c b/src/wh_client_she.c index b59b5e64b..a96469e15 100644 --- a/src/wh_client_she.c +++ b/src/wh_client_she.c @@ -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; diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index b08143c67..151599f62 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -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, + 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, @@ -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. */ + 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. */ + 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; diff --git a/src/wh_server_she.c b/src/wh_server_she.c index 22c9acace..b036c11fc 100644 --- a/src/wh_server_she.c +++ b/src/wh_server_she.c @@ -250,11 +250,10 @@ static int _SecureBootInit(whServerContext* server, uint16_t magic, server->she->blSize = req.sz; /* check if the boot mac key is empty */ keySz = sizeof(macKey); - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - WH_SHE_BOOT_MAC_KEY_ID), - NULL, macKey, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_BOOT_MAC_KEY_ID), + NULL, macKey, &keySz); if (ret == 0 && keySz != WH_SHE_KEY_SZ) { ret = WH_SHE_ERC_KEY_INVALID; } @@ -397,11 +396,9 @@ static int _SecureBootFinish(whServerContext* server, uint16_t magic, /* load the cmac to check */ if (ret == 0) { keySz = sizeof(macDigest); - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - WH_SHE_BOOT_MAC), - NULL, macDigest, &keySz); + ret = wh_Server_KeystoreReadKey( + server, WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_BOOT_MAC), + NULL, macDigest, &keySz); if (ret != 0) { ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } @@ -506,11 +503,11 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, /* read the auth key by AuthID */ if (ret == 0) { keySz = sizeof(kdfInput); - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - _PopAuthId(req.messageOne)), - NULL, kdfInput, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, + _PopAuthId(req.messageOne)), + NULL, kdfInput, &keySz); } /* make K2 using AES-MP(authKey | WH_SHE_KEY_UPDATE_MAC_C) */ if (ret == 0) { @@ -567,11 +564,10 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, wc_AesFree(server->she->sheAes); /* load the target key */ if (ret == 0) { - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - _PopId(req.messageOne)), - meta, kdfInput, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, _PopId(req.messageOne)), + meta, kdfInput, &keySz); /* Extract count and flags from the label, even if it failed */ wh_She_Label2Meta(meta->label, &she_meta_count, &she_meta_flags); /* if the keyslot is empty or write protection is not on continue */ @@ -604,8 +600,8 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, } /* write key with msg_counter_BE */ if (ret == 0) { - meta->id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - _PopId(req.messageOne)); + meta->id = + WH_SHE_MAKE_KEYID(server->comm->client_id, _PopId(req.messageOne)); she_meta_flags = _PopFlags(req.messageTwo); she_meta_count = wh_Utils_ntohl(msg_counter_val) >> 4; /* Update the meta label with new values */ @@ -741,8 +737,7 @@ static int _LoadPlainKey(whServerContext* server, uint16_t magic, &req); } - meta->id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - WH_SHE_RAM_KEY_ID); + meta->id = WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_RAM_KEY_ID); meta->len = WH_SHE_KEY_SZ; /* cache if ram key, overwrite otherwise */ @@ -789,11 +784,10 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, /* read the auth key by AuthID */ if (ret == 0) { keySz = WH_SHE_KEY_SZ; - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - WH_SHE_SECRET_KEY_ID), - meta, kdfInput, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_SECRET_KEY_ID), + meta, kdfInput, &keySz); if (ret != 0) { ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } @@ -819,10 +813,9 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, memcpy(resp.messageTwo, &counter_val, sizeof(uint32_t)); keySz = WH_SHE_KEY_SZ; ret = wh_Server_KeystoreReadKey( - server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - WH_SHE_RAM_KEY_ID), - meta, resp.messageTwo + WH_SHE_KEY_SZ, &keySz); + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_RAM_KEY_ID), meta, + resp.messageTwo + WH_SHE_KEY_SZ, &keySz); if (ret != 0) { ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } @@ -954,11 +947,10 @@ static int _InitRnd(whServerContext* server, uint16_t magic, uint16_t req_size, /* read secret key */ if (ret == 0) { keySz = WH_SHE_KEY_SZ; - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - WH_SHE_SECRET_KEY_ID), - meta, kdfInput, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_SECRET_KEY_ID), + meta, kdfInput, &keySz); if (ret != 0) { ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } @@ -974,11 +966,10 @@ static int _InitRnd(whServerContext* server, uint16_t magic, uint16_t req_size, /* read the current PRNG_SEED, i - 1, to cmacOutput */ if (ret == 0) { keySz = WH_SHE_KEY_SZ; - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - WH_SHE_PRNG_SEED_ID), - meta, cmacOutput, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_PRNG_SEED_ID), + meta, cmacOutput, &keySz); if (ret != 0) { ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } @@ -1000,8 +991,8 @@ static int _InitRnd(whServerContext* server, uint16_t magic, uint16_t req_size, wc_AesFree(server->she->sheAes); /* save PRNG_SEED, i */ if (ret == 0) { - meta->id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - WH_SHE_PRNG_SEED_ID); + meta->id = + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_PRNG_SEED_ID); meta->len = WH_SHE_KEY_SZ; /* Persist the PRNG seed to NVM, or cache it when there is no NVM to * persist to. wh_Server_KeystoreCacheKey evicts any existing entry for @@ -1129,11 +1120,10 @@ static int _ExtendSeed(whServerContext* server, uint16_t magic, /* read the PRNG_SEED into kdfInput */ if (ret == 0) { keySz = WH_SHE_KEY_SZ; - ret = wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - server->comm->client_id, - WH_SHE_PRNG_SEED_ID), - meta, kdfInput, &keySz); + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_PRNG_SEED_ID), + meta, kdfInput, &keySz); if (ret != 0) { ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } @@ -1145,8 +1135,8 @@ static int _ExtendSeed(whServerContext* server, uint16_t magic, } /* save PRNG_SEED */ if (ret == 0) { - meta->id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - WH_SHE_PRNG_SEED_ID); + meta->id = + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_PRNG_SEED_ID); meta->len = WH_SHE_KEY_SZ; /* Persist to NVM, or cache when there is no NVM. * wh_Server_KeystoreCacheKey evicts any existing entry for this id. */ @@ -1212,9 +1202,8 @@ static int _EncEcb(whServerContext* server, uint16_t magic, uint16_t req_size, if (ret == 0) { /* load the key */ ret = wh_Server_KeystoreReadKey( - server, WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - req.keyId), - NULL, tmpKey, &keySz); + server, WH_SHE_MAKE_KEYID(server->comm->client_id, req.keyId), NULL, + tmpKey, &keySz); if (ret == 0) { ret = wc_AesInit(server->she->sheAes, NULL, server->devId); } @@ -1290,9 +1279,8 @@ static int _EncCbc(whServerContext* server, uint16_t magic, uint16_t req_size, if (ret == 0) { /* load the key */ ret = wh_Server_KeystoreReadKey( - server, WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - req.keyId), - NULL, tmpKey, &keySz); + server, WH_SHE_MAKE_KEYID(server->comm->client_id, req.keyId), NULL, + tmpKey, &keySz); if (ret == 0) { ret = wc_AesInit(server->she->sheAes, NULL, server->devId); } @@ -1374,9 +1362,8 @@ static int _DecEcb(whServerContext* server, uint16_t magic, uint16_t req_size, if (ret == 0) { /* load the key */ ret = wh_Server_KeystoreReadKey( - server, WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - req.keyId), - NULL, tmpKey, &keySz); + server, WH_SHE_MAKE_KEYID(server->comm->client_id, req.keyId), NULL, + tmpKey, &keySz); if (ret == 0) { ret = wc_AesInit(server->she->sheAes, NULL, server->devId); } @@ -1456,9 +1443,8 @@ static int _DecCbc(whServerContext* server, uint16_t magic, uint16_t req_size, if (ret == 0) { /* load the key */ ret = wh_Server_KeystoreReadKey( - server, WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - req.keyId), - NULL, tmpKey, &keySz); + server, WH_SHE_MAKE_KEYID(server->comm->client_id, req.keyId), NULL, + tmpKey, &keySz); if (ret == 0 && keySz != WH_SHE_KEY_SZ) { ret = WH_SHE_ERC_KEY_INVALID; } @@ -1532,10 +1518,8 @@ static int _GenerateMac(whServerContext* server, uint16_t magic, /* load the key */ keySz = WH_SHE_KEY_SZ; ret = wh_Server_KeystoreReadKey( - server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - req.keyId), - NULL, tmpKey, &keySz); + server, WH_SHE_MAKE_KEYID(server->comm->client_id, req.keyId), NULL, + tmpKey, &keySz); if (ret == 0 && keySz != WH_SHE_KEY_SZ) { ret = WH_SHE_ERC_KEY_INVALID; } @@ -1600,10 +1584,8 @@ static int _VerifyMac(whServerContext* server, uint16_t magic, if (ret == 0) { keySz = WH_SHE_KEY_SZ; ret = wh_Server_KeystoreReadKey( - server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - req.keyId), - NULL, tmpKey, &keySz); + server, WH_SHE_MAKE_KEYID(server->comm->client_id, req.keyId), NULL, + tmpKey, &keySz); if (ret == 0 && keySz != WH_SHE_KEY_SZ) { ret = WH_SHE_ERC_KEY_INVALID; } diff --git a/test-refactor/client-server/wh_test_she.c b/test-refactor/client-server/wh_test_she.c index f5883e403..e723e9bc1 100644 --- a/test-refactor/client-server/wh_test_she.c +++ b/test-refactor/client-server/wh_test_she.c @@ -65,8 +65,7 @@ static int _destroySheKey(whClientContext* client, whNvmId clientSheKeyId) { int rc = 0; int32_t serverRc = 0; - whNvmId id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, - clientSheKeyId); + whNvmId id = WH_SHE_MAKE_KEYID(client->comm->client_id, clientSheKeyId); rc = wh_Client_NvmDestroyObjects(client, 1, &id, &serverRc); if (rc == WH_ERROR_OK) { diff --git a/test-refactor/misc/wh_test_multiclient.c b/test-refactor/misc/wh_test_multiclient.c index 34427e82e..bdc021051 100644 --- a/test-refactor/misc/wh_test_multiclient.c +++ b/test-refactor/misc/wh_test_multiclient.c @@ -52,6 +52,17 @@ #include "wolfhsm/wh_nvm_flash.h" #include "wolfhsm/wh_flash_ramsim.h" +#ifdef WOLFHSM_CFG_SHE_EXTENSION +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/cmac.h" +#include "wolfhsm/wh_she_common.h" +#include "wolfhsm/wh_she_crypto.h" +#include "wolfhsm/wh_client_she.h" +#include "wolfhsm/wh_message_she.h" +#endif /* WOLFHSM_CFG_SHE_EXTENSION */ + #include "wh_test_common.h" #include "wh_test_keywrap_util.h" #include "wh_test_list.h" @@ -1338,6 +1349,483 @@ static int _runGlobalKeysTests(whClientContext* client1, #endif /* WOLFHSM_CFG_GLOBAL_KEYS */ +/* ============================================================================ + * GLOBAL SHE KEYS TEST SUITE + * + * With WOLFHSM_CFG_SHE_GLOBAL_KEYS all SHE slots live in the global-keys + * namespace, so the two servers (sharing one NVM and its global cache) act as + * one SHE device seen by both clients. Uses the split request/response APIs + * since there is no server thread to pump requests. + * ========================================================================== */ + +#if defined(WOLFHSM_CFG_SHE_GLOBAL_KEYS) && !defined(WOLFHSM_CFG_NO_CRYPTO) + +/* SHE slots used by this suite */ +#define SHE_MC_USER_SLOT 4 +#define SHE_MC_LOAD_SLOT 5 +#define SHE_MC_PRIME_SLOT 8 +#define SHE_MC_CTR_SLOT 9 + +/* Provision a SHE slot in the shared NVM, the way ShePreProgramKey does but + * with the split API. Counter and SHE flags go in the object label. */ +static int _sheGlobalAddNvmKey(whClientContext* client, whServerContext* server, + uint8_t sheSlot, uint32_t counter, + uint32_t sheFlags, const uint8_t* key) +{ + int ret; + int32_t rc = 0; + uint8_t label[WH_NVM_LABEL_LEN] = {0}; + + wh_She_Meta2Label(counter, sheFlags, label); + ret = wh_Client_NvmAddObjectRequest( + client, WH_SHE_MAKE_KEYID(client->comm->client_id, sheSlot), 0, 0, + sizeof(label), label, WH_SHE_KEY_SZ, key); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_NvmAddObjectResponse(client, &rc); + } + if (ret == 0) { + ret = (int)rc; + } + return ret; +} + +/* One ECB encrypt through the given client/server pair */ +static int _sheGlobalEncEcb(whClientContext* client, whServerContext* server, + uint8_t sheSlot, uint8_t* in, uint8_t* out) +{ + int ret = wh_Client_SheEncEcbRequest(client, sheSlot, in, WH_SHE_KEY_SZ); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheEncEcbResponse(client, out, WH_SHE_KEY_SZ); + } + return ret; +} + +/* Software AES-ECB of one block, the expected value for the server results */ +static int _sheGlobalSwEcb(const uint8_t* key, const uint8_t* in, uint8_t* out) +{ + Aes aes[1]; + int ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesSetKey(aes, key, WH_SHE_KEY_SZ, NULL, AES_ENCRYPTION); + if (ret == 0) { + ret = wc_AesEncryptDirect(aes, out, in); + } + wc_AesFree(aes); + } + return ret; +} + +static int _sheGlobalSetUid(whClientContext* client, whServerContext* server, + uint8_t* uid, uint32_t uidSz) +{ + int ret = wh_Client_SheSetUidRequest(client, uid, uidSz); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheSetUidResponse(client); + } + return ret; +} + +static int _sheGlobalLoadPlainKey(whClientContext* client, + whServerContext* server, uint8_t* key) +{ + int ret = wh_Client_SheLoadPlainKeyRequest(client, key, WH_SHE_KEY_SZ); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheLoadPlainKeyResponse(client); + } + return ret; +} + +/* boot MAC digest = CMAC_bootMacKey(zeros || size || bootloader) */ +static int _sheGlobalComputeBootMac(const uint8_t* bootloader, + uint32_t bootloaderSz, + const uint8_t* bootMacKey, + uint8_t* digestOut) +{ + int ret; + Cmac cmac[1]; + uint8_t zeros[WH_SHE_BOOT_MAC_PREFIX_LEN] = {0}; + word32 digestSz = AES_BLOCK_SIZE; + + if ((ret = wc_InitCmac(cmac, bootMacKey, WH_SHE_KEY_SZ, WC_CMAC_AES, + NULL)) != 0) { + return ret; + } + if ((ret = wc_CmacUpdate(cmac, zeros, sizeof(zeros))) != 0) { + return ret; + } + if ((ret = wc_CmacUpdate(cmac, (const uint8_t*)&bootloaderSz, + sizeof(bootloaderSz))) != 0) { + return ret; + } + if ((ret = wc_CmacUpdate(cmac, bootloader, bootloaderSz)) != 0) { + return ret; + } + return wc_CmacFinal(cmac, digestOut, &digestSz); +} + +/* The secure-boot protocol (INIT / UPDATE / FINISH) only has a blocking + * client API, so drive the messages directly and pump the server between + * each step. The bootloader used here fits one UPDATE chunk. */ +static int _sheGlobalSecureBoot(whClientContext* client, + whServerContext* server, uint8_t* bootloader, + uint32_t bootloaderLen) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t dataSz; + uint8_t* respBuf; + + whMessageShe_SecureBootInitRequest* initReq; + whMessageShe_SecureBootUpdateRequest* updateReq; + whMessageShe_SecureBootInitResponse* initResp; + whMessageShe_SecureBootUpdateResponse* updateResp; + whMessageShe_SecureBootFinishResponse* finishResp; + + if (bootloaderLen > + (uint32_t)(WOLFHSM_CFG_COMM_DATA_LEN - + sizeof(whMessageShe_SecureBootUpdateRequest))) { + return WH_ERROR_BADARGS; + } + + respBuf = (uint8_t*)wh_CommClient_GetDataPtr(client->comm); + + /* INIT: announce the bootloader size */ + initReq = (whMessageShe_SecureBootInitRequest*)wh_CommClient_GetDataPtr( + client->comm); + initReq->sz = bootloaderLen; + WH_TEST_RETURN_ON_FAIL(wh_Client_SendRequest( + client, WH_MESSAGE_GROUP_SHE, WH_SHE_SECURE_BOOT_INIT, sizeof(*initReq), + (uint8_t*)initReq)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + ret = wh_Client_RecvResponse(client, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, respBuf); + if (ret != WH_ERROR_OK) { + return ret; + } + initResp = (whMessageShe_SecureBootInitResponse*)respBuf; + if (initResp->rc != WH_SHE_ERC_NO_ERROR) { + return initResp->rc; + } + + /* UPDATE: feed the bootloader (single chunk) */ + updateReq = (whMessageShe_SecureBootUpdateRequest*)wh_CommClient_GetDataPtr( + client->comm); + updateReq->sz = bootloaderLen; + memcpy((uint8_t*)(updateReq + 1), bootloader, bootloaderLen); + WH_TEST_RETURN_ON_FAIL(wh_Client_SendRequest( + client, WH_MESSAGE_GROUP_SHE, WH_SHE_SECURE_BOOT_UPDATE, + (uint16_t)(sizeof(*updateReq) + bootloaderLen), (uint8_t*)updateReq)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + ret = wh_Client_RecvResponse(client, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, respBuf); + if (ret != WH_ERROR_OK) { + return ret; + } + updateResp = (whMessageShe_SecureBootUpdateResponse*)respBuf; + if (updateResp->rc != WH_SHE_ERC_NO_ERROR) { + return updateResp->rc; + } + + /* FINISH: verify the boot MAC */ + WH_TEST_RETURN_ON_FAIL(wh_Client_SendRequest( + client, WH_MESSAGE_GROUP_SHE, WH_SHE_SECURE_BOOT_FINISH, 0, NULL)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + ret = wh_Client_RecvResponse(client, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, respBuf); + if (ret != WH_ERROR_OK) { + return ret; + } + finishResp = (whMessageShe_SecureBootFinishResponse*)respBuf; + return finishResp->rc; +} + +static int _sheGlobalGetStatus(whClientContext* client, whServerContext* server, + uint8_t* sreg) +{ + int ret = wh_Client_SheGetStatusRequest(client); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheGetStatusResponse(client, sreg); + } + return ret; +} + +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(HAVE_AESGCM) +static int _sheGlobalUnwrapAndCache(whClientContext* client, + whServerContext* server, uint8_t* blob, + uint16_t blobSz, uint16_t* outId) +{ + /* The KEK is a global CRYPTO key, so unlike SHE slot ids the client must + * name it with the global flag */ + int ret = wh_Client_KeyUnwrapAndCacheRequest( + client, WC_CIPHER_AES_GCM, + WH_CLIENT_KEYID_MAKE_GLOBAL(WH_TEST_MC_WRAP_KEK_ID), blob, blobSz); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_KeyUnwrapAndCacheResponse(client, WC_CIPHER_AES_GCM, + outId); + } + return ret; +} +#endif /* WOLFHSM_CFG_KEYWRAP && HAVE_AESGCM */ + +static int _runSheGlobalTests(whClientContext* client1, + whServerContext* server1, + whClientContext* client2, + whServerContext* server2) +{ + int ret; + int i; + uint8_t sheUid[WH_SHE_UID_SZ]; + uint8_t secretKey[WH_SHE_KEY_SZ]; + uint8_t masterKey[WH_SHE_KEY_SZ]; + uint8_t bootMacKey[WH_SHE_KEY_SZ]; + uint8_t bootDigest[WH_SHE_KEY_SZ]; + uint8_t bootloader[64]; + uint8_t userKey[WH_SHE_KEY_SZ]; + uint8_t loadKey[WH_SHE_KEY_SZ]; + uint8_t ramKey[WH_SHE_KEY_SZ]; + uint8_t ptIn[WH_SHE_KEY_SZ]; + uint8_t ct1[WH_SHE_KEY_SZ]; + uint8_t ct2[WH_SHE_KEY_SZ]; + uint8_t ctSw[WH_SHE_KEY_SZ]; + uint8_t sreg; + uint8_t m1[WH_SHE_M1_SZ]; + uint8_t m2[WH_SHE_M2_SZ]; + uint8_t m3[WH_SHE_M3_SZ]; + uint8_t m4[WH_SHE_M4_SZ]; + uint8_t m5[WH_SHE_M5_SZ]; + uint8_t m4Out[WH_SHE_M4_SZ]; + uint8_t m5Out[WH_SHE_M5_SZ]; + + WH_TEST_PRINT("Testing Global SHE Keys...\n"); + + for (i = 0; i < (int)sizeof(sheUid); i++) { + sheUid[i] = (uint8_t)i; + } + memset(secretKey, 0xA1, sizeof(secretKey)); + memset(masterKey, 0xA2, sizeof(masterKey)); + memset(bootMacKey, 0xA8, sizeof(bootMacKey)); + memset(bootloader, 0xB7, sizeof(bootloader)); + memset(userKey, 0xA3, sizeof(userKey)); + memset(loadKey, 0xA4, sizeof(loadKey)); + memset(ramKey, 0xA5, sizeof(ramKey)); + memset(ptIn, 0x11, sizeof(ptIn)); + + /* Client 1 provisions the shared SHE slots, including the boot MAC key + * and the expected bootloader digest; same UID on both servers */ + WH_TEST_RETURN_ON_FAIL(_sheGlobalComputeBootMac( + bootloader, sizeof(bootloader), bootMacKey, bootDigest)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_SECRET_KEY_ID, 0, 0, secretKey)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_MASTER_ECU_KEY_ID, 0, 0, masterKey)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_BOOT_MAC_KEY_ID, 0, 0, bootMacKey)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_BOOT_MAC, 0, 0, bootDigest)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalAddNvmKey(client1, server1, SHE_MC_USER_SLOT, 0, 0, userKey)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSetUid(client1, server1, sheUid, sizeof(sheUid))); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSetUid(client2, server2, sheUid, sizeof(sheUid))); + + /* Both servers secure boot against the keys client 1 provisioned. The + * boot state machine is per server, so each must boot on its own. */ + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSecureBoot(client1, server1, bootloader, sizeof(bootloader))); + sreg = 0; + WH_TEST_RETURN_ON_FAIL(_sheGlobalGetStatus(client1, server1, &sreg)); + WH_TEST_ASSERT_RETURN((sreg & WH_SHE_SREG_BOOT_OK) != 0); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSecureBoot(client2, server2, bootloader, sizeof(bootloader))); + sreg = 0; + WH_TEST_RETURN_ON_FAIL(_sheGlobalGetStatus(client2, server2, &sreg)); + WH_TEST_ASSERT_RETURN((sreg & WH_SHE_SREG_BOOT_OK) != 0); + WH_TEST_PRINT(" PASS: Both servers secure boot on shared boot keys\n"); + + /* Both clients encrypt with the slot client 1 provisioned */ + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client1, server1, SHE_MC_USER_SLOT, ptIn, ct1)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client2, server2, SHE_MC_USER_SLOT, ptIn, ct2)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(userKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct1, ct2, sizeof(ct1)) == 0); + WH_TEST_ASSERT_RETURN(memcmp(ct1, ctSw, sizeof(ct1)) == 0); + WH_TEST_PRINT(" PASS: Both clients share a provisioned SHE slot\n"); + + /* Client 1 installs a key with the LoadKey protocol; client 2 uses it */ + WH_TEST_RETURN_ON_FAIL(wh_She_GenerateLoadableKey( + SHE_MC_LOAD_SLOT, WH_SHE_MASTER_ECU_KEY_ID, 1, 0, sheUid, loadKey, + masterKey, m1, m2, m3, m4, m5)); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheLoadKeyRequest(client1, m1, m2, m3)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheLoadKeyResponse(client1, m4Out, m5Out)); + WH_TEST_ASSERT_RETURN(memcmp(m4Out, m4, sizeof(m4)) == 0); + WH_TEST_ASSERT_RETURN(memcmp(m5Out, m5, sizeof(m5)) == 0); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client2, server2, SHE_MC_LOAD_SLOT, ptIn, ct2)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(loadKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct2, ctSw, sizeof(ct2)) == 0); + WH_TEST_PRINT(" PASS: LoadKey by client 1 visible to client 2\n"); + +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(HAVE_AESGCM) + { + uint8_t blob[128]; + uint16_t blobSz; + uint16_t outId; + uint16_t expSz = (uint16_t)(WH_KEYWRAP_AES_GCM_HEADER_SIZE + + sizeof(whNvmMetadata) + WH_SHE_KEY_SZ); + uint8_t primeKey[WH_SHE_KEY_SZ]; + uint8_t ctrKey[WH_SHE_KEY_SZ]; + + memset(primeKey, 0xA6, sizeof(primeKey)); + memset(ctrKey, 0xA7, sizeof(ctrKey)); + + /* Wrap-export a SHE slot naming it by raw slot number, no global + * flag, under the shared trusted KEK */ + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExportRequest( + client1, WC_CIPHER_AES_GCM, SHE_MC_USER_SLOT, WH_KEYTYPE_SHE, + WH_CLIENT_KEYID_MAKE_GLOBAL(WH_TEST_MC_WRAP_KEK_ID))); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExportResponse( + client1, WC_CIPHER_AES_GCM, blob, &blobSz)); + WH_TEST_ASSERT_RETURN(blobSz == expSz); + WH_TEST_PRINT(" PASS: Wrap-export of a SHE slot by raw slot id\n"); + + /* Client 1 builds a blob for an unused slot, client 2 primes it, + * client 1 uses it through the shared global cache */ + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(client1->comm->client_id, SHE_MC_PRIME_SLOT), 1, + 0, primeKey, blob, &blobSz)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalUnwrapAndCache(client2, server2, blob, blobSz, &outId)); + WH_TEST_ASSERT_RETURN((outId & WH_KEYID_MASK) == SHE_MC_PRIME_SLOT); + WH_TEST_ASSERT_RETURN((outId & WH_KEYID_CLIENT_GLOBAL_FLAG) != 0); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client1, server1, SHE_MC_PRIME_SLOT, ptIn, ct1)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(primeKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct1, ctSw, sizeof(ct1)) == 0); + WH_TEST_PRINT(" PASS: Cross-client unwrap-and-cache prime\n"); + + /* Counter guard runs against the globally committed slot */ + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, SHE_MC_CTR_SLOT, 5, 0, ctrKey)); + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(client2->comm->client_id, SHE_MC_CTR_SLOT), 3, 0, + ctrKey, blob, &blobSz)); + ret = _sheGlobalUnwrapAndCache(client2, server2, blob, blobSz, &outId); + if (ret != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("SHE global counter rollback expected ACCESS, got " + "%d\n", + ret); + return (ret == 0) ? WH_ERROR_ABORTED : ret; + } + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(client2->comm->client_id, SHE_MC_CTR_SLOT), 5, 0, + ctrKey, blob, &blobSz)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalUnwrapAndCache(client2, server2, blob, blobSz, &outId)); + WH_TEST_PRINT(" PASS: Counter guard on a globally committed slot\n"); + } +#endif /* WOLFHSM_CFG_KEYWRAP && HAVE_AESGCM */ + + /* The RAM key is one shared volatile slot, but the plain-loaded state + * that allows exporting it is per server */ + WH_TEST_RETURN_ON_FAIL(_sheGlobalLoadPlainKey(client1, server1, ramKey)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client2, server2, WH_SHE_RAM_KEY_ID, ptIn, ct2)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(ramKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct2, ctSw, sizeof(ct2)) == 0); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheExportRamKeyRequest(client2)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2)); + ret = wh_Client_SheExportRamKeyResponse(client2, m1, m2, m3, m4, m5); + if (ret != WH_SHE_ERC_KEY_INVALID) { + WH_ERROR_PRINT("SHE global RAM key export without plain load expected " + "KEY_INVALID, got %d\n", + ret); + return (ret == 0) ? WH_ERROR_ABORTED : ret; + } + WH_TEST_RETURN_ON_FAIL(_sheGlobalLoadPlainKey(client2, server2, ramKey)); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheExportRamKeyRequest(client2)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_SheExportRamKeyResponse(client2, m1, m2, m3, m4, m5)); + WH_TEST_PRINT(" PASS: Shared RAM key, per-server export state\n"); + + /* Cleanup: evict the cached SHE entries (shared global cache, so one + * server suffices) and destroy the NVM objects so later suites and the + * next fixture run start clean. Clients cannot evict SHE-typed cache + * entries, so use the server API directly. */ + { + static const uint8_t evictSlots[] = { + WH_SHE_MASTER_ECU_KEY_ID, WH_SHE_BOOT_MAC_KEY_ID, WH_SHE_BOOT_MAC, + SHE_MC_USER_SLOT, SHE_MC_LOAD_SLOT, SHE_MC_PRIME_SLOT, + SHE_MC_CTR_SLOT, WH_SHE_RAM_KEY_ID, + }; + /* All SHE ids are global here, so the client id argument is moot */ + whNvmId destroyList[] = { + WH_SHE_MAKE_KEYID(0, WH_SHE_SECRET_KEY_ID), + WH_SHE_MAKE_KEYID(0, WH_SHE_MASTER_ECU_KEY_ID), + WH_SHE_MAKE_KEYID(0, WH_SHE_BOOT_MAC_KEY_ID), + WH_SHE_MAKE_KEYID(0, WH_SHE_BOOT_MAC), + WH_SHE_MAKE_KEYID(0, SHE_MC_USER_SLOT), + WH_SHE_MAKE_KEYID(0, SHE_MC_LOAD_SLOT), +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(HAVE_AESGCM) + /* Only created by the keywrap sub-tests above */ + WH_SHE_MAKE_KEYID(0, SHE_MC_CTR_SLOT), +#endif + }; + int32_t rc = 0; + + for (i = 0; i < (int)sizeof(evictSlots); i++) { + ret = wh_Server_KeystoreEvictKey( + server1, + WH_SHE_MAKE_KEYID(client1->comm->client_id, evictSlots[i])); + if (ret != 0 && ret != WH_ERROR_NOTFOUND) { + return ret; + } + } + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmDestroyObjectsRequest( + client1, (whNvmId)(sizeof(destroyList) / sizeof(destroyList[0])), + destroyList)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_NvmDestroyObjectsResponse(client1, &rc)); + WH_TEST_ASSERT_RETURN(rc == 0); + } + + WH_TEST_PRINT("All Global SHE Keys Tests PASSED ===\n"); + return 0; +} + +#endif /* WOLFHSM_CFG_SHE_GLOBAL_KEYS && !WOLFHSM_CFG_NO_CRYPTO */ + /* ============================================================================ * MULTI-CLIENT SEQUENTIAL TEST FRAMEWORK * ========================================================================== */ @@ -1428,6 +1916,11 @@ static int _whTest_MultiClient(void) /* Crypto contexts for both servers */ whServerCryptoContext crypto1[1] = {0}; whServerCryptoContext crypto2[1] = {0}; +#ifdef WOLFHSM_CFG_SHE_EXTENSION + /* SHE contexts for both servers */ + whServerSheContext she1[1]; + whServerSheContext she2[1]; +#endif #endif /* Server 1 configuration */ @@ -1444,6 +1937,9 @@ static int _whTest_MultiClient(void) .nvm = nvm, /* Shared NVM */ #if !defined(WOLFHSM_CFG_NO_CRYPTO) .crypto = crypto1, +#ifdef WOLFHSM_CFG_SHE_EXTENSION + .she = she1, +#endif #endif }}; whServerContext server1[1] = {0}; @@ -1463,6 +1959,9 @@ static int _whTest_MultiClient(void) #if !defined(WOLFHSM_CFG_NO_CRYPTO) .crypto = crypto2, +#ifdef WOLFHSM_CFG_SHE_EXTENSION + .she = she2, +#endif #endif }}; whServerContext server2[1] = {0}; @@ -1471,6 +1970,11 @@ static int _whTest_MultiClient(void) testServer1 = server1; testServer2 = server2; +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(WOLFHSM_CFG_SHE_EXTENSION) + memset(she1, 0, sizeof(she1)); + memset(she2, 0, sizeof(she2)); +#endif + #if !defined(WOLFHSM_CFG_NO_CRYPTO) /* Initialize wolfCrypt */ ret = wolfCrypt_Init(); @@ -1562,6 +2066,11 @@ static int _whTest_MultiClient(void) _runGlobalKeysTests(client1, server1, client2, server2)); #endif +#if defined(WOLFHSM_CFG_SHE_GLOBAL_KEYS) && !defined(WOLFHSM_CFG_NO_CRYPTO) + WH_TEST_RETURN_ON_FAIL( + _runSheGlobalTests(client1, server1, client2, server2)); +#endif + /* Future test suites here */ /* Cleanup */ diff --git a/test-refactor/misc/wh_test_she_keywrap.c b/test-refactor/misc/wh_test_she_keywrap.c index 93b28905d..a443530d2 100644 --- a/test-refactor/misc/wh_test_she_keywrap.c +++ b/test-refactor/misc/wh_test_she_keywrap.c @@ -321,9 +321,9 @@ static int _ShePreProgramKey(TestCtx* t, whNvmId keyId, uint32_t sheFlags, uint8_t label[WH_NVM_LABEL_LEN] = {0}; wh_She_Meta2Label(0, sheFlags, label); - return _NvmAddObject( - t, WH_MAKE_KEYID(WH_KEYTYPE_SHE, t->client->comm->client_id, keyId), 0, - 0, sizeof(label), label, keySz, key); + return _NvmAddObject(t, + WH_SHE_MAKE_KEYID(t->client->comm->client_id, keyId), + 0, 0, sizeof(label), label, keySz, key); } static int _KeyWrapExport(TestCtx* t, whKeyId keyId, uint16_t keyType, @@ -699,11 +699,10 @@ static int _SheKeywrapInterop(TestCtx* t) /* Prime an unused SHE slot via unwrap-and-cache, then use it. */ memset(sheKey, 0x5a, sizeof(sheKey)); blobSz = sizeof(blob); - ret = whTest_BuildSheKeyBlob(whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - t->client->comm->client_id, - SHE_PRIME_SLOT), - 1, 0, sheKey, blob, &blobSz); + ret = whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(t->client->comm->client_id, SHE_PRIME_SLOT), 1, 0, + sheKey, blob, &blobSz); if (ret != 0) { WH_ERROR_PRINT("SHE interop: build prime blob failed %d\n", ret); return ret; @@ -713,6 +712,20 @@ static int _SheKeywrapInterop(TestCtx* t) WH_ERROR_PRINT("SHE unwrap-and-cache failed %d\n", ret); return ret; } + /* Returned id is the slot number, with the global flag set when SHE + * slots are global */ + if ((outId & WH_KEYID_MASK) != SHE_PRIME_SLOT) { + WH_ERROR_PRINT("SHE unwrap-and-cache returned wrong slot 0x%x\n", + (unsigned)outId); + return WH_ERROR_ABORTED; + } +#ifdef WOLFHSM_CFG_SHE_GLOBAL_KEYS + if ((outId & WH_KEYID_CLIENT_GLOBAL_FLAG) == 0) { + WH_ERROR_PRINT("SHE unwrap-and-cache missing global flag 0x%x\n", + (unsigned)outId); + return WH_ERROR_ABORTED; + } +#endif memset(ecbIn, 0x11, sizeof(ecbIn)); ret = _SheEncEcb(t, SHE_PRIME_SLOT, ecbIn, ecbOut, sizeof(ecbIn)); if (ret == 0) { @@ -728,9 +741,8 @@ static int _SheKeywrapInterop(TestCtx* t) * equal-counter prime is accepted. */ wh_She_Meta2Label(5, 0, ctrLabel); ret = _NvmAddObject( - t, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, t->client->comm->client_id, SHE_CTR_SLOT), - 0, 0, sizeof(ctrLabel), ctrLabel, sizeof(sheKey), sheKey); + t, WH_SHE_MAKE_KEYID(t->client->comm->client_id, SHE_CTR_SLOT), 0, 0, + sizeof(ctrLabel), ctrLabel, sizeof(sheKey), sheKey); if (ret != 0) { WH_ERROR_PRINT("SHE interop: seed counter slot failed %d\n", ret); return ret; @@ -739,8 +751,8 @@ static int _SheKeywrapInterop(TestCtx* t) blobSz = sizeof(blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, t->client->comm->client_id, SHE_CTR_SLOT), - 3, 0, sheKey, blob, &blobSz); + WH_SHE_MAKE_KEYID(t->client->comm->client_id, SHE_CTR_SLOT), 3, 0, + sheKey, blob, &blobSz); if (ret != 0) { return ret; } @@ -754,8 +766,8 @@ static int _SheKeywrapInterop(TestCtx* t) blobSz = sizeof(blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, t->client->comm->client_id, SHE_CTR_SLOT), - 5, 0, sheKey, blob, &blobSz); + WH_SHE_MAKE_KEYID(t->client->comm->client_id, SHE_CTR_SLOT), 5, 0, + sheKey, blob, &blobSz); if (ret != 0) { return ret; } @@ -765,6 +777,25 @@ static int _SheKeywrapInterop(TestCtx* t) return (ret != 0) ? ret : WH_ERROR_ABORTED; } +#ifndef WOLFHSM_CFG_SHE_GLOBAL_KEYS + /* A blob carrying a global-namespace SHE id (minted by a global-SHE + * build) must be refused, since no SHE command on this build reads the + * global id and caching it would pin the slot with an unreachable key. */ + blobSz = sizeof(blob); + ret = whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_MAKE_KEYID(WH_KEYTYPE_SHE, WH_KEYUSER_GLOBAL, SHE_PRIME_SLOT), 1, 0, + sheKey, blob, &blobSz); + if (ret != 0) { + return ret; + } + ret = _KeyUnwrapAndCache(t, kekId, blob, blobSz, &outId); + if (ret != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("SHE global-id blob expected ACCESS, got %d\n", ret); + return WH_ERROR_ABORTED; + } +#endif /* !WOLFHSM_CFG_SHE_GLOBAL_KEYS */ + /* LoadKey update of a slot that is primed in cache and committed in NVM: * M4/M5 and later crypto must use the new key, not the stale cached * copy. */ @@ -818,6 +849,48 @@ static int _SheKeywrapInterop(TestCtx* t) } } +#ifdef WOLFHSM_CFG_SHE_GLOBAL_KEYS + /* A blob minted by a per-client build carries USER=client_id; unwrap + * must move it to the global namespace where SHE commands look. */ + { + const whNvmId SHE_LEGACY_SLOT = 8; + + memset(sheKey, 0x5a, sizeof(sheKey)); + blobSz = sizeof(blob); + ret = whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_MAKE_KEYID(WH_KEYTYPE_SHE, t->client->comm->client_id, + SHE_LEGACY_SLOT), + 1, 0, sheKey, blob, &blobSz); + if (ret != 0) { + WH_ERROR_PRINT("SHE interop: build legacy blob failed %d\n", ret); + return ret; + } + ret = _KeyUnwrapAndCache(t, kekId, blob, blobSz, &outId); + if (ret != 0) { + WH_ERROR_PRINT("SHE legacy-blob unwrap failed %d\n", ret); + return ret; + } + if ((outId & WH_KEYID_CLIENT_GLOBAL_FLAG) == 0 || + (outId & WH_KEYID_MASK) != SHE_LEGACY_SLOT) { + WH_ERROR_PRINT("SHE legacy blob not normalized to global " + "(outId 0x%x)\n", + (unsigned)outId); + return WH_ERROR_ABORTED; + } + memset(ecbIn, 0x33, sizeof(ecbIn)); + ret = _SheEncEcb(t, SHE_LEGACY_SLOT, ecbIn, ecbOut, sizeof(ecbIn)); + if (ret == 0) { + ret = + _SheDecEcb(t, SHE_LEGACY_SLOT, ecbOut, ecbBack, sizeof(ecbOut)); + } + if (ret != 0 || memcmp(ecbIn, ecbBack, sizeof(ecbIn)) != 0) { + WH_ERROR_PRINT("SHE legacy-slot ECB round trip failed %d\n", ret); + return (ret != 0) ? ret : WH_ERROR_ABORTED; + } + } +#endif /* WOLFHSM_CFG_SHE_GLOBAL_KEYS */ + /* The client must not be able to evict the server-owned KEK. The * fixture's NVM is torn down with the test, so no further cleanup. */ ret = _KeyEvict(t, kekId); diff --git a/test-refactor/misc/wh_test_she_no_nvm.c b/test-refactor/misc/wh_test_she_no_nvm.c index 4e9cabac7..21952b6f5 100644 --- a/test-refactor/misc/wh_test_she_no_nvm.c +++ b/test-refactor/misc/wh_test_she_no_nvm.c @@ -512,8 +512,7 @@ static int _WrapSheKeys(TestCtx* t, SheNoNvmKey* keys, int n) keys[i].blobSz = (uint16_t)sizeof(keys[i].blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, t->client->comm->client_id, - keys[i].slot), + WH_SHE_MAKE_KEYID(t->client->comm->client_id, keys[i].slot), keys[i].counter, keys[i].flags, keys[i].plain, keys[i].blob, &keys[i].blobSz); if (ret != 0) { diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index e26ec57f5..ba392a053 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -115,6 +115,13 @@ ifeq ($(SHE),1) DEF += -DWOLFHSM_CFG_SHE_EXTENSION endif +# Share all SHE key slots across clients (global SHE device view). Implies +# SHE=1. WOLFHSM_CFG_GLOBAL_KEYS comes from the test config. +ifeq ($(SHE_GLOBAL),1) + DEF += -DWOLFHSM_CFG_SHE_EXTENSION + DEF += -DWOLFHSM_CFG_SHE_GLOBAL_KEYS +endif + # Support an authentication-capable build ifeq ($(AUTH),1) DEF += -DWOLFHSM_CFG_ENABLE_AUTHENTICATION diff --git a/test-refactor/server/wh_test_nvm_optional.c b/test-refactor/server/wh_test_nvm_optional.c index 306d725f1..e7c0d1dcf 100644 --- a/test-refactor/server/wh_test_nvm_optional.c +++ b/test-refactor/server/wh_test_nvm_optional.c @@ -129,7 +129,7 @@ static int _SheKeystoreChecks(whServerContext* server) sheKey[i] = (uint8_t)(0xF0 ^ i); } - sheId = WH_MAKE_KEYID(WH_KEYTYPE_SHE, WH_TEST_DEFAULT_CLIENT_ID, 0x05); + sheId = WH_SHE_MAKE_KEYID(WH_TEST_DEFAULT_CLIENT_ID, 0x05); memset(meta, 0, sizeof(meta)); meta->id = sheId; meta->len = (whNvmSize)WH_SHE_KEY_SZ; @@ -151,9 +151,8 @@ static int _SheKeystoreChecks(whServerContext* server) WH_TEST_ASSERT_RETURN( WH_ERROR_OK == wh_Server_KeystoreReadKey(server, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - WH_TEST_DEFAULT_CLIENT_ID, - WH_SHE_MASTER_ECU_KEY_ID), + WH_SHE_MAKE_KEYID(WH_TEST_DEFAULT_CLIENT_ID, + WH_SHE_MASTER_ECU_KEY_ID), outMeta, outKey, &outSz)); WH_TEST_ASSERT_RETURN(outSz == (uint32_t)WH_SHE_KEY_SZ); allZero = 1; diff --git a/test-refactor/server/wh_test_she_server.c b/test-refactor/server/wh_test_she_server.c index d417873e8..8ef1aa201 100644 --- a/test-refactor/server/wh_test_she_server.c +++ b/test-refactor/server/wh_test_she_server.c @@ -69,8 +69,8 @@ int whTest_SheMasterEcuKeyFallback(whServerContext* server) return WH_ERROR_BADARGS; } - masterEcuKeyId = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - WH_SHE_MASTER_ECU_KEY_ID); + masterEcuKeyId = + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_MASTER_ECU_KEY_ID); /* Fill keyBuf with non-zero to ensure it gets overwritten */ memset(keyBuf, 0xFF, sizeof(keyBuf)); diff --git a/test/Makefile b/test/Makefile index faf251850..2aed5490b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -157,6 +157,13 @@ ifeq ($(SHE),1) DEF += -DWOLFHSM_CFG_SHE_EXTENSION endif +# Share all SHE key slots across clients (global SHE device view). Implies +# SHE=1. WOLFHSM_CFG_GLOBAL_KEYS comes from the test config. +ifeq ($(SHE_GLOBAL),1) + DEF += -DWOLFHSM_CFG_SHE_EXTENSION + DEF += -DWOLFHSM_CFG_SHE_GLOBAL_KEYS +endif + # Enable per-client crypto affinity (HW/SW devId selection) and its unit test ifeq ($(CRYPTO_AFFINITY),1) DEF += -DWOLFHSM_CFG_CRYPTO_AFFINITY diff --git a/test/wh_test_multiclient.c b/test/wh_test_multiclient.c index 796c7df88..6811caf8f 100644 --- a/test/wh_test_multiclient.c +++ b/test/wh_test_multiclient.c @@ -51,6 +51,14 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/cryptocb.h" +#ifdef WOLFHSM_CFG_SHE_EXTENSION +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/cmac.h" +#include "wolfhsm/wh_she_common.h" +#include "wolfhsm/wh_she_crypto.h" +#include "wolfhsm/wh_client_she.h" +#include "wolfhsm/wh_message_she.h" +#endif /* WOLFHSM_CFG_SHE_EXTENSION */ #endif /* !WOLFHSM_CFG_NO_CRYPTO */ #include "wh_test_common.h" @@ -1412,6 +1420,483 @@ static int _runGlobalKeysTests(whClientContext* client1, #endif /* WOLFHSM_CFG_GLOBAL_KEYS */ +/* ============================================================================ + * GLOBAL SHE KEYS TEST SUITE + * + * With WOLFHSM_CFG_SHE_GLOBAL_KEYS all SHE slots live in the global-keys + * namespace, so the two servers (sharing one NVM and its global cache) act as + * one SHE device seen by both clients. Uses the split request/response APIs + * since there is no server thread to pump requests. + * ========================================================================== */ + +#if defined(WOLFHSM_CFG_SHE_GLOBAL_KEYS) && !defined(WOLFHSM_CFG_NO_CRYPTO) + +/* SHE slots used by this suite */ +#define SHE_MC_USER_SLOT 4 +#define SHE_MC_LOAD_SLOT 5 +#define SHE_MC_PRIME_SLOT 8 +#define SHE_MC_CTR_SLOT 9 + +/* Provision a SHE slot in the shared NVM, the way ShePreProgramKey does but + * with the split API. Counter and SHE flags go in the object label. */ +static int _sheGlobalAddNvmKey(whClientContext* client, whServerContext* server, + uint8_t sheSlot, uint32_t counter, + uint32_t sheFlags, const uint8_t* key) +{ + int ret; + int32_t rc = 0; + uint8_t label[WH_NVM_LABEL_LEN] = {0}; + + wh_She_Meta2Label(counter, sheFlags, label); + ret = wh_Client_NvmAddObjectRequest( + client, WH_SHE_MAKE_KEYID(client->comm->client_id, sheSlot), 0, 0, + sizeof(label), label, WH_SHE_KEY_SZ, key); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_NvmAddObjectResponse(client, &rc); + } + if (ret == 0) { + ret = (int)rc; + } + return ret; +} + +/* One ECB encrypt through the given client/server pair */ +static int _sheGlobalEncEcb(whClientContext* client, whServerContext* server, + uint8_t sheSlot, uint8_t* in, uint8_t* out) +{ + int ret = wh_Client_SheEncEcbRequest(client, sheSlot, in, WH_SHE_KEY_SZ); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheEncEcbResponse(client, out, WH_SHE_KEY_SZ); + } + return ret; +} + +/* Software AES-ECB of one block, the expected value for the server results */ +static int _sheGlobalSwEcb(const uint8_t* key, const uint8_t* in, uint8_t* out) +{ + Aes aes[1]; + int ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesSetKey(aes, key, WH_SHE_KEY_SZ, NULL, AES_ENCRYPTION); + if (ret == 0) { + ret = wc_AesEncryptDirect(aes, out, in); + } + wc_AesFree(aes); + } + return ret; +} + +static int _sheGlobalSetUid(whClientContext* client, whServerContext* server, + uint8_t* uid, uint32_t uidSz) +{ + int ret = wh_Client_SheSetUidRequest(client, uid, uidSz); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheSetUidResponse(client); + } + return ret; +} + +static int _sheGlobalLoadPlainKey(whClientContext* client, + whServerContext* server, uint8_t* key) +{ + int ret = wh_Client_SheLoadPlainKeyRequest(client, key, WH_SHE_KEY_SZ); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheLoadPlainKeyResponse(client); + } + return ret; +} + +/* boot MAC digest = CMAC_bootMacKey(zeros || size || bootloader) */ +static int _sheGlobalComputeBootMac(const uint8_t* bootloader, + uint32_t bootloaderSz, + const uint8_t* bootMacKey, + uint8_t* digestOut) +{ + int ret; + Cmac cmac[1]; + uint8_t zeros[WH_SHE_BOOT_MAC_PREFIX_LEN] = {0}; + word32 digestSz = AES_BLOCK_SIZE; + + if ((ret = wc_InitCmac(cmac, bootMacKey, WH_SHE_KEY_SZ, WC_CMAC_AES, + NULL)) != 0) { + return ret; + } + if ((ret = wc_CmacUpdate(cmac, zeros, sizeof(zeros))) != 0) { + return ret; + } + if ((ret = wc_CmacUpdate(cmac, (const uint8_t*)&bootloaderSz, + sizeof(bootloaderSz))) != 0) { + return ret; + } + if ((ret = wc_CmacUpdate(cmac, bootloader, bootloaderSz)) != 0) { + return ret; + } + return wc_CmacFinal(cmac, digestOut, &digestSz); +} + +/* The secure-boot protocol (INIT / UPDATE / FINISH) only has a blocking + * client API, so drive the messages directly and pump the server between + * each step. The bootloader used here fits one UPDATE chunk. */ +static int _sheGlobalSecureBoot(whClientContext* client, + whServerContext* server, uint8_t* bootloader, + uint32_t bootloaderLen) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t dataSz; + uint8_t* respBuf; + + whMessageShe_SecureBootInitRequest* initReq; + whMessageShe_SecureBootUpdateRequest* updateReq; + whMessageShe_SecureBootInitResponse* initResp; + whMessageShe_SecureBootUpdateResponse* updateResp; + whMessageShe_SecureBootFinishResponse* finishResp; + + if (bootloaderLen > + (uint32_t)(WOLFHSM_CFG_COMM_DATA_LEN - + sizeof(whMessageShe_SecureBootUpdateRequest))) { + return WH_ERROR_BADARGS; + } + + respBuf = (uint8_t*)wh_CommClient_GetDataPtr(client->comm); + + /* INIT: announce the bootloader size */ + initReq = (whMessageShe_SecureBootInitRequest*)wh_CommClient_GetDataPtr( + client->comm); + initReq->sz = bootloaderLen; + WH_TEST_RETURN_ON_FAIL(wh_Client_SendRequest( + client, WH_MESSAGE_GROUP_SHE, WH_SHE_SECURE_BOOT_INIT, sizeof(*initReq), + (uint8_t*)initReq)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + ret = wh_Client_RecvResponse(client, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, respBuf); + if (ret != WH_ERROR_OK) { + return ret; + } + initResp = (whMessageShe_SecureBootInitResponse*)respBuf; + if (initResp->rc != WH_SHE_ERC_NO_ERROR) { + return initResp->rc; + } + + /* UPDATE: feed the bootloader (single chunk) */ + updateReq = (whMessageShe_SecureBootUpdateRequest*)wh_CommClient_GetDataPtr( + client->comm); + updateReq->sz = bootloaderLen; + memcpy((uint8_t*)(updateReq + 1), bootloader, bootloaderLen); + WH_TEST_RETURN_ON_FAIL(wh_Client_SendRequest( + client, WH_MESSAGE_GROUP_SHE, WH_SHE_SECURE_BOOT_UPDATE, + (uint16_t)(sizeof(*updateReq) + bootloaderLen), (uint8_t*)updateReq)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + ret = wh_Client_RecvResponse(client, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, respBuf); + if (ret != WH_ERROR_OK) { + return ret; + } + updateResp = (whMessageShe_SecureBootUpdateResponse*)respBuf; + if (updateResp->rc != WH_SHE_ERC_NO_ERROR) { + return updateResp->rc; + } + + /* FINISH: verify the boot MAC */ + WH_TEST_RETURN_ON_FAIL(wh_Client_SendRequest( + client, WH_MESSAGE_GROUP_SHE, WH_SHE_SECURE_BOOT_FINISH, 0, NULL)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server)); + ret = wh_Client_RecvResponse(client, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, respBuf); + if (ret != WH_ERROR_OK) { + return ret; + } + finishResp = (whMessageShe_SecureBootFinishResponse*)respBuf; + return finishResp->rc; +} + +static int _sheGlobalGetStatus(whClientContext* client, whServerContext* server, + uint8_t* sreg) +{ + int ret = wh_Client_SheGetStatusRequest(client); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_SheGetStatusResponse(client, sreg); + } + return ret; +} + +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(HAVE_AESGCM) +static int _sheGlobalUnwrapAndCache(whClientContext* client, + whServerContext* server, uint8_t* blob, + uint16_t blobSz, uint16_t* outId) +{ + /* The KEK is a global CRYPTO key, so unlike SHE slot ids the client must + * name it with the global flag */ + int ret = wh_Client_KeyUnwrapAndCacheRequest( + client, WC_CIPHER_AES_GCM, + WH_CLIENT_KEYID_MAKE_GLOBAL(WH_TEST_MC_WRAP_KEK_ID), blob, blobSz); + if (ret == 0) { + ret = wh_Server_HandleRequestMessage(server); + } + if (ret == 0) { + ret = wh_Client_KeyUnwrapAndCacheResponse(client, WC_CIPHER_AES_GCM, + outId); + } + return ret; +} +#endif /* WOLFHSM_CFG_KEYWRAP && HAVE_AESGCM */ + +static int _runSheGlobalTests(whClientContext* client1, + whServerContext* server1, + whClientContext* client2, + whServerContext* server2) +{ + int ret; + int i; + uint8_t sheUid[WH_SHE_UID_SZ]; + uint8_t secretKey[WH_SHE_KEY_SZ]; + uint8_t masterKey[WH_SHE_KEY_SZ]; + uint8_t bootMacKey[WH_SHE_KEY_SZ]; + uint8_t bootDigest[WH_SHE_KEY_SZ]; + uint8_t bootloader[64]; + uint8_t userKey[WH_SHE_KEY_SZ]; + uint8_t loadKey[WH_SHE_KEY_SZ]; + uint8_t ramKey[WH_SHE_KEY_SZ]; + uint8_t ptIn[WH_SHE_KEY_SZ]; + uint8_t ct1[WH_SHE_KEY_SZ]; + uint8_t ct2[WH_SHE_KEY_SZ]; + uint8_t ctSw[WH_SHE_KEY_SZ]; + uint8_t sreg; + uint8_t m1[WH_SHE_M1_SZ]; + uint8_t m2[WH_SHE_M2_SZ]; + uint8_t m3[WH_SHE_M3_SZ]; + uint8_t m4[WH_SHE_M4_SZ]; + uint8_t m5[WH_SHE_M5_SZ]; + uint8_t m4Out[WH_SHE_M4_SZ]; + uint8_t m5Out[WH_SHE_M5_SZ]; + + WH_TEST_PRINT("Testing Global SHE Keys...\n"); + + for (i = 0; i < (int)sizeof(sheUid); i++) { + sheUid[i] = (uint8_t)i; + } + memset(secretKey, 0xA1, sizeof(secretKey)); + memset(masterKey, 0xA2, sizeof(masterKey)); + memset(bootMacKey, 0xA8, sizeof(bootMacKey)); + memset(bootloader, 0xB7, sizeof(bootloader)); + memset(userKey, 0xA3, sizeof(userKey)); + memset(loadKey, 0xA4, sizeof(loadKey)); + memset(ramKey, 0xA5, sizeof(ramKey)); + memset(ptIn, 0x11, sizeof(ptIn)); + + /* Client 1 provisions the shared SHE slots, including the boot MAC key + * and the expected bootloader digest; same UID on both servers */ + WH_TEST_RETURN_ON_FAIL(_sheGlobalComputeBootMac( + bootloader, sizeof(bootloader), bootMacKey, bootDigest)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_SECRET_KEY_ID, 0, 0, secretKey)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_MASTER_ECU_KEY_ID, 0, 0, masterKey)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_BOOT_MAC_KEY_ID, 0, 0, bootMacKey)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, WH_SHE_BOOT_MAC, 0, 0, bootDigest)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalAddNvmKey(client1, server1, SHE_MC_USER_SLOT, 0, 0, userKey)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSetUid(client1, server1, sheUid, sizeof(sheUid))); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSetUid(client2, server2, sheUid, sizeof(sheUid))); + + /* Both servers secure boot against the keys client 1 provisioned. The + * boot state machine is per server, so each must boot on its own. */ + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSecureBoot(client1, server1, bootloader, sizeof(bootloader))); + sreg = 0; + WH_TEST_RETURN_ON_FAIL(_sheGlobalGetStatus(client1, server1, &sreg)); + WH_TEST_ASSERT_RETURN((sreg & WH_SHE_SREG_BOOT_OK) != 0); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalSecureBoot(client2, server2, bootloader, sizeof(bootloader))); + sreg = 0; + WH_TEST_RETURN_ON_FAIL(_sheGlobalGetStatus(client2, server2, &sreg)); + WH_TEST_ASSERT_RETURN((sreg & WH_SHE_SREG_BOOT_OK) != 0); + WH_TEST_PRINT(" PASS: Both servers secure boot on shared boot keys\n"); + + /* Both clients encrypt with the slot client 1 provisioned */ + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client1, server1, SHE_MC_USER_SLOT, ptIn, ct1)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client2, server2, SHE_MC_USER_SLOT, ptIn, ct2)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(userKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct1, ct2, sizeof(ct1)) == 0); + WH_TEST_ASSERT_RETURN(memcmp(ct1, ctSw, sizeof(ct1)) == 0); + WH_TEST_PRINT(" PASS: Both clients share a provisioned SHE slot\n"); + + /* Client 1 installs a key with the LoadKey protocol; client 2 uses it */ + WH_TEST_RETURN_ON_FAIL(wh_She_GenerateLoadableKey( + SHE_MC_LOAD_SLOT, WH_SHE_MASTER_ECU_KEY_ID, 1, 0, sheUid, loadKey, + masterKey, m1, m2, m3, m4, m5)); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheLoadKeyRequest(client1, m1, m2, m3)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheLoadKeyResponse(client1, m4Out, m5Out)); + WH_TEST_ASSERT_RETURN(memcmp(m4Out, m4, sizeof(m4)) == 0); + WH_TEST_ASSERT_RETURN(memcmp(m5Out, m5, sizeof(m5)) == 0); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client2, server2, SHE_MC_LOAD_SLOT, ptIn, ct2)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(loadKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct2, ctSw, sizeof(ct2)) == 0); + WH_TEST_PRINT(" PASS: LoadKey by client 1 visible to client 2\n"); + +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(HAVE_AESGCM) + { + uint8_t blob[128]; + uint16_t blobSz; + uint16_t outId; + uint16_t expSz = (uint16_t)(WH_KEYWRAP_AES_GCM_HEADER_SIZE + + sizeof(whNvmMetadata) + WH_SHE_KEY_SZ); + uint8_t primeKey[WH_SHE_KEY_SZ]; + uint8_t ctrKey[WH_SHE_KEY_SZ]; + + memset(primeKey, 0xA6, sizeof(primeKey)); + memset(ctrKey, 0xA7, sizeof(ctrKey)); + + /* Wrap-export a SHE slot naming it by raw slot number, no global + * flag, under the shared trusted KEK */ + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExportRequest( + client1, WC_CIPHER_AES_GCM, SHE_MC_USER_SLOT, WH_KEYTYPE_SHE, + WH_CLIENT_KEYID_MAKE_GLOBAL(WH_TEST_MC_WRAP_KEK_ID))); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExportResponse( + client1, WC_CIPHER_AES_GCM, blob, &blobSz)); + WH_TEST_ASSERT_RETURN(blobSz == expSz); + WH_TEST_PRINT(" PASS: Wrap-export of a SHE slot by raw slot id\n"); + + /* Client 1 builds a blob for an unused slot, client 2 primes it, + * client 1 uses it through the shared global cache */ + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(client1->comm->client_id, SHE_MC_PRIME_SLOT), 1, + 0, primeKey, blob, &blobSz)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalUnwrapAndCache(client2, server2, blob, blobSz, &outId)); + WH_TEST_ASSERT_RETURN((outId & WH_KEYID_MASK) == SHE_MC_PRIME_SLOT); + WH_TEST_ASSERT_RETURN((outId & WH_KEYID_CLIENT_GLOBAL_FLAG) != 0); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client1, server1, SHE_MC_PRIME_SLOT, ptIn, ct1)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(primeKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct1, ctSw, sizeof(ct1)) == 0); + WH_TEST_PRINT(" PASS: Cross-client unwrap-and-cache prime\n"); + + /* Counter guard runs against the globally committed slot */ + WH_TEST_RETURN_ON_FAIL(_sheGlobalAddNvmKey( + client1, server1, SHE_MC_CTR_SLOT, 5, 0, ctrKey)); + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(client2->comm->client_id, SHE_MC_CTR_SLOT), 3, 0, + ctrKey, blob, &blobSz)); + ret = _sheGlobalUnwrapAndCache(client2, server2, blob, blobSz, &outId); + if (ret != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("SHE global counter rollback expected ACCESS, got " + "%d\n", + ret); + return (ret == 0) ? WH_ERROR_ABORTED : ret; + } + blobSz = sizeof(blob); + WH_TEST_RETURN_ON_FAIL(whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_SHE_MAKE_KEYID(client2->comm->client_id, SHE_MC_CTR_SLOT), 5, 0, + ctrKey, blob, &blobSz)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalUnwrapAndCache(client2, server2, blob, blobSz, &outId)); + WH_TEST_PRINT(" PASS: Counter guard on a globally committed slot\n"); + } +#endif /* WOLFHSM_CFG_KEYWRAP && HAVE_AESGCM */ + + /* The RAM key is one shared volatile slot, but the plain-loaded state + * that allows exporting it is per server */ + WH_TEST_RETURN_ON_FAIL(_sheGlobalLoadPlainKey(client1, server1, ramKey)); + WH_TEST_RETURN_ON_FAIL( + _sheGlobalEncEcb(client2, server2, WH_SHE_RAM_KEY_ID, ptIn, ct2)); + WH_TEST_RETURN_ON_FAIL(_sheGlobalSwEcb(ramKey, ptIn, ctSw)); + WH_TEST_ASSERT_RETURN(memcmp(ct2, ctSw, sizeof(ct2)) == 0); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheExportRamKeyRequest(client2)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2)); + ret = wh_Client_SheExportRamKeyResponse(client2, m1, m2, m3, m4, m5); + if (ret != WH_SHE_ERC_KEY_INVALID) { + WH_ERROR_PRINT("SHE global RAM key export without plain load expected " + "KEY_INVALID, got %d\n", + ret); + return (ret == 0) ? WH_ERROR_ABORTED : ret; + } + WH_TEST_RETURN_ON_FAIL(_sheGlobalLoadPlainKey(client2, server2, ramKey)); + WH_TEST_RETURN_ON_FAIL(wh_Client_SheExportRamKeyRequest(client2)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_SheExportRamKeyResponse(client2, m1, m2, m3, m4, m5)); + WH_TEST_PRINT(" PASS: Shared RAM key, per-server export state\n"); + + /* Cleanup: evict the cached SHE entries (shared global cache, so one + * server suffices) and destroy the NVM objects so later suites and the + * next fixture run start clean. Clients cannot evict SHE-typed cache + * entries, so use the server API directly. */ + { + static const uint8_t evictSlots[] = { + WH_SHE_MASTER_ECU_KEY_ID, WH_SHE_BOOT_MAC_KEY_ID, WH_SHE_BOOT_MAC, + SHE_MC_USER_SLOT, SHE_MC_LOAD_SLOT, SHE_MC_PRIME_SLOT, + SHE_MC_CTR_SLOT, WH_SHE_RAM_KEY_ID, + }; + /* All SHE ids are global here, so the client id argument is moot */ + whNvmId destroyList[] = { + WH_SHE_MAKE_KEYID(0, WH_SHE_SECRET_KEY_ID), + WH_SHE_MAKE_KEYID(0, WH_SHE_MASTER_ECU_KEY_ID), + WH_SHE_MAKE_KEYID(0, WH_SHE_BOOT_MAC_KEY_ID), + WH_SHE_MAKE_KEYID(0, WH_SHE_BOOT_MAC), + WH_SHE_MAKE_KEYID(0, SHE_MC_USER_SLOT), + WH_SHE_MAKE_KEYID(0, SHE_MC_LOAD_SLOT), +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(HAVE_AESGCM) + /* Only created by the keywrap sub-tests above */ + WH_SHE_MAKE_KEYID(0, SHE_MC_CTR_SLOT), +#endif + }; + int32_t rc = 0; + + for (i = 0; i < (int)sizeof(evictSlots); i++) { + ret = wh_Server_KeystoreEvictKey( + server1, + WH_SHE_MAKE_KEYID(client1->comm->client_id, evictSlots[i])); + if (ret != 0 && ret != WH_ERROR_NOTFOUND) { + return ret; + } + } + WH_TEST_RETURN_ON_FAIL(wh_Client_NvmDestroyObjectsRequest( + client1, (whNvmId)(sizeof(destroyList) / sizeof(destroyList[0])), + destroyList)); + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_NvmDestroyObjectsResponse(client1, &rc)); + WH_TEST_ASSERT_RETURN(rc == 0); + } + + WH_TEST_PRINT("All Global SHE Keys Tests PASSED ===\n"); + return 0; +} + +#endif /* WOLFHSM_CFG_SHE_GLOBAL_KEYS && !WOLFHSM_CFG_NO_CRYPTO */ + /* ============================================================================ * MULTI-CLIENT SEQUENTIAL TEST FRAMEWORK * ========================================================================== */ @@ -1502,6 +1987,11 @@ static int whTest_MultiClientSequential(void) /* Crypto contexts for both servers */ whServerCryptoContext crypto1[1] = {0}; whServerCryptoContext crypto2[1] = {0}; +#ifdef WOLFHSM_CFG_SHE_EXTENSION + /* SHE contexts for both servers */ + whServerSheContext she1[1]; + whServerSheContext she2[1]; +#endif #endif /* Server 1 configuration */ @@ -1518,6 +2008,9 @@ static int whTest_MultiClientSequential(void) .nvm = nvm, /* Shared NVM */ #if !defined(WOLFHSM_CFG_NO_CRYPTO) .crypto = crypto1, +#ifdef WOLFHSM_CFG_SHE_EXTENSION + .she = she1, +#endif #endif }}; whServerContext server1[1] = {0}; @@ -1537,6 +2030,9 @@ static int whTest_MultiClientSequential(void) #if !defined(WOLFHSM_CFG_NO_CRYPTO) .crypto = crypto2, +#ifdef WOLFHSM_CFG_SHE_EXTENSION + .she = she2, +#endif #endif }}; whServerContext server2[1] = {0}; @@ -1545,6 +2041,11 @@ static int whTest_MultiClientSequential(void) testServer1 = server1; testServer2 = server2; +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(WOLFHSM_CFG_SHE_EXTENSION) + memset(she1, 0, sizeof(she1)); + memset(she2, 0, sizeof(she2)); +#endif + #if !defined(WOLFHSM_CFG_NO_CRYPTO) /* Initialize wolfCrypt */ ret = wolfCrypt_Init(); @@ -1636,6 +2137,11 @@ static int whTest_MultiClientSequential(void) _runGlobalKeysTests(client1, server1, client2, server2)); #endif +#if defined(WOLFHSM_CFG_SHE_GLOBAL_KEYS) && !defined(WOLFHSM_CFG_NO_CRYPTO) + WH_TEST_RETURN_ON_FAIL( + _runSheGlobalTests(client1, server1, client2, server2)); +#endif + /* Future test suites here */ /* Cleanup */ diff --git a/test/wh_test_she.c b/test/wh_test_she.c index 09a83295a..c04423d2c 100644 --- a/test/wh_test_she.c +++ b/test/wh_test_she.c @@ -110,7 +110,7 @@ static int _destroySheKey(whClientContext* client, whNvmId clientSheKeyId) int rc = 0; int32_t serverRc = 0; - whNvmId id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, clientSheKeyId); + whNvmId id = WH_SHE_MAKE_KEYID(client->comm->client_id, clientSheKeyId); rc = wh_Client_NvmDestroyObjects(client, 1, &id, &serverRc); if (rc == WH_ERROR_OK) { @@ -612,9 +612,8 @@ int whTest_SheClientConfig(whClientConfig* config) blobSz = sizeof(blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, - SHE_PRIME_SLOT), - 1, 0, sheKey, blob, &blobSz); + WH_SHE_MAKE_KEYID(client->comm->client_id, SHE_PRIME_SLOT), 1, 0, + sheKey, blob, &blobSz); if (ret != 0) { WH_ERROR_PRINT("SHE interop: build prime blob failed %d\n", ret); goto exit; @@ -625,6 +624,22 @@ int whTest_SheClientConfig(whClientConfig* config) WH_ERROR_PRINT("SHE unwrap-and-cache failed %d\n", ret); goto exit; } + /* Returned id is the slot number, with the global flag set when SHE + * slots are global */ + if ((outId & WH_KEYID_MASK) != SHE_PRIME_SLOT) { + WH_ERROR_PRINT("SHE unwrap-and-cache returned wrong slot 0x%x\n", + (unsigned)outId); + ret = WH_ERROR_ABORTED; + goto exit; + } +#ifdef WOLFHSM_CFG_SHE_GLOBAL_KEYS + if ((outId & WH_KEYID_CLIENT_GLOBAL_FLAG) == 0) { + WH_ERROR_PRINT("SHE unwrap-and-cache missing global flag 0x%x\n", + (unsigned)outId); + ret = WH_ERROR_ABORTED; + goto exit; + } +#endif memset(ecbIn, 0x11, sizeof(ecbIn)); ret = wh_Client_SheEncEcb(client, SHE_PRIME_SLOT, ecbIn, ecbOut, sizeof(ecbIn)); @@ -642,12 +657,9 @@ int whTest_SheClientConfig(whClientConfig* config) * slot with counter=5, then check a lower-counter prime is rejected * and an equal-counter prime is accepted. */ wh_She_Meta2Label(5, 0, ctrLabel); - ret = wh_Client_NvmAddObject(client, - WH_MAKE_KEYID(WH_KEYTYPE_SHE, - client->comm->client_id, - SHE_CTR_SLOT), - 0, 0, sizeof(ctrLabel), ctrLabel, - sizeof(sheKey), sheKey, &serverRc); + ret = wh_Client_NvmAddObject( + client, WH_SHE_MAKE_KEYID(client->comm->client_id, SHE_CTR_SLOT), 0, + 0, sizeof(ctrLabel), ctrLabel, sizeof(sheKey), sheKey, &serverRc); if (ret == 0) { ret = serverRc; } @@ -659,9 +671,8 @@ int whTest_SheClientConfig(whClientConfig* config) blobSz = sizeof(blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, - SHE_CTR_SLOT), - 3, 0, sheKey, blob, &blobSz); + WH_SHE_MAKE_KEYID(client->comm->client_id, SHE_CTR_SLOT), 3, 0, + sheKey, blob, &blobSz); if (ret != 0) { goto exit; } @@ -678,9 +689,8 @@ int whTest_SheClientConfig(whClientConfig* config) blobSz = sizeof(blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, - SHE_CTR_SLOT), - 5, 0, sheKey, blob, &blobSz); + WH_SHE_MAKE_KEYID(client->comm->client_id, SHE_CTR_SLOT), 5, 0, + sheKey, blob, &blobSz); if (ret != 0) { goto exit; } @@ -692,6 +702,28 @@ int whTest_SheClientConfig(whClientConfig* config) goto exit; } +#ifndef WOLFHSM_CFG_SHE_GLOBAL_KEYS + /* A blob carrying a global-namespace SHE id (minted by a global-SHE + * build) must be refused, since no SHE command on this build reads + * the global id and caching it would pin the slot with an unreachable + * key. */ + blobSz = sizeof(blob); + ret = whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_MAKE_KEYID(WH_KEYTYPE_SHE, WH_KEYUSER_GLOBAL, SHE_PRIME_SLOT), 1, + 0, sheKey, blob, &blobSz); + if (ret != 0) { + goto exit; + } + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, kekId, + blob, blobSz, &outId); + if (ret != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("SHE global-id blob expected ACCESS, got %d\n", ret); + ret = WH_ERROR_ABORTED; + goto exit; + } +#endif /* !WOLFHSM_CFG_SHE_GLOBAL_KEYS */ + /* LoadKey update of a slot that is primed in cache and committed in * NVM: M4/M5 and later crypto must use the new key, not the stale * cached copy. */ @@ -749,6 +781,53 @@ int whTest_SheClientConfig(whClientConfig* config) } } +#ifdef WOLFHSM_CFG_SHE_GLOBAL_KEYS + /* A blob minted by a per-client build carries USER=client_id; unwrap + * must move it to the global namespace where SHE commands look. */ + { + const whNvmId SHE_LEGACY_SLOT = 8; + + blobSz = sizeof(blob); + ret = whTest_BuildSheKeyBlob( + whTest_KeywrapKek, sizeof(whTest_KeywrapKek), + WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, + SHE_LEGACY_SLOT), + 1, 0, sheKey, blob, &blobSz); + if (ret != 0) { + WH_ERROR_PRINT("SHE interop: build legacy blob failed %d\n", + ret); + goto exit; + } + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, kekId, + blob, blobSz, &outId); + if (ret != 0) { + WH_ERROR_PRINT("SHE legacy-blob unwrap failed %d\n", ret); + goto exit; + } + if ((outId & WH_KEYID_CLIENT_GLOBAL_FLAG) == 0 || + (outId & WH_KEYID_MASK) != SHE_LEGACY_SLOT) { + WH_ERROR_PRINT("SHE legacy blob not normalized to global " + "(outId 0x%x)\n", + (unsigned)outId); + ret = WH_ERROR_ABORTED; + goto exit; + } + memset(ecbIn, 0x33, sizeof(ecbIn)); + ret = wh_Client_SheEncEcb(client, SHE_LEGACY_SLOT, ecbIn, ecbOut, + sizeof(ecbIn)); + if (ret == 0) { + ret = wh_Client_SheDecEcb(client, SHE_LEGACY_SLOT, ecbOut, + ecbBack, sizeof(ecbOut)); + } + if (ret != 0 || memcmp(ecbIn, ecbBack, sizeof(ecbIn)) != 0) { + WH_ERROR_PRINT("SHE legacy-slot ECB round trip failed %d\n", + ret); + ret = (ret != 0) ? ret : WH_ERROR_ABORTED; + goto exit; + } + } +#endif /* WOLFHSM_CFG_SHE_GLOBAL_KEYS */ + /* cleanup: destroy the NVM counter slot. The client must not be able * to evict the server-owned KEK. */ (void)_destroySheKey(client, SHE_CTR_SLOT); @@ -1474,8 +1553,8 @@ static int wh_She_TestMasterEcuKeyFallback(void) WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, s_conf)); WH_TEST_RETURN_ON_FAIL(wh_Server_SetConnected(server, WH_COMM_CONNECTED)); - masterEcuKeyId = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, - WH_SHE_MASTER_ECU_KEY_ID); + masterEcuKeyId = + WH_SHE_MAKE_KEYID(server->comm->client_id, WH_SHE_MASTER_ECU_KEY_ID); /* Fill keyBuf with non-zero to ensure it gets overwritten */ memset(keyBuf, 0xFF, sizeof(keyBuf)); diff --git a/test/wh_test_she_no_nvm.c b/test/wh_test_she_no_nvm.c index 84dd1e7e0..ae52d83ce 100644 --- a/test/wh_test_she_no_nvm.c +++ b/test/wh_test_she_no_nvm.c @@ -173,8 +173,7 @@ static int _WrapSheKeyBlobs(whClientContext* client, SheNoNvmKey* keys, int n) keys[i].blobSz = (uint16_t)sizeof(keys[i].blob); ret = whTest_BuildSheKeyBlob( whTest_KeywrapKek, sizeof(whTest_KeywrapKek), - WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id, - keys[i].slot), + WH_SHE_MAKE_KEYID(client->comm->client_id, keys[i].slot), keys[i].counter, keys[i].flags, keys[i].plain, keys[i].blob, &keys[i].blobSz); if (ret != 0) { diff --git a/tools/whnvmtool/.gitignore b/tools/whnvmtool/.gitignore index 24048b65f..b6f852c7c 100644 --- a/tools/whnvmtool/.gitignore +++ b/tools/whnvmtool/.gitignore @@ -2,4 +2,5 @@ whnvmtool test/test_whnvmtool *.bin *.hex +!test/data/*.bin nvm_metadata.txt diff --git a/tools/whnvmtool/Makefile b/tools/whnvmtool/Makefile index da2ae9fd7..963147bdf 100644 --- a/tools/whnvmtool/Makefile +++ b/tools/whnvmtool/Makefile @@ -110,9 +110,15 @@ test-gen: $(OUT) rm -f whNvmImage.bin whNvmImage.hex ./$(OUT) --test test/nvminit/test.nvminit --invert-erased-byte +# Run the negative tests, where invalid configs and key files must fail the tool. +# Order-only prereq on test-gen: every whnvmtool instance binds the same TCP +# port, so the two tool-running targets must not overlap under make -j. +test-invalid: $(OUT) | test-gen + ./test/test_invalid_input.sh ./$(OUT) + # Run the test suite, which requires first generating the test NVM image check: test -test: $(OUT) test-gen +test: $(OUT) test-gen test-invalid $(MAKE) -C test/ CFLAGS_EXTRA="-DFLASH_ERASED_BYTE=0x00 $(CFLAGS_EXTRA)" WOLFSSL_DIR=$$(realpath $(WOLFSSL_DIR)) cd test && ./test_whnvmtool @@ -140,4 +146,4 @@ clean-test: $(MAKE) -C test clean # PHONY targets -.PHONY: all clean test-gen test +.PHONY: all clean test-gen test-invalid test diff --git a/tools/whnvmtool/README.md b/tools/whnvmtool/README.md index d470d2cb7..7bf62d80b 100644 --- a/tools/whnvmtool/README.md +++ b/tools/whnvmtool/README.md @@ -51,27 +51,64 @@ key