diff --git a/.clang-format b/.clang-format index 67f5f699e..601a9404c 100755 --- a/.clang-format +++ b/.clang-format @@ -2,6 +2,7 @@ Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true AlignEscapedNewlinesLeft: true diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index ff3fbccd6..b3a054040 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -152,6 +152,18 @@ jobs: if: matrix.group == 'wolfcrypt' run: cd test-refactor/posix && make clean && make -j CRYPTO_AFFINITY=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test with clients loading port keys (runs the port key crypto + # test, gated behind WOLFHSM_CFG_PORTKEY_LOAD) + - name: Build and test refactor PORTKEY_LOAD ASAN + if: matrix.group == 'wolfcrypt' + run: cd test-refactor/posix && make clean && make -j PORTKEY_LOAD=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run + + # Build and test port keys alongside DMA (exercises the DMA handlers) + # LMS/XMSS verify-only; full PQ runs in DMA ASAN and THREADSAFE everything + - name: Build and test refactor PORTKEY_LOAD DMA ASAN + if: matrix.group == 'wolfcrypt' + run: cd test-refactor/posix && make clean && make -j PORTKEY_LOAD=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=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 if: matrix.group == 'wolfcrypt' diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 29699cc14..020d34f7b 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -22,8 +22,11 @@ This chapter provides a detailed overview of the high level features that wolfHS - [Key Cache, Key IDs, and NVM Backing Store](#key-cache-key-ids-and-nvm-backing-store) - [Global Keys](#global-keys) - [Wrapped Keys](#wrapped-keys) - - [Hardware-Only Keys](#hardware-only-keys) - - [Supported Components](#supported-components) + - [Port Keys](#port-keys) + - [The Port Key Abstraction](#the-port-key-abstraction) + - [Where Port Keys Can Be Used](#where-port-keys-can-be-used) + - [Port and Server Responsibilities](#port-and-server-responsibilities) + - [Classifying Port Keys](#classifying-port-keys) - [Key Usage Policies](#key-usage-policies) - [Certificate Management](#certificate-management) - [Trusted Root Storage](#trusted-root-storage) @@ -136,16 +139,16 @@ wh_Client_RsaSetKeyId(&rsa, keyId); sigLen = wc_RsaSSL_Sign(msg, msgLen, sig, sizeof(sig), &rsa, &rng); ``` -The same ID can name a key the client just cached, one provisioned into NVM at the factory, or one the server generated and never exported — the client uses it the same way in every case. How IDs are assigned and structured is covered under [Keystore](#keystore). +The same ID can name a key the client just cached, one provisioned into NVM at the factory, or one the server generated and never exported — the client uses it the same way in every case. It can also name a [port key](#port-keys) that the port manages, such as a key held in the crypto hardware itself. How IDs are assigned and structured is covered under [Keystore](#keystore). ### Hardware Acceleration and Crypto Affinity -Many of the platforms wolfHSM targets ship a dedicated crypto accelerator alongside their secure core. The server can use these accelerators per-algorithm through the same crypto callback mechanism: a port-supplied callback, registered at server init, redirects supported operations to the vendor's hardware driver, and anything not implemented in hardware falls back to wolfCrypt software. Which algorithms are accelerated depends on the silicon and is documented in each platform's port. +Many of the platforms wolfHSM targets ship a dedicated crypto accelerator alongside their secure core. The server can use these accelerators per-algorithm through the same crypto callback mechanism: a port-supplied callback, registered at server init, redirects supported operations to the vendor's hardware driver, and anything not implemented in hardware falls back to wolfCrypt software. Which algorithms are accelerated depends on the silicon and is documented in each platform's port. With [port keys](#port-keys), the key can also stay in the accelerator: the server passes it to the port by reference and never reads it. Clients control whether a given crypto request should prefer hardware or software execution through the **crypto affinity** API. This feature is compiled in only when `WOLFHSM_CFG_CRYPTO_AFFINITY` is defined; when it is not, the server always uses its configured device ID and the client API below is unavailable. Affinity is a per-client setting with two values: - `WH_CRYPTO_AFFINITY_HW` (default): the server attempts to execute the operation using the configured hardware crypto device. If the server was not configured with a valid hardware device ID, or if the requested algorithm is not implemented in hardware, the request transparently falls back to wolfCrypt's software implementation. -- `WH_CRYPTO_AFFINITY_SW`: the server always executes the operation using wolfCrypt's software implementation, bypassing any registered hardware device. +- `WH_CRYPTO_AFFINITY_SW`: the server always executes the operation using wolfCrypt's software implementation, bypassing any registered hardware device. Requests that use a [port key](#port-keys) fail with `WH_ERROR_NOTFOUND`, since software has no copy of the key. Affinity is stored on the client and sent in the header of every crypto request, so a change takes effect on the next operation with no extra round-trip. @@ -233,7 +236,7 @@ Client-facing NVM IDs use the same encoding as client-facing `whKeyId` values. - **Bits 0 to 7** contain the numeric object ID from 1 to 255. ID 0 is reserved as an erased sentinel and is rejected by `wh_Client_NvmAddObject`. - **Bit 8** (`WH_KEYID_CLIENT_GLOBAL_FLAG`) selects the shared global namespace when `WOLFHSM_CFG_GLOBAL_KEYS` is defined. Without that define, every NVM function rejects IDs with this flag set and returns `WH_ERROR_BADARGS`. -- **Bits 9 and 10** represent wrapped and hardware key flags. These are invalid for NVM objects, and NVM functions reject IDs with these bits set. Bits above bit 10 are also rejected. +- **Bits 9 and 10** represent wrapped and port key flags. These are invalid for NVM objects, and NVM functions reject IDs with these bits set. Bits above bit 10 are also rejected. On each request, the server expands the client ID to include TYPE = `WH_KEYTYPE_NVM` and USER set to the connection client ID (or 0 for global objects). Responses translate IDs back to the client format. @@ -323,12 +326,12 @@ The keystore is two-tier: a fixed-size **key cache** in server RAM holds the wor Keys are named by a 16-bit identifier (`whKeyId`), which has two forms — a simple one the client uses and a fuller one the server uses internally: -- **Client-side**: Each client gets a dedicated namespace of 255 key identifiers that are specific to and only accessible by that client. These IDs range from `[1, 255]`, where `0` is the reserved sentinel value `WH_KEYID_ERASED` used internally to mark empty key slots (this sentinel value is also used to request a dynamically assigned ID for a key cache operation — see the keystore API documentation for more information). The client can also set a flag bit in the keyId top byte to ask for special handling — bit 8 for a [global key](#global-keys), bit 9 for a [wrapped key](#wrapped-keys), and bit 10 for a [hardware-only key](#hardware-only-keys). That is all a client ever deals with, and the `WH_CLIENT_KEYID_MAKE_*` macros in `wolfhsm/wh_client.h` set those flags for it. +- **Client-side**: Each client gets a dedicated namespace of 255 key identifiers that are specific to and only accessible by that client. These IDs range from `[1, 255]`, where `0` is the reserved sentinel value `WH_KEYID_ERASED` used internally to mark empty key slots (this sentinel value is also used to request a dynamically assigned ID for a key cache operation — see the keystore API documentation for more information). The client can also set a flag bit in the keyId top byte to ask for special handling — bit 8 for a [global key](#global-keys), bit 9 for a [wrapped key](#wrapped-keys), and bit 10 for a [port key](#port-keys). That is all a client ever deals with, and the `WH_CLIENT_KEYID_MAKE_*` macros in `wolfhsm/wh_client.h` set those flags for it. - **Server-side**: internally every key has a globally unique id that also encodes *what* the key is and *who* owns it. When a request arrives, the server expands the client's provided keyId number into this full form, and collapses it back on the way out (`wh_KeyId_TranslateFromClient()` and its inverse). Client code never touches the internal fields. The server-side `whKeyId` packs three fields into its 16 bits: -- **TYPE** (top 4 bits): the kind of object — `WH_KEYTYPE_CRYPTO` for ordinary crypto keys, `WH_KEYTYPE_SHE` for AUTOSAR SHE keys, `WH_KEYTYPE_COUNTER` for monotonic counters, `WH_KEYTYPE_WRAPPED` for wrapped-key metadata, and `WH_KEYTYPE_NVM` for non-key NVM objects (see [Client NVM Access and Per-Client Namespaces](#client-nvm-access-and-per-client-namespaces)). +- **TYPE** (top 4 bits): the kind of object — `WH_KEYTYPE_CRYPTO` for ordinary crypto keys, `WH_KEYTYPE_SHE` for AUTOSAR SHE keys, `WH_KEYTYPE_COUNTER` for monotonic counters, `WH_KEYTYPE_WRAPPED` for wrapped-key metadata, `WH_KEYTYPE_PORT` for [port keys](#port-keys), and `WH_KEYTYPE_NVM` for non-key NVM objects (see [Client NVM Access and Per-Client Namespaces](#client-nvm-access-and-per-client-namespaces)). - **USER** (middle 4 bits): the owning client. Value `0` is reserved for the global-key namespace when `WOLFHSM_CFG_GLOBAL_KEYS` is enabled. - **ID** (low 8 bits): the number the client chose. @@ -400,7 +403,7 @@ The lifecycle exposed to clients consists of four primary operations: - **Unwrap-and-export**: the client supplies a wrapped blob and the KEK's keyId; the server decrypts the blob, authenticates the tag, and returns the recovered metadata and key bytes to the client. This is the operation used by host-side workflows that need to consume the key off-device, for example to inject it into a non-HSM peer. - **Unwrap-and-cache**: the client supplies a wrapped blob and the KEK's keyId; the server decrypts the blob and installs the recovered key directly into the keystore cache as if `wh_Client_KeyCache` had been called locally with the recovered bytes. This is the more common operation in production deployments, since it lets a key live on disk in encrypted form and be hydrated into the HSM at runtime without the plaintext ever transiting the client. Unwrapped keys are cache-only and cannot be committed to NVM. Because this injects a key into the server keystore, the KEK must be a **trusted KEK** (see below). -In every operation the KEK is identified by its existing keyId in the keystore. A software (keystore-resident) KEK must carry the `WH_NVM_FLAGS_USAGE_WRAP` usage flag, enforced server-side by the keystore policy machinery: a key without the `WRAP` usage flag cannot wrap or unwrap — key material or data — regardless of any client request. This usage-flag check does not apply to [hardware-only KEKs](#hardware-only-keys), which carry no `whNvmMetadata`; for them the hardware keystore backend is the policy authority instead. +In every operation the KEK is identified by its existing keyId: a key in the keystore, or a [port key](#port-keys). A software (keystore-resident) KEK must carry the `WH_NVM_FLAGS_USAGE_WRAP` usage flag, enforced server-side by the keystore policy machinery: a key without the `WRAP` usage flag cannot wrap or unwrap — key material or data — regardless of any client request. A port KEK needs the same flag, in the server's record of the port key. A parallel pair of APIs — `wh_Client_DataWrap` and `wh_Client_DataUnwrap` — applies the same construction to arbitrary application data rather than key material. These are useful when a client needs the same authenticated-encryption guarantee for non-key payloads using a key resident in the HSM. Like the key operations, data wrap and unwrap require the KEK to carry `WH_NVM_FLAGS_USAGE_WRAP`; unlike *wrap-export* and *unwrap-and-cache*, they do not require a **trusted KEK**, since no server-held secret crosses the boundary. Data blobs are cryptographically domain-separated from key blobs, so *data unwrap* cannot recover key material wrapped by *wrap* or *wrap-export*, and a *data wrap* blob cannot be injected as a key through *unwrap-and-cache* (see [Cryptographic Domain Separation](#cryptographic-domain-separation)). @@ -410,14 +413,16 @@ A parallel pair of APIs — `wh_Client_DataWrap` and `wh_Client_DataUnwrap` — Two of the operations — **wrap-export** and **unwrap-and-cache** — additionally require the KEK to be a **trusted KEK**, because each moves a *server-held* secret across the client trust boundary: wrap-export extracts one, and unwrap-and-cache injects one. If a client could name a KEK whose plaintext it knows (for example an AES key it cached itself), it could decrypt an extracted blob to recover the target key, or forge a blob to inject a key of its choosing, defeating the wrapped-key confidentiality guarantee. A trusted KEK is one that the client can neither read nor set, and is one of the following: -- a **[hardware-only key](#hardware-only-keys)**: A key stored in hardware in a port-specific manner (see next section) +- a **fixed key**: a [port key](#port-keys) the port put in place, which no client can set or read - a software key carrying the server-only **`WH_NVM_FLAGS_TRUSTED`** flag. -For a software KEK, the `WH_NVM_FLAGS_TRUSTED` flag will be stripped by the server from *every* client request that supplies key or object metadata such that a client can never cause a key or object to carry it. The strip lives in the policy-checked entry points that all client requests funnel through. It can be set only by calling the unchecked keystore/NVM API directly, which is not exposed via the client API but remains usable for server-side code or offline provisioning via `whnvmtool`. A key carrying the flag is treated as unreadable, immutable, non-evictable, and non-exportable through the client API, and is the only cache/NVM key permitted to act as a KEK for wrap-export and unwrap-and-cache. +A port key that a client loaded is never trusted, since the client may know its bytes. + +For a software KEK, the `WH_NVM_FLAGS_TRUSTED` flag will be stripped by the server from *every* client request that supplies key or object metadata such that a client can never cause a key or object to carry it. The strip lives in the policy-checked entry points that all client requests funnel through. It can be set only by calling the unchecked keystore/NVM API directly, which is not exposed via the client API but remains usable for server-side code or offline provisioning via `whnvmtool`. A key carrying the flag is treated as unreadable, immutable, non-evictable, and non-exportable through the client API, and a client can't load it into a port key. It is the only cache/NVM key permitted to act as a KEK for wrap-export and unwrap-and-cache. The ordinary **wrap**, **unwrap-and-export**, and **data wrap/unwrap** operations are not affected by the trusted-KEK requirement, as no server secret crosses the boundary in those; they accept any client-chosen KEK that carries `WH_NVM_FLAGS_USAGE_WRAP`. -For deployment guidance, an NVM-backed system that wishes to use software KEKs for keywrap export should provision the software KEK in an offline manner (e.g. using `whnvmtool` or building the NVM image manually). An NVM-less system has no secure persistent source for software-KEK bytes other than the firmware image, so a **hardware KEK is required** in this case. +For deployment guidance, an NVM-backed system that wishes to use software KEKs for keywrap export should provision the software KEK in an offline manner (e.g. using `whnvmtool` or building the NVM image manually). An NVM-less system has no secure persistent source for software-KEK bytes other than the firmware image, so its trusted KEK **must be a fixed key**, such as a key in OTP or fuses, or one the port derives in hardware at boot. #### Server KEK Best Practices @@ -433,7 +438,7 @@ See the `whnvmtool` README for the concrete flag value and provisioning mechanic **Never grant a KEK any usage beyond `WRAP`** — in particular not `WH_NVM_FLAGS_USAGE_ENCRYPT` or `WH_NVM_FLAGS_USAGE_DECRYPT`, and not the catch-all `WH_NVM_FLAGS_USAGE_ANY`. The server enforces usage flags on every wolfCrypt request through the crypto callback (see [Key Usage Policies](#key-usage-policies)), and that check is the only thing stopping a client from using a KEK as an ordinary cipher key. A wrap blob is just AES-GCM ciphertext under the KEK, so a KEK that also carries `USAGE_DECRYPT` can be fed to a raw AES-GCM decrypt under its keyId — returning the wrapped key's plaintext directly and defeating wrapped-key confidentiality regardless of every other control. A KEK never needs encrypt or decrypt usage to function, so `WRAP`-only is both fully functional and the only safe choice. -**Give each key one role.** A key should be a KEK *or* a general-purpose encryption/signing/derivation key, never both; provision separate keys if an application needs both. Sharing usage across roles is exactly what the crypto callback's usage enforcement exists to prevent. A [hardware KEK](#hardware-only-keys) sidesteps this class of mistake by construction: hardware-only keys carry no usage metadata and are rejected in every operation except keywrap, so they can never be used as a cipher key regardless of configuration. +**Give each key one role.** A key should be a KEK *or* a general-purpose encryption/signing/derivation key, never both; provision separate keys if an application needs both. Sharing usage across roles is exactly what the crypto callback's usage enforcement exists to prevent. The same rule applies to a [port KEK](#port-keys): the port should register it with `WH_NVM_FLAGS_USAGE_WRAP` only, and the server then refuses it as a cipher key. `wh_PortKey_RegisterFixed()` doesn't check this, and every fixed key is trusted, so a fixed KEK that also allows `WH_NVM_FLAGS_USAGE_DECRYPT` is the same decryption oracle. #### Cryptographic Domain Separation @@ -443,30 +448,283 @@ This is what keeps the general-purpose data path from becoming a decryption orac The tag is not stored in or transmitted with the blob; the server and every blob producer derive it from the operation being performed. Any code that builds a blob the server will later unwrap — most commonly an offline provisioning tool that pre-wraps keys for *unwrap-and-cache* — must bind the matching tag as AAD, using `WH_KEYWRAP_AAD_KEY_STR` for key blobs. Because the tag is part of the authenticated wrap format, its byte value is fixed: changing it invalidates every previously produced blob and must not be done once wrapped blobs exist in the field. -### Hardware-Only Keys +### Port Keys + +A **port key** is a key that the port manages. It is usually intrinsic to the device, or stored and managed by its hardware: a key fixed in fuses, OTP or ROM, a device key that a key manager derives at boot, or a key slot in a crypto accelerator. A port key usually names where a key is stored, such as an accelerator key slot register or an indexed ROM slot, rather than the key material itself. For a key fixed in fuses, OTP or ROM, the location and the key are one and the same. + +These keys live outside the wolfHSM [keystore](#keystore), and what they are differs on every platform. Port keys give the port a way to define its platform's keys and key storage locations so that the rest of wolfHSM can use them. A client names a port key by ID, like a keystore key. The server checks the request against the key's owner and usage flags, then usually passes the port a reference to the key instead of key bytes, and the port runs the operation with the key. + +Two build options enable the feature: + +- `WOLFHSM_CFG_PORTKEY` enables port keys. It needs `WOLF_PRIVATE_KEY_ID` in the wolfCrypt build. +- `WOLFHSM_CFG_PORTKEY_LOAD` also lets clients [load keystore keys](#clients-can-load-keys) into port keys. It needs `WOLF_CRYPTO_CB_KEYSTORE` in the wolfCrypt build. + +#### The Port Key Abstraction + +The server keeps track of port keys in a **registry** comprised of a table in the port key front end (`wolfhsm/wh_portkey.h`) with a record for each port key that clients can name. A record holds the key's number, its owner, the usage flags it allows, its size, its access mode, and, for a key loaded from the keystore, the keystore key it came from. The registry never holds any key material. The port fills in the registry at startup with every port key. + +There are two types of port keys: + +- A **fixed key** is a key the port puts in place itself, such as a key in OTP or ROM, a key it derives at boot, or a key a key manager writes into an accelerator key slot. The port sets its owner and usage flags, and no client can clear or replace it. +- A **loadable key** is an empty port key, such as a writable accelerator key slot, that clients can load their own keystore keys into. It needs `WOLFHSM_CFG_PORTKEY_LOAD`. Loading one of a client's keystore keys into a loadable key makes it a **client-loaded key** owned by that client, and clearing the key makes it empty again. + +Each fixed key also has an **access mode**, which says how the server reaches the key: + +- **By reference** (`WH_PORTKEY_ACCESS_BY_REF`): the server passes wolfCrypt a reference to the key, and the port's crypto callback runs the operation with the key where it is. The key's bytes never leave the port. This suits a key the port's hardware holds, such as one in an accelerator key slot. +- **By value** (`WH_PORTKEY_ACCESS_BY_VALUE`): the port supplies the key's bytes through its `GetKey` hook on each use, and the server runs the operation in wolfCrypt as it does with a keystore key, so the crypto callback needs no code for the key. This suits a key the port produces as bytes, such as one it derives from a device secret, reads out of OTP, or gets from a security service. The bytes are wiped after the request, and are never cached, written to NVM, or sent to a client. + +A client-loaded key is always accessed by reference. In either access mode, clients name and use the key the same way. [Classifying Port Keys](#classifying-port-keys) shows how to choose the type and access mode for each of a platform's keys when writing port code. + +A port key with no record, or an empty loadable key, can't be used by the core library, whatever the port holds. The registry lives in server RAM, so the port fills it in again after every reset. It holds up to `WOLFHSM_CFG_PORTKEY_MAX_KEYS` records (default 8), one for each fixed key and loadable key, and is normally shared by every server context, so all clients see the same port keys. + +| | Fixed key | Client-loaded key | +|---|---|---| +| Usable by | every client, or one client, as the port chose | the client that loaded it, or every client if it was loaded with the global flag | +| Usage flags | set by the port | copied from the source keystore key | +| Access mode | by reference or by value, as the port chose | by reference | +| Lasts until | the HSM resets; the port registers it again at startup | its owner clears it, its source key is erased or revoked, or the HSM resets | +| [Trusted KEK](#trusted-keks) | yes, since no client can set or read it | no, since the client may know its bytes | +| Key bytes | stay in the port, unless it is accessed by value | stay in the port | + +For example, the mock port in `test-refactor/misc/wh_test_portkey_crypto.c`, which the examples in this section use, drives an AES engine with eight AES-128 key slots, and port key `n` is slot `n`. At startup it writes a fixed key into slot 2 and registers it as a fixed key for every client, and registers slots 3 and 4 as loadable keys. It keeps its other slots for itself, so they have no record. + +Each layer names a port key in its own way: + +- **Client**: the port key number (1 to 255) with the port key flag (`WH_KEYID_CLIENT_PORT_FLAG`, bit 10) set, normally built with `WH_CLIENT_KEYID_MAKE_PORT(n)`. Unlike keystore key IDs, port key numbers aren't per client: `WH_CLIENT_KEYID_MAKE_PORT(3)` names slot 3 for every client, and the registry decides who may use it. +- **Server**: the server translates the port key flag to the dedicated key type `WH_KEYTYPE_PORT` (see [Key Cache, Key IDs, and NVM Backing Store](#key-cache-key-ids-and-nvm-backing-store)). USER is the calling client, or 0 with the [global flag](#global-keys), and ID is the port key number. The keystore refuses this type, so a port key ID only reaches code that supports port keys. +- **Port**: a key reference in the wolfCrypt key object (see [How The Port Runs the Operations](#how-the-port-runs-the-operations)). + +#### Where Port Keys Can Be Used + +A client uses a port key ID like any other key ID. It binds the ID to a wolfCrypt object with the algorithm's `SetKeyId` call, such as `wh_Client_AesSetKeyId()`, or passes it as the KEK of a [key wrap](#wrapped-keys) call. The following operations accept port keys: + +| Operation | Required Usage Flags | +|---|---| +| AES-ECB, AES-CBC, AES-CTR and AES-GCM | `WH_NVM_FLAGS_USAGE_ENCRYPT` to encrypt, `WH_NVM_FLAGS_USAGE_DECRYPT` to decrypt | +| AES-CMAC, one-shot or multi-step | `WH_NVM_FLAGS_USAGE_SIGN` or `WH_NVM_FLAGS_USAGE_VERIFY` | +| Every key wrap and data wrap operation, as the KEK | `WH_NVM_FLAGS_USAGE_WRAP`. Wrap-export and unwrap-and-cache also need a [trusted KEK](#trusted-keks), so they take only fixed keys | + +Each port documents which of these its hardware runs, and with which port keys. Every other operation refuses a port key ID with `WH_ERROR_ACCESS`. + +An operation using a port key accessed by reference runs in the port or fails, since wolfCrypt has no copy of the key to fall back on in software. A request sent with [`WH_CRYPTO_AFFINITY_SW`](#hardware-acceleration-and-crypto-affinity), or to a server with no crypto device, fails with `WH_ERROR_NOTFOUND`. A key accessed by value is the exception: the server has its bytes, so it runs in software too. + +Using the mock port backend as an example, any client can encrypt with the fixed key in slot 2 by simply referencing it in a crypto operation: + +Assuming the port has registered an arbitrary port key ID 2 with the appropriate usage, the client code to use it looks just like any other crypto request, except the keyId has the port key flag set: + +```c +wc_AesInit(&aes, NULL, WH_CLIENT_DEVID(client)); +wh_Client_AesSetKeyId(&aes, WH_CLIENT_KEYID_MAKE_PORT(2)); /* use port key 2 */ +wc_AesSetIV(&aes, iv); +wc_AesCbcEncrypt(&aes, cipher, plain, sizeof(plain)); +wc_AesFree(&aes); +``` + +#### Port and Server Responsibilities + +Since the core wolfHSM library is hardware agnostic and portable across supported platforms, the port layer is what ultimately knows which port keys exist and how to run operations with them. The port registers available keys with the server, which can then enforce the usage policies when handling requests. Clients can also manually manage loading port keys directly when applicable (e.g. supported by the port) via the direct client port key load API. + +Each port documents: + +- Every port key number, and whether it is a fixed key (with its access mode) or a loadable key (with `WOLFHSM_CFG_PORTKEY_LOAD`) +- the key types and sizes each port key holds, and the operations it supports +- for each fixed key, its owner and usage flags, and whether its value is constant, derived per device, or new at every boot +- which algorithms run in hardware for keys sent in requests or read from the keystore + +##### How The Port Registers Its Keys + +At startup, after `wh_PortKey_Init()`, the port registers each of its own keys as a fixed key with `wh_PortKey_RegisterFixed()`. It passes the key's server key ID, the usage flags the key allows, its size, and its access mode. `WH_KEYUSER_GLOBAL`/`0` in the key ID user field shares the key with every client, whereas a specific client ID limits it to that client. With `WOLFHSM_CFG_PORTKEY_LOAD`, the port also registers each port key clients may load with `wh_PortKey_RegisterLoadable()`, passing only its key ID. A port key ID can be registered only once, and registration fails with `WH_ERROR_NOSPACE` once the registry is full. The server application then binds the front end to every server context through `whServerConfig.portKey`, and clients can use the keys. + +```c +static whPortKeyContext portKey; +whPortKeyConfig portKeyCfg = {.cb = NULL}; /* no backend hooks */ + +wc_CryptoCb_RegisterDevice(PORT_DEVID, Port_CryptoCb, NULL); +wh_PortKey_Init(&portKey, &portKeyCfg); + +/* The port wrote a key into slot 2 at boot. USER 0 shares it with every + * client */ +wh_PortKey_RegisterFixed(&portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 2), + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, + AES_128_KEY_SIZE, WH_PORTKEY_ACCESS_BY_REF); + +/* Clients may load slots 3 and 4 */ +wh_PortKey_RegisterLoadable(&portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 3)); +wh_PortKey_RegisterLoadable(&portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 4)); + +whServerConfig serverCfg = { + /* ... comm_config, nvm, crypto ... */ + .devId = PORT_DEVID, + .portKey = &portKey, +}; +``` + +The server marks a fixed key so that no client can clear or replace it, and treats it as a [trusted KEK](#trusted-keks). A KEK, such as one in OTP, must be registered with `WH_NVM_FLAGS_USAGE_WRAP` alone, or a client could use it to decrypt the keys it wraps (see [Server KEK Best Practices](#server-kek-best-practices)). + +A fixed key accessed by value also needs the `GetKey` hook in the port's `whPortKeyCb` table, which copies the key's bytes for each request: + +```c +/* keyId is the registered keyId. Port_DeriveKek() is the port's own code */ +static int Port_GetKey(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len) +{ + if (*inout_len < AES_256_KEY_SIZE) { + return WH_ERROR_BUFFER_SIZE; + } + if (Port_DeriveKek(WH_KEYID_ID(keyId), out) != 0) { + return WH_ERROR_ABORTED; + } + *inout_len = AES_256_KEY_SIZE; + return WH_ERROR_OK; +} + +static const whPortKeyCb portKeyCb = {.GetKey = Port_GetKey}; +whPortKeyConfig portKeyCfg = {.cb = &portKeyCb}; + +wh_PortKey_Init(&portKey, &portKeyCfg); +/* A KEK derived from a device secret, shared with every client */ +wh_PortKey_RegisterFixed(&portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 5), + WH_NVM_FLAGS_USAGE_WRAP, AES_256_KEY_SIZE, + WH_PORTKEY_ACCESS_BY_VALUE); +``` + +`GetKey` runs with the front end's lock held, so it must not call `wh_PortKey_*` functions, and it must return a key of the registered size. For a key that isn't available yet, it should return a port error code: the server turns `WH_ERROR_NOTREADY` into `WH_ERROR_ABORTED`, since a blocking client would read it as no response yet. Server-side code, such as a custom callback that derives keys from a by-value key, can get the bytes with `wh_PortKey_Read()`, which makes the same checks first. It must never send them to a client. + +##### How The Port Runs the Operations + +For a key accessed by reference, the server never reads the key. It sets up the wolfCrypt object by reference instead, using wolfCrypt's `WOLF_PRIVATE_KEY_ID` feature (which exposes ID setting helper functions like `wc_AesInit_Id()` or `wc_InitCmac_Id()`). In place of a key, the object then holds a **key reference** in `aes->id` or `cmac->id`, written by `wh_PortKey_KeyIdToRef()`. The port gets the port key number back with `wh_PortKey_KeyRefToId()`. For a cryptographic operation, wolfCrypt then passes every operation on the object to the port's crypto callback, which must: + +- Run the operation with the port key the key reference names, when the object has one. If the reference doesn't name a key the port has, return an error such as `WC_HW_E`, never `CRYPTOCB_UNAVAILABLE`, since software has no copy of the key. +- Keep running state in the cryptographic object, such as `aes->reg`, `aes->tmp` and `aes->left`, or the buffer and digest of a `Cmac`, and not in the port. The server rebuilds the object for each request from the state the client sends back, so that a stream such as AES-CTR or multi-step CMAC can span requests. +- Treat an object without a key reference, including one set up with a port key accessed by value, as an ordinary software key: run it in hardware, or return `CRYPTOCB_UNAVAILABLE` to leave it to wolfCrypt. A CMAC request with key bytes (`info->cmac.key`) is a software key whatever `cmac->idLen` holds, because wolfCrypt's one-shot `wc_AesCmacGenerate_ex()` passes the `Cmac` to the callback before initializing it. +- Keep slots it uses for its own purposes, such as for running software keys in hardware, out of reach of clients by never registering them. Otherwise a request's key could overwrite a client's port key. + +```c +/* Slot of the port key a key reference names, or -1. Port key n is + * slot n */ +static int Port_RefToSlot(const byte* ref, word32 refSz) +{ + int id = wh_PortKey_KeyRefToId(ref, refSz); /* 0 if not one */ + + return ((id > 0) && (id < PORT_SLOTS)) ? id : -1; +} + +static int Port_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + switch (info->algo_type) { + case WC_ALGO_TYPE_CIPHER: + /* aes->idLen != 0: run with the slot named by aes->id. Key + * wrap's AES-GCM arrives here too */ + return Port_Aes(info); + case WC_ALGO_TYPE_CMAC: + /* info->cmac.key != NULL: a software key. Otherwise + * cmac->idLen != 0: run with the slot named by cmac->id */ + return Port_Cmac(info); + case WC_ALGO_TYPE_KEYSTORE: + /* Load or clear a slot for a client */ + return Port_Keystore(info); + default: + return CRYPTOCB_UNAVAILABLE; + } +} +``` + +With `WOLFHSM_CFG_THREADSAFE`, the front end's lock (`whPortKeyConfig.lockConfig`) serializes the registry and the keystore requests, but crypto operations run outside it. If several server threads share the accelerator, the port must serialize access to it (see [Crypto Under Concurrency](#crypto-under-concurrency)). The mock port implements every callback described here. + +##### The Server Checks Policy + +For every request that names a port key, the wolfHSM server library checks the key against its record with `wh_PortKey_Resolve()`, which makes the same checks as `wh_PortKey_CheckUse()`: + +- The port must have registered the key, and a client must have loaded a loadable key. +- Its owner must be the calling client, or every client. +- It must allow the operation's [usage flag](#key-usage-policies), as listed in [Where Port Keys Can Be Used](#where-port-keys-can-be-used). +- For wrap-export and unwrap-and-cache, it must be a fixed key. + +Only a request that passes these checks reaches the port's `GetKey` hook or crypto callback. The server also stops clients from clearing or replacing fixed keys, refuses to load a key the port didn't register as loadable, and applies the loading rules below. + +##### Clients Can Load Keys + +Clients don't need pre-provisioned port keys to use the hardware. A port can run keys sent in requests or read from the keystore in hardware as well, for example by loading each one into a spare slot for the operation and clearing it afterwards, or it can leave them to software. Loading lets a client keep one of its keystore keys in a port key instead, for as many operations as it needs. Operations with the loaded key then skip the per-operation keystore read and key load, keep the key bytes out of server memory, and either run in the port or fail. Only the client knows which keys it will reuse, so the port can't make this choice for it. + +Loading needs `WOLFHSM_CFG_PORTKEY_LOAD`, and a port that registers some of its port keys as loadable. A client can then copy a keystore key into a port key with `wh_Client_PortKeyLoadFromKeystore()`, then name the port key like a fixed key: + +```c +#define PORT_AES_KEY WH_CLIENT_KEYID_MAKE_PORT(3) /* loadable slot */ +#define APP_AES_KEY 5 /* keystore key */ + +/* Copy the keystore key into the port key */ +wh_Client_PortKeyLoadFromKeystore(client, PORT_AES_KEY, APP_AES_KEY, NULL); + +wc_AesInit(&aes, NULL, WH_CLIENT_DEVID(client)); +wh_Client_AesSetKeyId(&aes, PORT_AES_KEY); +wc_AesSetIV(&aes, iv); +wc_AesCbcEncrypt(&aes, cipher, plain, sizeof(plain)); +wc_AesFree(&aes); + +/* Clear the slot when done, so other clients can load it */ +wh_Client_PortKeyClear(client, PORT_AES_KEY); +``` + +The server reads the source key with the keystore's usual policy checks, and hands it to the port through wolfCrypt's keystore requests (`WC_ALGO_TYPE_KEYSTORE`), which reach the same crypto callback. The key bytes go straight from the keystore to the port. + +To facilitate client loading, the port must implement the following two wolfCrypt keystore callbacks. The server only sends them for keys the port registered as loadable. + +| Request | Port's job | +|---|---| +| `WC_KEYSTORE_IMPORT_PLAIN` | Write the key (key type `WC_KEYSTORE_KEY_AES`) into the port key, or refuse a key the port can't hold | +| `WC_KEYSTORE_DELETE` | Clear the port key | + +The server then records the requesting client as the key's owner, with the source key's usage flags. The rules: + +- **Ownership.** Until the owner clears the key, no other client can use, load or clear it, and the owner can load it again to replace its key. A key loaded with the global flag, `WH_CLIENT_KEYID_MAKE_GLOBAL(WH_CLIENT_KEYID_MAKE_PORT(n))`, is shared: any client can use it, and clear it using the global flag. This needs [global keys](#global-keys). +- **Picking a key.** With ID 0 (`WH_CLIENT_KEYID_MAKE_PORT(0)`), the server tries each empty loadable key in the order the port registered them, uses the first one the port accepts the key into, and returns its ID in the last argument. +- **Source key.** The source must be a keystore key the client can reach (its own, or a global key), with at least one usage flag, and at most `WOLFHSM_CFG_PORTKEY_MAX_KEY_SIZE` bytes (default 32). A `WH_NVM_FLAGS_NONEXPORTABLE` key can be loaded, since its bytes go to the port and not to a client. A `WH_NVM_FLAGS_TRUSTED` key can't, and a loaded key is never a trusted KEK. To keep a trusted KEK in a port key, the port should register it as a fixed key. +- **Lifetime.** Erasing or revoking the source key clears every port key loaded from it, but evicting it doesn't, so a client can evict the source from the cache once it is loaded. If the port fails to clear a port key, the server marks it empty anyway, so the key can't be used, and returns the port's error. +- **Other calls.** `wh_Client_PortKeyClearAll()` clears every key the client loaded without the global flag; neither it nor `wh_Client_PortKeyClear()` touches a fixed key. `wh_Client_PortKeyGetInfo()` describes any registered port key from its record: its key type, size, owner and usage flags, all 0 for an empty loadable key. A key the port didn't register returns `WH_ERROR_NOTFOUND`. + +#### Classifying Port Keys + +This section is for port authors, and builds on [Port and Server Responsibilities](#port-and-server-responsibilities). Every hardware platform has its own unique mechanism for storing and accessing keys, so the port code must decide how to register each one. The following two questions should be considered when classifying a key: + +1. **Who puts the key in place?** If the port code or hardware does, without any client, then it is a fixed key. This typically includes a key in fuses, OTP, or ROM, a key the port or a key manager derives at boot, or a key written during device provisioning. Conversely, if clients should choose the key at run time from their own keystore keys, the port registers loadable keys for them instead. Loadable keys almost always correspond to general purpose key slots or registers in an accelerator. +2. **For a fixed key, can the hardware use the key where it is, or does it need to obtain the key bytes?** If the hardware that runs the operation selects the key by number, the key is accessed by reference. If the key is bytes that the port reads or computes, it is accessed by value. These correspond to the two "access modes" supplied to a fixed key on registry. + +| The platform has | Type | Access mode | The port provides | +|---|---|---|---| +| A key the accelerator selects by number, from an OTP bank, its own key store, or a slot written at boot | fixed | by reference | a crypto callback that runs each operation with the key the reference names | +| A key the port reads or computes as bytes: OTP the CPU can read, a key derived in software, or a key from a security service | fixed | by value | a `GetKey` hook | +| Writable accelerator key slots | loadable | by reference | a crypto callback that loads and clears the slots, and runs operations with them | + +The examples below show how a port could support common hardware key use cases + +##### Example: OTP Keys and an Accelerator With No Key Storage + +Scenario: A device has an AES accelerator with no key storage of its own, so software must write the key into an input key register for each operation. The device also has an OTP key bank that the CPU can read. To use an OTP key for a AES operation, the port must read the key from OTP and write it into the key register. + +In this case, the port registers each OTP key as a fixed key accessed by value, and supplies a `GetKey` hook that reads the key from OTP. When a client requests an AES operation with one of these keys, the wolfHSM server code checks the request, calls `GetKey`, and sets the key's bytes in its local AES object, which wolfCrypt then passes to the port's crypto callback. The port's callback now sees an ordinary software key (for AES, in `aes->devKey`), writes it into the key register, and runs the operation entirely in hardware. The server wipes the bytes after the request. -Some platforms provide key material that the HSM core can read but that never lives in the wolfHSM keystore at all — KEKs burned into OTP or fuses, keys held by an SoC key-management block, or other hardware-provisioned secrets. The optional **hardware keystore front-end** (`WOLFHSM_CFG_HWKEYSTORE`) lets clients reference such keys as KEKs in [wrapped-key](#wrapped-keys) operations while guaranteeing the material never enters the key cache, never touches NVM, and is never returned to a client. +##### Example: An Accelerator That Selects Keys by Number -The front-end (`wolfhsm/wh_hwkeystore.h`) is a platform-agnostic, fully configurable abstraction over the hardware keystore, following the same backend-callback-table paradigm as other elements of the core library. A backend provides a `whHwKeystoreCb` callback table whose required `GetKey` callback copies a requested key's bytes into a caller-provided buffer on demand, plus optional `Init` and `Cleanup` callbacks for backend setup/teardown. The callback table, an opaque backend context and config (for backend-specific state), and a lock (serializing callback invocations when the backing hardware is shared across server threads under `WOLFHSM_CFG_THREADSAFE`) are described by a `whHwKeystoreConfig` and held in a `whHwKeystoreContext`. The server application initializes the context once with `wh_HwKeystore_Init()` — which binds the callback table and invokes the backend `Init` callback if present — and binds it to one or more server contexts through the optional `hwKeystore` member of `whServerConfig`. +Scenario A: A device has an accelerator that reads keys by number from an OTP bank or from its own key store. The key to use is selected by a register field in the accelerator, and the CPU never sees the key's bytes. +Scenario B: A device has an accelerator with a bank of general purpose key slots. The port wants to reserve one of these slots for exclusive use (e.g. not client-loadable) and loads the key (or a derived key) into one of the accelerator slots to persist for the lifetime of the application. The accelerator can now select that key slot by number, via input register or command. -Clients designate a hardware-only key by setting `WH_KEYID_CLIENT_HW_FLAG` (bit 10) in the request keyId, normally via the `WH_CLIENT_KEYID_MAKE_HW()` macro in `wolfhsm/wh_client.h`. The server translates the flag to the internal key type `WH_KEYTYPE_HW` and routes accordingly: +The port registers each of these keys as a fixed key accessed by reference, and needs no `GetKey` hook. For scenario B, the port would also write the key into the applicable accelerator slot on startup before registration. When a client requests an operation with one of the keys, the server checks the request and passes wolfCrypt a key reference instead of key bytes. The crypto callback gets the port key number from the reference with `wh_PortKey_KeyRefToId()`, selects that key in the accelerator, and runs the operation. The key's bytes never leave the hardware, so an operation with the key either runs in the accelerator or fails. -- In the keywrap operations (key wrap, wrap-export, unwrap-and-export, unwrap-and-cache, and data wrap/unwrap), a hardware-only KEK id causes the server to fetch the KEK from the hardware keystore into a local stack buffer via `wh_HwKeystore_GetKey()`, perform the AES-GCM operation, and zeroize the buffer before returning. The KEK is never cached and never appears in any response. An unwrap-and-cache under a hardware KEK still caches the unwrapped payload since the payload is an ordinary key; only the KEK is hardware-resident. -- Hardware-only keyIds are rejected in every other operation with `WH_ERROR_ACCESS`. +##### Example: Keys Derived in Software -Because hardware-only keys carry no `whNvmMetadata`, the [usage-flag policy machinery](#key-usage-policies) does not apply to them. The largest key the keywrap path will fetch from the backend is bounded by `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` (default 32 bytes, sized for an AES-256 KEK), which should be respected (or enforced), by the port layer. +A device holds a device secret, and the port derives several keys from it in software, such as one KEK for each purpose. No hardware holds the derived keys. -#### Supported Components +The port registers each derived key as a fixed key accessed by value, with a `GetKey` hook that derives the key. The server then runs operations with the key in wolfCrypt, in software or with whatever acceleration the crypto callback gives keys sent in requests. Keys from a security service, such as a secure element or a trusted execution environment that releases key bytes, fit the same way, as long as they can be accessed via `GetKey` callback. -Hardware-only keys can be consumed by exactly one wolfHSM component today — the **[keywrap](#wrapped-keys) API**, where a `WH_CLIENT_KEYID_MAKE_HW()` id names the KEK. The supported operations are: +##### Example: Accelerator Key Slots for Client Keys -- **Key wrap** (`wh_Client_KeyWrap`) — wrap a client-supplied key under the hardware-resident KEK. -- **Key wrap-export** (`wh_Client_KeyWrapExport`) — wrap a server-resident key named by id under the hardware KEK, without exposing its plaintext. A hardware KEK is inherently a [trusted KEK](#trusted-keks), which this operation requires. -- **Key unwrap-and-export** (`wh_Client_KeyUnwrapAndExport`) — unwrap a blob and return the recovered key to the client. -- **Key unwrap-and-cache** (`wh_Client_KeyUnwrapAndCache`) — unwrap a blob into the key cache; only the KEK is hardware-resident, while the unwrapped payload becomes an ordinary cached key. -- **Data wrap and unwrap** (`wh_Client_DataWrap`, `wh_Client_DataUnwrap`) — apply the same authenticated encryption to arbitrary application data. +A device has an accelerator with writable key slots, which keep a key across operations. Clients have keystore keys that they use for many operations, and want those operations to run in hardware without the server reading the key from the keystore and writing it into the accelerator for every request. -No other component can name a hardware-only key: a direct wolfCrypt operation through the crypto callback, or any keystore lifecycle operation (cache, export, commit, evict, erase, revoke), is rejected with `WH_ERROR_ACCESS`. **Support is limited to keywrap KEK usage for now** — broader use, such as referencing a hardware-only key directly in a crypto operation, may be added in a future release. +With `WOLFHSM_CFG_PORTKEY_LOAD`, the port registers some of the slots as loadable keys. A client can then load one of its keystore keys into a slot with `wh_Client_PortKeyLoadFromKeystore()`, use the slot's port key ID for as many operations as it needs, and then clear the slot when done (see [Clients Can Load Keys](#clients-can-load-keys)). The crypto callback writes and clears the slots for the server's keystore requests (`WC_ALGO_TYPE_KEYSTORE`), and runs operations with them by reference. Slots the port keeps for itself, such as a scratch slot for keys sent by the client inline in a request, stay unregistered. Note that fixed and loadable keys can share an accelerator, as in the mock port: it writes a fixed key into slot 2 at startup, and registers slots 3 and 4 as loadable keys. ### Key Usage Policies @@ -485,11 +743,11 @@ The usage flags constrain which cryptographic operations a given key may partici Multiple usage flags may be combined, and `WH_NVM_FLAGS_USAGE_ANY` is a convenience constant equal to the bitwise OR of all USAGE bits. A key whose metadata carries no USAGE bits at all is treated as not permitted for any cryptographic use — attempting to use it returns `WH_ERROR_USAGE`. This is intentional: a default-zero metadata does not silently grant access; the application must explicitly opt in to each operation a key may perform. -Policy enforcement happens server-side on every relevant operation. Every wolfCrypt request that flows through the crypto callback routes through the keystore's usage check before the underlying primitive runs, and the operation is rejected with `WH_ERROR_USAGE` if the flag for that operation is not set. Because enforcement is server-side, and because the metadata is bound into the wrapped blob for wrapped keys, no client request can bypass it. +Policy enforcement happens server-side on every relevant operation. Every wolfCrypt request that flows through the crypto callback routes through the keystore's usage check before the underlying primitive runs, and the operation is rejected with `WH_ERROR_USAGE` if the flag for that operation is not set. Because enforcement is server-side, and because the metadata is bound into the wrapped blob for wrapped keys, no client request can bypass it. Requests that use a [port key](#port-keys) are checked the same way, against the usage flags in the server's record of the key: those of the source key for a key a client loaded, or those the port registered for a fixed key. Lifecycle and usage flags are bound to a key at the moment it is first cached or generated and travel with the key into NVM on commit. They cannot be edited in place through the standard client API — once a key has been created with `USAGE_SIGN` only, the only way to also grant `USAGE_VERIFY` is to erase the key and recreate it (or, for a public verification key, to extract the public half through the public-only export path and use it as an independent key). This non-editability is what makes the policy useful as a security control: an attacker who compromises a client cannot loosen the policy on an already-provisioned key. -The keystore additionally provides a **revocation** operation (`wh_Client_KeyRevoke`) that clears all `USAGE_*` bits and sets `WH_NVM_FLAGS_NONMODIFIABLE` on a key without destroying the underlying storage. After revocation, every cryptographic use of the key returns `WH_ERROR_USAGE`, and the key cannot be re-enabled. Revocation is persisted to NVM for committed keys and survives reset; for cache-only keys, eviction has the same effect since the key cannot be reloaded. This makes revocation useful for emergency rotation (taking a compromised signing key out of circulation without immediately reclaiming its NVM slot) and for staged decommissioning, while still leaving the key bytes in place for audit or forensic recovery. +The keystore additionally provides a **revocation** operation (`wh_Client_KeyRevoke`) that clears all `USAGE_*` bits and sets `WH_NVM_FLAGS_NONMODIFIABLE` on a key without destroying the underlying storage. After revocation, every cryptographic use of the key returns `WH_ERROR_USAGE`, and the key cannot be re-enabled. Revocation is persisted to NVM for committed keys and survives reset; for cache-only keys, eviction has the same effect since the key cannot be reloaded. This makes revocation useful for emergency rotation (taking a compromised signing key out of circulation without immediately reclaiming its NVM slot) and for staged decommissioning, while still leaving the key bytes in place for audit or forensic recovery. With `WOLFHSM_CFG_PORTKEY_LOAD`, revoking or erasing a key also clears every [port key](#port-keys) loaded from it. Concrete examples of policy-driven scenarios that this machinery supports: diff --git a/docs/src/9-Configuration.md b/docs/src/9-Configuration.md index 91ed0b00b..09e9ada00 100644 --- a/docs/src/9-Configuration.md +++ b/docs/src/9-Configuration.md @@ -76,8 +76,10 @@ These macros enable or tune optional cryptographic subsystems built on top of wo | `WOLFHSM_CFG_KEYWRAP` | Undefined | If defined, compile the key-wrap subsystem (`wh_Client_KeyWrap*` / server counterparts). Uses AES-GCM internally and therefore requires wolfCrypt built with AES and `HAVE_AESGCM`. Incompatible with `WOLFHSM_CFG_NO_CRYPTO`. | | `WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE` | `2000`, or `WOLFHSM_CFG_COMM_DATA_LEN` minus the request overhead when that is smaller | Maximum size, in bytes, of a key that can be wrapped or unwrapped in a single operation. Only consulted when `WOLFHSM_CFG_KEYWRAP` is defined. The key plus its request header and metadata must fit within `WOLFHSM_CFG_COMM_DATA_LEN`; the default is derived so that it always does, and an explicit value that does not fit fails to build. | | `WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE` | `2000`, or `WOLFHSM_CFG_COMM_DATA_LEN` minus the request overhead when that is smaller | Maximum size, in bytes, of the plaintext or wrapped payload carried by a single key-wrap request. Only consulted when `WOLFHSM_CFG_KEYWRAP` is defined. The payload plus its request header must fit within `WOLFHSM_CFG_COMM_DATA_LEN`; the default is derived so that it always does, and an explicit value that does not fit fails to build. | -| `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_PORTKEY` | Undefined | If defined, compile port key support: the port key front end (`wh_PortKey_*`), the `WH_KEYTYPE_PORT` key type and `WH_CLIENT_KEYID_MAKE_PORT()`. The port registers port keys, and clients use them in AES, CMAC and key wrap operations, which the port's crypto callback runs by key reference. Requires `WOLF_PRIVATE_KEY_ID` in wolfCrypt. See [Port Keys](5-Features.md#port-keys). | +| `WOLFHSM_CFG_PORTKEY_LOAD` | Undefined | If defined, clients can also load keystore keys into port keys, clear them, and describe port keys. Requires `WOLFHSM_CFG_PORTKEY`, and `WOLF_CRYPTO_CB_KEYSTORE` in wolfCrypt. Incompatible with `WOLFHSM_CFG_NO_CRYPTO`. | +| `WOLFHSM_CFG_PORTKEY_MAX_KEYS` | `8` | Number of port keys the port can register, counting fixed keys and loadable keys. Only consulted when `WOLFHSM_CFG_PORTKEY` is defined. | +| `WOLFHSM_CFG_PORTKEY_MAX_KEY_SIZE` | `32` | Largest keystore key, in bytes, a client can load into a port key; sizes the local buffer that holds the key during the load. Only consulted when `WOLFHSM_CFG_PORTKEY_LOAD` 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). | diff --git a/src/wh_client.c b/src/wh_client.c index b859e40d8..39c24aed7 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -1450,6 +1450,239 @@ int wh_Client_KeyRevoke(whClientContext* c, whKeyId keyId) return ret; } +int wh_Client_PortKeyLoadFromKeystoreRequest(whClientContext* c, + whKeyId portKeyId, + whKeyId srcKeyId) +{ + whMessageKeystore_PortLoadRequest* req = NULL; + + if ((c == NULL) || WH_KEYID_ISERASED(srcKeyId)) { + return WH_ERROR_BADARGS; + } + + req = (whMessageKeystore_PortLoadRequest*)wh_CommClient_GetDataPtr(c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + memset(req, 0, sizeof(*req)); + req->portKeyId = portKeyId; + req->srcId = srcKeyId; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_PORT_LOAD, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_PortKeyLoadFromKeystoreResponse(whClientContext* c, + whKeyId* outPortKeyId) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_PortLoadResponse* resp = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + resp = + (whMessageKeystore_PortLoadResponse*)wh_CommClient_GetDataPtr(c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(c, &group, &action, &size, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == 0) { + if (size < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + else if (resp->rc != 0) { + ret = resp->rc; + } + else if (outPortKeyId != NULL) { + *outPortKeyId = resp->portKeyId; + } + } + return ret; +} + +int wh_Client_PortKeyLoadFromKeystore(whClientContext* c, whKeyId portKeyId, + whKeyId srcKeyId, whKeyId* outPortKeyId) +{ + int ret; + ret = wh_Client_PortKeyLoadFromKeystoreRequest(c, portKeyId, srcKeyId); + if (ret == 0) { + do { + ret = wh_Client_PortKeyLoadFromKeystoreResponse(c, outPortKeyId); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +/* ID 0 clears every key the client loaded */ +static int _PortKeyClearRequest(whClientContext* c, whKeyId portKeyId) +{ + whMessageKeystore_PortClearRequest* req = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + req = + (whMessageKeystore_PortClearRequest*)wh_CommClient_GetDataPtr(c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + memset(req, 0, sizeof(*req)); + req->portKeyId = portKeyId; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_PORT_CLEAR, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_PortKeyClearRequest(whClientContext* c, whKeyId portKeyId) +{ + if (WH_KEYID_ISERASED(portKeyId)) { + return WH_ERROR_BADARGS; + } + return _PortKeyClearRequest(c, portKeyId); +} + +int wh_Client_PortKeyClearResponse(whClientContext* c) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_PortClearResponse* resp = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + resp = + (whMessageKeystore_PortClearResponse*)wh_CommClient_GetDataPtr(c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(c, &group, &action, &size, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == 0) { + if (size < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + else if (resp->rc != 0) { + ret = resp->rc; + } + } + return ret; +} + +int wh_Client_PortKeyClear(whClientContext* c, whKeyId portKeyId) +{ + int ret; + ret = wh_Client_PortKeyClearRequest(c, portKeyId); + if (ret == 0) { + do { + ret = wh_Client_PortKeyClearResponse(c); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +int wh_Client_PortKeyClearAllRequest(whClientContext* c) +{ + return _PortKeyClearRequest(c, WH_KEYID_ERASED); +} + +int wh_Client_PortKeyClearAllResponse(whClientContext* c) +{ + return wh_Client_PortKeyClearResponse(c); +} + +int wh_Client_PortKeyClearAll(whClientContext* c) +{ + int ret; + ret = wh_Client_PortKeyClearAllRequest(c); + if (ret == 0) { + do { + ret = wh_Client_PortKeyClearAllResponse(c); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +int wh_Client_PortKeyGetInfoRequest(whClientContext* c, whKeyId portKeyId) +{ + whMessageKeystore_PortInfoRequest* req = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageKeystore_PortInfoRequest*)wh_CommClient_GetDataPtr(c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + memset(req, 0, sizeof(*req)); + req->portKeyId = portKeyId; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_PORT_INFO, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_PortKeyGetInfoResponse(whClientContext* c, whPortKeyInfo* info) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_PortInfoResponse* resp = NULL; + + if ((c == NULL) || (info == NULL)) { + return WH_ERROR_BADARGS; + } + + resp = + (whMessageKeystore_PortInfoResponse*)wh_CommClient_GetDataPtr(c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(c, &group, &action, &size, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == 0) { + if (size < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + else if (resp->rc != 0) { + ret = resp->rc; + } + else { + info->keyType = resp->keyType; + info->keyBits = resp->keyBits; + info->flags = resp->flags; + info->owner = resp->owner; + } + } + return ret; +} + +int wh_Client_PortKeyGetInfo(whClientContext* c, whKeyId portKeyId, + whPortKeyInfo* info) +{ + int ret; + ret = wh_Client_PortKeyGetInfoRequest(c, portKeyId); + if (ret == 0) { + do { + ret = wh_Client_PortKeyGetInfoResponse(c, info); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + int wh_Client_CounterInitRequest(whClientContext* c, whNvmId counterId, uint32_t counter) { diff --git a/src/wh_hwkeystore.c b/src/wh_hwkeystore.c deleted file mode 100644 index 69f1b04ae..000000000 --- a/src/wh_hwkeystore.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2026 wolfSSL Inc. - * - * This file is part of wolfHSM. - * - * wolfHSM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfHSM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with wolfHSM. If not, see . - */ -/* - * src/wh_hwkeystore.c - * - * Hardware keystore front-end implementation. Dispatches to a backend callback - * table (whHwKeystoreCb) with argument validation and lock serialization. See - * wolfhsm/wh_hwkeystore.h for the module description. - */ - -/* Pick up compile-time configuration */ -#include "wolfhsm/wh_settings.h" - -#ifdef WOLFHSM_CFG_HWKEYSTORE - -#include -#include -#include - -#include "wolfhsm/wh_error.h" -#include "wolfhsm/wh_hwkeystore.h" - -int wh_HwKeystore_Init(whHwKeystoreContext* context, - const whHwKeystoreConfig* config) -{ - if ((context == NULL) || (config == NULL) || (config->cb == NULL) || - (config->cb->GetKey == NULL)) { - return WH_ERROR_BADARGS; - } - - memset(context, 0, sizeof(*context)); - context->cb = config->cb; - context->context = config->context; - - /* Initialize the lock before the backend so that, on a backend Init - * failure, teardown is a simple lock cleanup with no backend state to - * undo. This keeps init and cleanup symmetric: wh_HwKeystore_Cleanup tears - * down the backend then the lock, the reverse of the order here */ -#ifdef WOLFHSM_CFG_THREADSAFE - { - int rc = wh_Lock_Init(&context->lock, config->lockConfig); - if (rc != WH_ERROR_OK) { - memset(context, 0, sizeof(*context)); - return rc; - } - } -#endif /* WOLFHSM_CFG_THREADSAFE */ - - /* Initialize the backend if it provides an Init callback. Done last so it - * is the only fallible step after the lock: on failure, undo the lock so a - * successful backend Init is never left without a paired Cleanup */ - if (context->cb->Init != NULL) { - int rc = context->cb->Init(context->context, config->config); - if (rc != WH_ERROR_OK) { -#ifdef WOLFHSM_CFG_THREADSAFE - (void)wh_Lock_Cleanup(&context->lock); -#endif /* WOLFHSM_CFG_THREADSAFE */ - memset(context, 0, sizeof(*context)); - return rc; - } - } - - context->initialized = 1; - return WH_ERROR_OK; -} - -int wh_HwKeystore_Cleanup(whHwKeystoreContext* context) -{ - int rc = WH_ERROR_OK; - - if (context == NULL) { - return WH_ERROR_BADARGS; - } - - /* Capture the backend teardown result but always finish releasing the lock - * and zeroing the context, then surface the backend error. */ - if ((context->cb != NULL) && (context->cb->Cleanup != NULL)) { - rc = context->cb->Cleanup(context->context); - } - -#ifdef WOLFHSM_CFG_THREADSAFE - { - /* Only let a lock-release failure surface when the backend cleanup - * itself succeeded, so a real backend error is not hidden */ - int relRc = wh_Lock_Cleanup(&context->lock); - if (rc == WH_ERROR_OK) { - rc = relRc; - } - } -#endif /* WOLFHSM_CFG_THREADSAFE */ - - memset(context, 0, sizeof(*context)); - return rc; -} - -int wh_HwKeystore_GetKey(whHwKeystoreContext* context, whKeyId keyId, - uint8_t* out, uint16_t* inout_len) -{ - int rc; - - if ((context == NULL) || (context->initialized == 0) || - (context->cb == NULL) || (context->cb->GetKey == NULL) || - (out == NULL) || (inout_len == NULL)) { - return WH_ERROR_BADARGS; - } - -#ifdef WOLFHSM_CFG_THREADSAFE - rc = wh_Lock_Acquire(&context->lock); - if (rc != WH_ERROR_OK) { - return rc; - } -#endif /* WOLFHSM_CFG_THREADSAFE */ - - rc = context->cb->GetKey(context->context, keyId, out, inout_len); - -#ifdef WOLFHSM_CFG_THREADSAFE - { - /* Preserve the backend result; only surface a release failure when the - * GetKey call itself succeeded so a real lock error is not hidden */ - int relRc = wh_Lock_Release(&context->lock); - if (rc == WH_ERROR_OK) { - rc = relRc; - } - } -#endif /* WOLFHSM_CFG_THREADSAFE */ - - return rc; -} - -#endif /* WOLFHSM_CFG_HWKEYSTORE */ diff --git a/src/wh_keyid.c b/src/wh_keyid.c index eb26ce0ca..b6d0dc52b 100644 --- a/src/wh_keyid.c +++ b/src/wh_keyid.c @@ -45,11 +45,11 @@ whKeyId wh_KeyId_TranslateFromClient(uint16_t type, uint16_t clientId, } #endif -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Convert hardware-only flag to TYPE=WH_KEYTYPE_HW. Checked after - * the wrapped flag so HW wins if a client sets both */ - if ((reqId & WH_KEYID_CLIENT_HW_FLAG) != 0) { - type = WH_KEYTYPE_HW; +#ifdef WOLFHSM_CFG_PORTKEY + /* Convert the port key flag to TYPE=WH_KEYTYPE_PORT. Checked after + * the wrapped flag so the port key flag wins if a client sets both */ + if ((reqId & WH_KEYID_CLIENT_PORT_FLAG) != 0) { + type = WH_KEYTYPE_PORT; } #endif @@ -59,9 +59,9 @@ whKeyId wh_KeyId_TranslateFromClient(uint16_t type, uint16_t clientId, whKeyId wh_KeyId_TranslateObjectIdFromClient(uint16_t type, uint16_t clientId, whKeyId reqId) { - /* Strip wrapped and hardware flags so fixed types are preserved. */ + /* Strip wrapped and port key flags so fixed types are preserved. */ reqId &= - (whKeyId) ~(WH_KEYID_CLIENT_WRAPPED_FLAG | WH_KEYID_CLIENT_HW_FLAG); + (whKeyId) ~(WH_KEYID_CLIENT_WRAPPED_FLAG | WH_KEYID_CLIENT_PORT_FLAG); return wh_KeyId_TranslateFromClient(type, clientId, reqId); } @@ -72,8 +72,8 @@ int wh_KeyId_CheckClientObjectId(whKeyId reqId) 0) { return WH_ERROR_BADARGS; } - /* Wrapped and hardware flags are not valid for fixed-type objects */ - if ((reqId & (WH_KEYID_CLIENT_WRAPPED_FLAG | WH_KEYID_CLIENT_HW_FLAG)) != + /* Wrapped and port key flags are not valid for fixed-type objects */ + if ((reqId & (WH_KEYID_CLIENT_WRAPPED_FLAG | WH_KEYID_CLIENT_PORT_FLAG)) != 0) { return WH_ERROR_BADARGS; } @@ -113,10 +113,10 @@ whKeyId wh_KeyId_TranslateToClient(whKeyId serverId) } #endif -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Convert TYPE=HW to hardware-only flag */ - if (WH_KEYID_TYPE(serverId) == WH_KEYTYPE_HW) { - clientId |= WH_KEYID_CLIENT_HW_FLAG; +#ifdef WOLFHSM_CFG_PORTKEY + /* Convert TYPE=PORT to the port key flag */ + if (WH_KEYID_TYPE(serverId) == WH_KEYTYPE_PORT) { + clientId |= WH_KEYID_CLIENT_PORT_FLAG; } #endif diff --git a/src/wh_message_keystore.c b/src/wh_message_keystore.c index 5aa467c12..03e00abe8 100644 --- a/src/wh_message_keystore.c +++ b/src/wh_message_keystore.c @@ -249,6 +249,78 @@ int wh_MessageKeystore_TranslateRevokeResponse( return 0; } +int wh_MessageKeystore_TranslatePortLoadRequest( + uint16_t magic, const whMessageKeystore_PortLoadRequest* src, + whMessageKeystore_PortLoadRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, portKeyId); + WH_T16(magic, dest, src, srcId); + return 0; +} + +int wh_MessageKeystore_TranslatePortLoadResponse( + uint16_t magic, const whMessageKeystore_PortLoadResponse* src, + whMessageKeystore_PortLoadResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + WH_T16(magic, dest, src, portKeyId); + return 0; +} + +int wh_MessageKeystore_TranslatePortClearRequest( + uint16_t magic, const whMessageKeystore_PortClearRequest* src, + whMessageKeystore_PortClearRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, portKeyId); + return 0; +} + +int wh_MessageKeystore_TranslatePortClearResponse( + uint16_t magic, const whMessageKeystore_PortClearResponse* src, + whMessageKeystore_PortClearResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + return 0; +} + +int wh_MessageKeystore_TranslatePortInfoRequest( + uint16_t magic, const whMessageKeystore_PortInfoRequest* src, + whMessageKeystore_PortInfoRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, portKeyId); + return 0; +} + +int wh_MessageKeystore_TranslatePortInfoResponse( + uint16_t magic, const whMessageKeystore_PortInfoResponse* src, + whMessageKeystore_PortInfoResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + WH_T32(magic, dest, src, keyType); + WH_T32(magic, dest, src, keyBits); + WH_T16(magic, dest, src, flags); + WH_T16(magic, dest, src, owner); + return 0; +} + #ifdef WOLFHSM_CFG_DMA /* * DMA-based keystore operations diff --git a/src/wh_portkey.c b/src/wh_portkey.c new file mode 100644 index 000000000..c308d6620 --- /dev/null +++ b/src/wh_portkey.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * src/wh_portkey.c + * + * Port key front end: the records of the port keys the port + * registered, and the calls into the port's keystore callback. See + * wolfhsm/wh_portkey.h for the module description. + */ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#ifdef WOLFHSM_CFG_PORTKEY + +#include +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_utils.h" +#include "wolfhsm/wh_portkey.h" + +#ifdef WOLFHSM_CFG_PORTKEY_LOAD +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/cryptocb.h" +#include "wolfssl/wolfcrypt/wc_keystore.h" +#endif + +int wh_PortKey_Init(whPortKeyContext* context, const whPortKeyConfig* config) +{ + if ((context == NULL) || (config == NULL)) { + return WH_ERROR_BADARGS; + } + + memset(context, 0, sizeof(*context)); + context->cb = config->cb; + context->context = config->context; + + /* Initialize the lock before the backend so that, on a backend Init + * failure, teardown is a simple lock cleanup with no backend state to + * undo. This keeps init and cleanup symmetric: wh_PortKey_Cleanup tears + * down the backend then the lock, the reverse of the order here */ +#ifdef WOLFHSM_CFG_THREADSAFE + { + int rc = wh_Lock_Init(&context->lock, config->lockConfig); + if (rc != WH_ERROR_OK) { + memset(context, 0, sizeof(*context)); + return rc; + } + } +#endif /* WOLFHSM_CFG_THREADSAFE */ + + /* Initialize the backend if it provides an Init callback. Done last so it + * is the only fallible step after the lock: on failure, undo the lock so a + * successful backend Init is never left without a paired Cleanup */ + if ((context->cb != NULL) && (context->cb->Init != NULL)) { + int rc = context->cb->Init(context->context, config->config); + if (rc != WH_ERROR_OK) { +#ifdef WOLFHSM_CFG_THREADSAFE + (void)wh_Lock_Cleanup(&context->lock); +#endif /* WOLFHSM_CFG_THREADSAFE */ + memset(context, 0, sizeof(*context)); + return rc; + } + } + + context->initialized = 1; + return WH_ERROR_OK; +} + +int wh_PortKey_Cleanup(whPortKeyContext* context) +{ + int rc = WH_ERROR_OK; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + + /* Capture the backend teardown result but always finish releasing the lock + * and zeroing the context, then surface the backend error. */ + if ((context->cb != NULL) && (context->cb->Cleanup != NULL)) { + rc = context->cb->Cleanup(context->context); + } + +#ifdef WOLFHSM_CFG_THREADSAFE + { + /* Only let a lock-release failure surface when the backend cleanup + * itself succeeded, so a real backend error is not hidden */ + int relRc = wh_Lock_Cleanup(&context->lock); + if (rc == WH_ERROR_OK) { + rc = relRc; + } + } +#endif /* WOLFHSM_CFG_THREADSAFE */ + + memset(context, 0, sizeof(*context)); + return rc; +} + +static int _Lock(whPortKeyContext* context) +{ +#ifdef WOLFHSM_CFG_THREADSAFE + return wh_Lock_Acquire(&context->lock); +#else + (void)context; + return WH_ERROR_OK; +#endif +} + +static int _Unlock(whPortKeyContext* context) +{ +#ifdef WOLFHSM_CFG_THREADSAFE + return wh_Lock_Release(&context->lock); +#else + (void)context; + return WH_ERROR_OK; +#endif +} + +/* A port keyId the front end can act on: TYPE=PORT and a non-zero ID */ +static int _Valid(whPortKeyContext* context, whKeyId keyId) +{ + return (context != NULL) && (context->initialized != 0) && + WH_KEYID_ISPORT(keyId) && !WH_KEYID_ISERASED(keyId); +} + +/* Table entry for the port key with the same ID field, or NULL */ +static whPortKeyRecord* _Find(whPortKeyContext* context, whKeyId keyId) +{ + int i; + + for (i = 0; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + if ((context->keys[i].id != WH_KEYID_ERASED) && + (WH_KEYID_ID(context->keys[i].id) == WH_KEYID_ID(keyId))) { + return &context->keys[i]; + } + } + return NULL; +} + +static whPortKeyRecord* _FindFree(whPortKeyContext* context) +{ + int i; + + for (i = 0; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + if (context->keys[i].id == WH_KEYID_ERASED) { + return &context->keys[i]; + } + } + return NULL; +} + +void wh_PortKey_KeyIdToRef(whKeyId keyId, uint8_t* ref) +{ + ref[0] = (uint8_t)(keyId >> 8); + ref[1] = (uint8_t)keyId; +} + +int wh_PortKey_KeyRefToId(const uint8_t* ref, uint32_t refSz) +{ + whKeyId keyId; + + if ((ref == NULL) || (refSz != WH_PORTKEY_REF_SIZE)) { + return 0; + } + keyId = (whKeyId)(((whKeyId)ref[0] << 8) | ref[1]); + return WH_KEYID_ISPORT(keyId) ? (int)WH_KEYID_ID(keyId) : 0; +} + +/* Add a record for keyId, unless its ID is already registered */ +static int _Add(whPortKeyContext* context, whKeyId keyId, whNvmFlags flags, + uint16_t len, uint8_t accessMode) +{ + whPortKeyRecord* entry; + int ret; + + if (!_Valid(context, keyId)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + if (_Find(context, keyId) != NULL) { + ret = WH_ERROR_ACCESS; + } + else { + entry = _FindFree(context); + if (entry == NULL) { + ret = WH_ERROR_NOSPACE; + } + else { + entry->id = keyId; + entry->srcId = WH_KEYID_ERASED; + entry->flags = flags; + entry->len = len; + entry->accessMode = accessMode; + } + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_RegisterFixed(whPortKeyContext* context, whKeyId keyId, + whNvmFlags flags, uint16_t len, + uint32_t accessMode) +{ + /* A record with len 0 is an empty loadable key */ + if ((len == 0) || ((accessMode != WH_PORTKEY_ACCESS_BY_REF) && + (accessMode != WH_PORTKEY_ACCESS_BY_VALUE))) { + return WH_ERROR_BADARGS; + } + /* The port supplies a by-value key's bytes on each use */ + if ((accessMode == WH_PORTKEY_ACCESS_BY_VALUE) && + ((context == NULL) || (context->cb == NULL) || + (context->cb->GetKey == NULL))) { + return WH_ERROR_BADARGS; + } + /* Clients can't clear or replace a fixed key, or set or read it, so it is + * also a trusted KEK */ + return _Add(context, keyId, + (flags & WH_NVM_FLAGS_USAGE_ANY) | WH_NVM_FLAGS_NONDESTROYABLE | + WH_NVM_FLAGS_TRUSTED, + len, (uint8_t)accessMode); +} + +/* Check a request against the key's record. Call with the lock held */ +static int _Check(whPortKeyContext* context, whKeyId keyId, whNvmFlags flags, + whPortKeyRecord** outEntry) +{ + whPortKeyRecord* entry = _Find(context, keyId); + whNvmFlags usage = flags & WH_NVM_FLAGS_USAGE_ANY; + int ret = WH_ERROR_OK; + + if ((entry == NULL) || (entry->len == 0)) { + ret = WH_ERROR_NOTFOUND; + } + else if ((WH_KEYID_USER(entry->id) != WH_KEYUSER_GLOBAL) && + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else if (((flags & WH_NVM_FLAGS_TRUSTED) != 0) && + ((entry->flags & WH_NVM_FLAGS_TRUSTED) == 0)) { + ret = WH_ERROR_ACCESS; + } + else if ((usage == 0) || ((entry->flags & usage) != usage)) { + ret = WH_ERROR_USAGE; + } + else { + *outEntry = entry; + } + return ret; +} + +/* Copy a by-value key's bytes from the port. Call with the lock held */ +static int _GetKey(whPortKeyContext* context, const whPortKeyRecord* entry, + uint8_t* out, uint16_t* inout_len) +{ + uint16_t len = *inout_len; + int ret; + + if (len < entry->len) { + return WH_ERROR_BUFFER_SIZE; + } + if ((context->cb == NULL) || (context->cb->GetKey == NULL)) { + return WH_ERROR_ABORTED; + } + + ret = context->cb->GetKey(context->context, entry->id, out, &len); + if (ret == WH_ERROR_NOTREADY) { + /* A blocking client would wait for a response forever */ + ret = WH_ERROR_ABORTED; + } + else if ((ret == WH_ERROR_OK) && (len != entry->len)) { + /* Not the key the port registered */ + ret = WH_ERROR_ABORTED; + } + if (ret == WH_ERROR_OK) { + *inout_len = len; + } + else { + /* The port may have written part of a key */ + wh_Utils_ForceZero(out, *inout_len); + } + return ret; +} + +int wh_PortKey_CheckUse(whPortKeyContext* context, whKeyId keyId, + whNvmFlags flags, whKeyId* outKeyId) +{ + whPortKeyRecord* entry = NULL; + int ret; + + if (!_Valid(context, keyId) || (outKeyId == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + ret = _Check(context, keyId, flags, &entry); + if (ret == WH_ERROR_OK) { + *outKeyId = entry->id; + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_Resolve(whPortKeyContext* context, whKeyId keyId, + whNvmFlags flags, whKeyId* outKeyId, uint8_t* key, + uint16_t* inout_keySz) +{ + whPortKeyRecord* entry = NULL; + int ret; + + if (!_Valid(context, keyId) || (outKeyId == NULL) || (key == NULL) || + (inout_keySz == NULL)) { + return WH_ERROR_BADARGS; + } + *outKeyId = WH_KEYID_ERASED; + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + ret = _Check(context, keyId, flags, &entry); + if ((ret == WH_ERROR_OK) && + (entry->accessMode == WH_PORTKEY_ACCESS_BY_VALUE)) { + ret = _GetKey(context, entry, key, inout_keySz); + } + else if (ret == WH_ERROR_OK) { + /* Accessed by reference, so the bytes stay in the port */ + *outKeyId = entry->id; + *inout_keySz = 0; + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_Read(whPortKeyContext* context, whKeyId keyId, whNvmFlags flags, + uint8_t* out, uint16_t* inout_len) +{ + whKeyId refKeyId = WH_KEYID_ERASED; + int ret; + + ret = wh_PortKey_Resolve(context, keyId, flags, &refKeyId, out, inout_len); + if ((ret == WH_ERROR_OK) && (refKeyId != WH_KEYID_ERASED)) { + ret = WH_ERROR_ACCESS; + } + return ret; +} + +#ifdef WOLFHSM_CFG_PORTKEY_LOAD + +static int _PortImport(int devId, whKeyId keyId, const uint8_t* key, + uint16_t keySz) +{ + uint8_t ref[WH_PORTKEY_REF_SIZE]; + + wh_PortKey_KeyIdToRef(keyId, ref); + return wc_KeyStore_ImportPlain(devId, ref, sizeof(ref), WC_KEYSTORE_KEY_AES, + key, keySz, 0, NULL); +} + +/* Mark a loadable key empty, keeping its ID */ +static void _Empty(whPortKeyRecord* entry) +{ + entry->id = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, + WH_KEYID_ID(entry->id)); + entry->srcId = WH_KEYID_ERASED; + entry->flags = 0; + entry->len = 0; +} + +/* Clear the key through the port and mark it empty. It is marked empty even if + * the port fails, so the key can't be used again */ +static int _Remove(int devId, whPortKeyRecord* entry) +{ + uint8_t ref[WH_PORTKEY_REF_SIZE]; + int ret; + + wh_PortKey_KeyIdToRef(entry->id, ref); + ret = wc_KeyStore_Delete(devId, ref, sizeof(ref), NULL); + _Empty(entry); + return ret; +} + +int wh_PortKey_RegisterLoadable(whPortKeyContext* context, whKeyId keyId) +{ + /* No owner until a client loads it. Loaded keys are accessed by + * reference */ + return _Add(context, + WH_MAKE_KEYID(WH_KEYID_TYPE(keyId), WH_KEYUSER_GLOBAL, + WH_KEYID_ID(keyId)), + 0, 0, WH_PORTKEY_ACCESS_BY_REF); +} + +int wh_PortKey_Load(whPortKeyContext* context, int devId, whKeyId* inout_keyId, + whKeyId srcId, whNvmFlags flags, const uint8_t* key, + uint16_t keySz) +{ + whPortKeyRecord* entry = NULL; + whKeyId keyId; + int i; + int ret; + + if ((context == NULL) || (context->initialized == 0) || + (inout_keyId == NULL) || !WH_KEYID_ISPORT(*inout_keyId) || + (key == NULL) || (keySz == 0)) { + return WH_ERROR_BADARGS; + } + keyId = *inout_keyId; + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(keyId)) { + /* Try each empty loadable key in turn */ + ret = WH_ERROR_NOSPACE; + for (i = 0; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + if ((context->keys[i].id == WH_KEYID_ERASED) || + (context->keys[i].len != 0)) { + continue; + } + keyId = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYID_USER(*inout_keyId), + WH_KEYID_ID(context->keys[i].id)); + ret = _PortImport(devId, keyId, key, keySz); + if (ret == 0) { + entry = &context->keys[i]; + break; + } + } + } + else { + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + /* Only the owner may replace a key, and never a fixed key */ + else if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || + ((entry->len != 0) && + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId)))) { + ret = WH_ERROR_ACCESS; + } + else { + ret = _PortImport(devId, keyId, key, keySz); + } + } + + if (ret == 0) { + entry->id = keyId; + entry->srcId = srcId; + entry->flags = flags & WH_NVM_FLAGS_USAGE_ANY; + entry->len = keySz; + *inout_keyId = keyId; + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_Clear(whPortKeyContext* context, int devId, whKeyId keyId) +{ + whPortKeyRecord* entry; + int ret; + + if (!_Valid(context, keyId)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + entry = _Find(context, keyId); + if ((entry == NULL) || (entry->len == 0)) { + ret = WH_ERROR_NOTFOUND; + } + else if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else { + ret = _Remove(devId, entry); + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_ClearAll(whPortKeyContext* context, int devId, uint16_t user) +{ + whPortKeyRecord* entry; + int i; + int rc; + int ret; + + if ((context == NULL) || (context->initialized == 0)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + for (i = 0; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + entry = &context->keys[i]; + if ((entry->len != 0) && + ((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) == 0) && + (WH_KEYID_USER(entry->id) == user)) { + rc = _Remove(devId, entry); + if (ret == WH_ERROR_OK) { + ret = rc; + } + } + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_ClearSource(whPortKeyContext* context, int devId, whKeyId srcId) +{ + whPortKeyRecord* entry; + int i; + int rc; + int ret; + + if ((context == NULL) || (context->initialized == 0) || + (srcId == WH_KEYID_ERASED)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + for (i = 0; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + entry = &context->keys[i]; + if ((entry->len != 0) && (entry->srcId == srcId)) { + rc = _Remove(devId, entry); + if (ret == WH_ERROR_OK) { + ret = rc; + } + } + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +int wh_PortKey_GetInfo(whPortKeyContext* context, whKeyId keyId, + whPortKeyInfo* info) +{ + whPortKeyRecord* entry; + int ret; + + if (!_Valid(context, keyId) || (info == NULL)) { + return WH_ERROR_BADARGS; + } + memset(info, 0, sizeof(*info)); + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + else if (entry->len != 0) { + /* Port keys are only used as AES keys */ + info->keyType = WC_KEYSTORE_KEY_AES; + info->keyBits = (uint32_t)entry->len * 8; + info->flags = entry->flags & WH_NVM_FLAGS_USAGE_ANY; + info->owner = WH_KEYID_USER(entry->id); + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +#endif /* WOLFHSM_CFG_PORTKEY_LOAD */ + +#endif /* WOLFHSM_CFG_PORTKEY */ diff --git a/src/wh_server.c b/src/wh_server.c index b2296ed9f..f6dbe89ed 100644 --- a/src/wh_server.c +++ b/src/wh_server.c @@ -150,9 +150,9 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config) } } #endif /* WOLFHSM_CFG_ENABLE_AUTHENTICATION */ -#ifdef WOLFHSM_CFG_HWKEYSTORE - server->hwKeystore = config->hwKeystore; -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#ifdef WOLFHSM_CFG_PORTKEY + server->portKey = config->portKey; +#endif /* WOLFHSM_CFG_PORTKEY */ rc = wh_CommServer_Init(server->comm, config->comm_config, wh_Server_SetConnectedCb, (void*)server); @@ -688,6 +688,12 @@ static uint16_t _FormatErrorResponse(uint16_t magic, uint16_t group, size = sizeof(whMessageKeystore_DataUnwrapResponse); break; #endif /* WOLFHSM_CFG_KEYWRAP */ + case WH_KEY_PORT_LOAD: + case WH_KEY_PORT_CLEAR: + case WH_KEY_PORT_INFO: + /* rc comes first in all three, so the largest fits all */ + size = sizeof(whMessageKeystore_PortInfoResponse); + break; default: size = sizeof(int32_t); break; diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index ee1dbd2ec..02dfb649f 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -3173,13 +3173,71 @@ static int _HandleEd25519VerifyDma(whServerContext* ctx, uint16_t magic, #endif /* HAVE_ED25519 */ #ifndef NO_AES +/* Check that the client may use a port key for this request. A by-value key + * comes back as bytes in key and *inout_keyLen. Any other key comes back in + * *outPortKey, to pass to wolfCrypt by reference for the port to run */ +static int _PortKeyResolve(whServerContext* ctx, int devId, whKeyId keyId, + whNvmFlags usage, whKeyId* outPortKey, uint8_t* key, + uint32_t* inout_keyLen) +{ +#ifdef WOLFHSM_CFG_PORTKEY + uint16_t keyLen; + int ret; + + *outPortKey = WH_KEYID_ERASED; + if (ctx->portKey == NULL) { + return WH_ERROR_NOTFOUND; + } + keyLen = (*inout_keyLen > 0xFFFFu) ? 0xFFFFu : (uint16_t)*inout_keyLen; + ret = wh_PortKey_Resolve(ctx->portKey, keyId, usage, outPortKey, key, + &keyLen); + if (ret == WH_ERROR_OK) { + *inout_keyLen = keyLen; + /* A key used by reference needs the port's crypto device */ + if ((*outPortKey != WH_KEYID_ERASED) && (devId == INVALID_DEVID)) { + *outPortKey = WH_KEYID_ERASED; + ret = WH_ERROR_NOTFOUND; + } + } + return ret; +#else + /* Port keys are not built in */ + (void)ctx; + (void)devId; + (void)keyId; + (void)usage; + (void)outPortKey; + (void)key; + (void)inout_keyLen; + return WH_ERROR_ACCESS; +#endif +} + +/* Init aes for a request. A port key goes to wolfCrypt by reference: no + * key is set, and the crypto callback runs every operation */ +static int _AesInitForKey(Aes* aes, int devId, whKeyId portKey) +{ +#ifdef WOLFHSM_CFG_PORTKEY + if (portKey != WH_KEYID_ERASED) { + uint8_t ref[WH_PORTKEY_REF_SIZE]; + + wh_PortKey_KeyIdToRef(portKey, ref); + return wc_AesInit_Id(aes, ref, (int)sizeof(ref), NULL, devId); + } +#else + (void)portKey; +#endif + return wc_AesInit(aes, NULL, devId); +} + #ifdef WOLFSSL_AES_COUNTER static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId portKey = WH_KEYID_ERASED; whMessageCrypto_AesCtrRequest req; whMessageCrypto_AesCtrResponse res; uint8_t cachedKey[AES_256_KEY_SIZE]; @@ -3224,7 +3282,20 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] IV ", iv, AES_BLOCK_SIZE); WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] tmp ", tmp, AES_BLOCK_SIZE); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISPORT(key_id)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + key_len = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -3244,19 +3315,26 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] Key ", key, key_len); } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCtr] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesSetKeyDirect(aes, (byte*)key, (word32)key_len, (byte*)iv, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. A port key only needs the counter */ + if (portKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = + wc_AesSetKeyDirect(aes, (byte*)key, (word32)key_len, (byte*)iv, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* Reject client-supplied left values outside the valid range. * wc_AesCtrEncrypt indexes aes->tmp via AES_BLOCK_SIZE - aes->left; @@ -3318,7 +3396,9 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesCtrDmaRequest req; whMessageCrypto_AesCtrDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId portKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -3378,7 +3458,20 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISPORT(keyId)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + keyLen = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -3396,8 +3489,9 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCtr] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -3425,13 +3519,20 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); + aesInit = (ret == WH_ERROR_OK); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesSetKeyDirect(aes, (byte*)key, (word32)keyLen, (byte*)iv, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. A port key only needs the counter */ + if (portKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = + wc_AesSetKeyDirect(aes, (byte*)key, (word32)keyLen, (byte*)iv, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* Reject client-supplied left values outside the valid range. * wc_AesCtrEncrypt indexes aes->tmp via AES_BLOCK_SIZE - aes->left; @@ -3491,7 +3592,9 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -3515,7 +3618,8 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId portKey = WH_KEYID_ERASED; whMessageCrypto_AesEcbRequest req; whMessageCrypto_AesEcbResponse res; uint8_t cachedKey[AES_256_KEY_SIZE]; @@ -3556,7 +3660,20 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesEcb] Input data", in, len); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISPORT(key_id)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + key_len = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -3576,19 +3693,23 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesEcb] Key ", key, key_len); } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesEcb] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); } if (ret == WH_ERROR_OK) { - /* load the key. AES-ECB does not use IV */ - ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, NULL, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. AES-ECB does not use IV. A port key needs no + * setup */ + if (portKey == WH_KEYID_ERASED) { + ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, NULL, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* do the crypto operation */ if (enc != 0) { @@ -3631,7 +3752,9 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesEcbDmaRequest req; whMessageCrypto_AesEcbDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId portKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -3680,7 +3803,20 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISPORT(keyId)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + keyLen = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -3698,8 +3834,9 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesEcb] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -3725,10 +3862,11 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); + aesInit = (ret == WH_ERROR_OK); } - if (ret == WH_ERROR_OK) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED) { ret = wc_AesSetKey(aes, (byte*)key, (word32)keyLen, NULL, req.enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); } @@ -3778,7 +3916,9 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -3800,7 +3940,8 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId portKey = WH_KEYID_ERASED; whMessageCrypto_AesCbcRequest req; whMessageCrypto_AesCbcResponse res; uint8_t cachedKey[AES_256_KEY_SIZE]; @@ -3846,7 +3987,20 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] Input data", in, len); WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] IV", iv, AES_BLOCK_SIZE); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISPORT(key_id)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + key_len = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -3866,19 +4020,25 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] Key ", key, key_len); } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCbc] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, (byte*)iv, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. A port key only needs the IV */ + if (portKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, (byte*)iv, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* do the crypto operation */ if (enc != 0) { @@ -3922,7 +4082,9 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesCbcDmaRequest req; whMessageCrypto_AesCbcDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId portKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -3977,7 +4139,20 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISPORT(keyId)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + keyLen = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -3995,8 +4170,9 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCbc] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -4022,12 +4198,19 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); + aesInit = (ret == WH_ERROR_OK); } if (ret == WH_ERROR_OK) { - ret = wc_AesSetKey(aes, (byte*)key, keyLen, (byte*)iv, - req.enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* A port key only needs the IV */ + if (portKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = wc_AesSetKey(aes, (byte*)key, keyLen, (byte*)iv, + req.enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } } if (ret == WH_ERROR_OK) { @@ -4075,7 +4258,9 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -4097,7 +4282,8 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId portKey = WH_KEYID_ERASED; uint8_t cachedKey[AES_256_KEY_SIZE]; whNvmMetadata keyMeta[1]; @@ -4166,7 +4352,20 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId, (uint32_t)needed_size); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISPORT(key_id)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + key_len = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -4184,18 +4383,21 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesGcm] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesGcmSetKey(aes, (byte*)key, (word32)key_len); + /* load the key. A port key needs no setup */ + if (portKey == WH_KEYID_ERASED) { + ret = wc_AesGcmSetKey(aes, (byte*)key, (word32)key_len); + } WH_DEBUG_SERVER_VERBOSE("AesGcmSetKey key_id:%u key_len:%u ret:%d\n", key_id, key_len, ret); WH_DEBUG_VERBOSE_HEXDUMP("[server] key: ", key, key_len); @@ -4258,7 +4460,9 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesGcmDmaRequest req; whMessageCrypto_AesGcmDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId portKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -4332,7 +4536,20 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISPORT(keyId)) { + /* Port key: by reference, or the bytes of a by-value key */ + uint32_t cachedKeyLen = sizeof(cachedKey); + + ret = _PortKeyResolve(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &portKey, cachedKey, &cachedKeyLen); + if ((ret == WH_ERROR_OK) && (portKey == WH_KEYID_ERASED)) { + key = cachedKey; + keyLen = cachedKeyLen; + } + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -4350,8 +4567,9 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesGcm] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -4397,10 +4615,11 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, portKey); + aesInit = (ret == WH_ERROR_OK); } - if (ret == WH_ERROR_OK) { + if (ret == WH_ERROR_OK && portKey == WH_KEYID_ERASED) { ret = wc_AesGcmSetKey(aes, (byte*)key, (word32)keyLen); } @@ -4453,7 +4672,9 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -4474,13 +4695,17 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, #if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) /* Resolve CMAC key from request (inline key or keystore ID). - * outKey must be at least AES_256_KEY_SIZE bytes. */ -static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey, - uint32_t requestKeySz, whKeyId clientKeyId, - uint8_t* outKey, uint32_t* outKeyLen) + * outKey must be at least AES_256_KEY_SIZE bytes. A port key is returned + * in *outPortKey instead, to use by reference */ +static int _CmacResolveKey(whServerContext* ctx, int devId, + const uint8_t* requestKey, uint32_t requestKeySz, + whKeyId clientKeyId, uint8_t* outKey, + uint32_t* outKeyLen, whKeyId* outPortKey) { int ret = WH_ERROR_OK; + *outPortKey = WH_KEYID_ERASED; + if (requestKeySz != 0) { /* Client provided the key directly in the request */ memcpy(outKey, requestKey, requestKeySz); @@ -4491,6 +4716,27 @@ static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey, whKeyId keyId = wh_KeyId_TranslateFromClient( WH_KEYTYPE_CRYPTO, ctx->comm->client_id, clientKeyId); + if (WH_KEYID_ISPORT(keyId)) { + /* Port key: by reference, or the bytes of a by-value key. CMAC + * accepts sign or verify usage */ + ret = _PortKeyResolve(ctx, devId, keyId, WH_NVM_FLAGS_USAGE_SIGN, + outPortKey, outKey, outKeyLen); + if (ret == WH_ERROR_USAGE) { + ret = _PortKeyResolve(ctx, devId, keyId, + WH_NVM_FLAGS_USAGE_VERIFY, outPortKey, + outKey, outKeyLen); + } + if ((ret == WH_ERROR_OK) && (*outPortKey == WH_KEYID_ERASED) && + (*outKeyLen != AES_128_KEY_SIZE) && + (*outKeyLen != AES_192_KEY_SIZE) && + (*outKeyLen != AES_256_KEY_SIZE)) { + ret = WH_ERROR_ABORTED; + } + if (ret != WH_ERROR_OK) { + *outKeyLen = 0; + } + return ret; + } /* Copy the key into the caller's private buffer, enforcing the usage * policy against the same locked snapshot that is read. CMAC accepts * sign or verify usage, so retry with verify on a usage failure; each @@ -4521,6 +4767,25 @@ static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey, return ret; } +/* Init cmac with key bytes, or with a port key by reference */ +static int _CmacInitForKey(Cmac* cmac, const uint8_t* key, uint32_t keyLen, + whKeyId portKey, int devId) +{ +#ifdef WOLFHSM_CFG_PORTKEY + if (portKey != WH_KEYID_ERASED) { + uint8_t ref[WH_PORTKEY_REF_SIZE]; + + wh_PortKey_KeyIdToRef(portKey, ref); + return wc_InitCmac_Id(cmac, NULL, 0, WC_CMAC_AES, NULL, ref, + (int)sizeof(ref), NULL, devId); + } +#else + (void)portKey; +#endif + return wc_InitCmac_ex(cmac, key, (word32)keyLen, WC_CMAC_AES, NULL, NULL, + devId); +} + static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, uint16_t seq, const void* cryptoDataIn, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize) @@ -4568,9 +4833,11 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, uint8_t tmpKey[AES_256_KEY_SIZE]; uint32_t tmpKeyLen = sizeof(tmpKey); Cmac cmac[1]; + whKeyId portKey = WH_KEYID_ERASED; /* Resolve the key to use */ - ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, &tmpKeyLen); + ret = _CmacResolveKey(ctx, devId, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen, &portKey); /* Oneshot: input and output are both present */ if (ret == 0 && req.inSz != 0 && req.outSz != 0) { @@ -4578,8 +4845,21 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_SERVER_VERBOSE("cmac generate oneshot\n"); - ret = wc_AesCmacGenerate_ex(cmac, out, &len, in, req.inSz, tmpKey, - (word32)tmpKeyLen, NULL, devId); + if (portKey != WH_KEYID_ERASED) { + /* A port key is set up by reference first */ + ret = _CmacInitForKey(cmac, NULL, 0, portKey, devId); + if (ret == 0) { + ret = wc_AesCmacGenerate_ex(cmac, out, &len, in, req.inSz, NULL, + 0, NULL, devId); + } + } + else { + /* wolfCrypt passes cmac to the port before initializing it. Zero + * it so no stale key reference gets through */ + memset(cmac, 0, sizeof(cmac)); + ret = wc_AesCmacGenerate_ex(cmac, out, &len, in, req.inSz, tmpKey, + (word32)tmpKeyLen, NULL, devId); + } if (ret == 0) { res.outSz = len; @@ -4593,8 +4873,7 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, req.inSz, req.outSz, req.keyId); /* Initialize CMAC context with key (re-derives k1/k2 subkeys) */ - ret = wc_InitCmac_ex(cmac, tmpKey, tmpKeyLen, WC_CMAC_AES, NULL, NULL, - devId); + ret = _CmacInitForKey(cmac, tmpKey, tmpKeyLen, portKey, devId); WH_DEBUG_SERVER_VERBOSE("cmac init with keylen:%d ret:%d\n", tmpKeyLen, ret); @@ -9019,6 +9298,7 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, uint8_t tmpKey[AES_256_KEY_SIZE]; uint32_t tmpKeyLen = sizeof(tmpKey); Cmac cmac[1]; + whKeyId portKey = WH_KEYID_ERASED; /* Oneshot fast path: DMA input only (no inline), output requested. The * streaming protocol never produces outSz>0 with DMA input (Final is @@ -9036,14 +9316,17 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, /* Resolve key */ if (ret == WH_ERROR_OK) { - ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, - &tmpKeyLen); + ret = _CmacResolveKey(ctx, devId, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen, &portKey); } if (ret == WH_ERROR_OK && req.keySz != 0) { /* Client-supplied key - direct one-shot */ WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot\n"); + /* wolfCrypt passes cmac to the port before initializing it. Zero + * it so no stale key reference gets through */ + memset(cmac, 0, sizeof(cmac)); ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, req.input.sz, tmpKey, (word32)tmpKeyLen, NULL, devId); } @@ -9052,8 +9335,7 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot with keyId:%x\n", req.keyId); - ret = wc_InitCmac_ex(cmac, tmpKey, (word32)tmpKeyLen, WC_CMAC_AES, - NULL, NULL, devId); + ret = _CmacInitForKey(cmac, tmpKey, tmpKeyLen, portKey, devId); if (ret == WH_ERROR_OK) { ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, @@ -9077,13 +9359,12 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, (int)req.outSz, req.keyId); /* Resolve key */ - ret = - _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, &tmpKeyLen); + ret = _CmacResolveKey(ctx, devId, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen, &portKey); /* Initialize CMAC context with key (re-derives k1/k2 subkeys) */ if (ret == 0) { - ret = wc_InitCmac_ex(cmac, tmpKey, (word32)tmpKeyLen, WC_CMAC_AES, - NULL, NULL, devId); + ret = _CmacInitForKey(cmac, tmpKey, tmpKeyLen, portKey, devId); WH_DEBUG_SERVER_VERBOSE("dma cmac init with keylen:%d ret:%d\n", tmpKeyLen, ret); } diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index e520469b2..ae3433ef7 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -125,6 +125,9 @@ typedef enum { /* Exporting only the public half of a public-key object is considered * non-sensitive and is intentionally not gated by NONEXPORTABLE. */ WH_KS_OP_EXPORT_PUBLIC, + /* Copying a key into a port key. The key never goes back to a client, so + * NONEXPORTABLE does not apply */ + WH_KS_OP_PORT_LOAD, } whKsOp; static int _KeyIsCommitted(whServerContext* server, whKeyId keyId) @@ -168,15 +171,15 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys are not keystore-managed: no keystore operation is +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys are not keystore-managed: no keystore operation is * permitted on them. Must be checked before the existence lookup below, * since these keys are never in cache or NVM and some callers (e.g. * GetCacheSlotChecked) deliberately tolerate WH_ERROR_NOTFOUND */ - if (WH_KEYID_ISHW(keyId)) { + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* Check cache first */ ret = _FindInCache(server, keyId, NULL, NULL, NULL, &cacheMeta); @@ -208,10 +211,10 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, /* A trusted KEK is frozen against all client keystore ops: it can only be * *used* as a KEK by the keywrap path, which freshens it via the unchecked - * cache-slot path and so bypasses this gate. Mirrors the WH_KEYID_ISHW gate - * above but flag-based, so the flag is self-protecting regardless of the - * key's other bits, and blocks a client re-caching over the KEK id to drop - * the flag. */ + * cache-slot path and so bypasses this gate. Mirrors the WH_KEYID_ISPORT + * gate above but flag-based, so the flag is self-protecting regardless of + * the key's other bits, and blocks a client re-caching over the KEK id to + * drop the flag. */ if (flags & WH_NVM_FLAGS_TRUSTED) { return WH_ERROR_ACCESS; } @@ -247,6 +250,7 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, case WH_KS_OP_COMMIT: case WH_KS_OP_REVOKE: + case WH_KS_OP_PORT_LOAD: /* Always allowed */ break; default: @@ -740,12 +744,12 @@ int wh_Server_KeystoreGetUniqueId(whServerContext* server, whNvmId* inout_id) return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only key ids are assigned by the hardware backend */ - if (type == WH_KEYTYPE_HW) { +#ifdef WOLFHSM_CFG_PORTKEY + /* The port defines port key ids, so the keystore never assigns one */ + if (type == WH_KEYTYPE_PORT) { return WH_ERROR_BADARGS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* try every index until we find a unique one, don't worry about capacity */ for (id = WH_KEYID_IDMAX; id > WH_KEYID_ERASED; id--) { @@ -807,14 +811,14 @@ int wh_Server_KeystoreGetCacheSlot(whServerContext* server, whKeyId keyId, return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys must never occupy a cache slot. This core check is +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys must never occupy a cache slot. This core check is * the sole protection for the DMA cache path, which allocates its slot * here without going through _KeystoreCacheKey */ - if (WH_KEYID_ISHW(keyId)) { + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ ret = _FindInCache(server, keyId, &idx, &isBig, &buf, &foundMeta); if (ret == WH_ERROR_OK) { @@ -867,12 +871,12 @@ static int _KeystoreCacheKey(whServerContext* server, whNvmMetadata* meta, return WH_ERROR_ACCESS; } #endif -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys must never enter the key cache */ - if (WH_KEYID_ISHW(meta->id)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys must never enter the key cache */ + if (WH_KEYID_ISPORT(meta->id)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ if (checked) { ret = wh_Server_KeystoreGetCacheSlotChecked(server, meta->id, meta->len, @@ -994,14 +998,14 @@ int wh_Server_KeystoreFreshenKey(whServerContext* server, whKeyId keyId, return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys are never cached and are not usable through the - * keystore. The keywrap KEK path fetches them directly from the hardware - * keystore instead */ - if (WH_KEYID_ISHW(keyId)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys are never cached and are not usable through the + * keystore. Key wrap and the crypto handlers use them by reference + * instead */ + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* Use local buffers to allow for optional (NULL) output parameters */ cacheBufOut = (outBuf != NULL) ? outBuf : (uint8_t**)&cacheBufLocal; @@ -1065,12 +1069,12 @@ int wh_Server_KeystoreReadKey(whServerContext* server, whKeyId keyId, return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only key material must never be read out of the server */ - if (WH_KEYID_ISHW(keyId)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port key material must never be read out of the server */ + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* Check the cache using unified function */ ret = _FindInCache(server, keyId, NULL, NULL, &cacheBuffer, &cacheMeta); @@ -1210,12 +1214,12 @@ int wh_Server_KeystoreEvictKey(whServerContext* server, whNvmId keyId) return WH_ERROR_BADARGS; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys are never cached, so there is nothing to evict */ - if (WH_KEYID_ISHW(keyId)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys are never cached, so there is nothing to evict */ + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* Get the appropriate cache context for this key */ ctx = _GetCacheContext(server, keyId); @@ -1258,12 +1262,12 @@ int wh_Server_KeystoreCommitKey(whServerContext* server, whNvmId keyId) return WH_ERROR_ABORTED; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys must never be persisted to NVM */ - if (WH_KEYID_ISHW(keyId)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys must never be persisted to NVM */ + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* Get the appropriate cache context for this key */ ctx = _GetCacheContext(server, keyId); @@ -1294,8 +1298,26 @@ int wh_Server_KeystoreCommitKeyChecked(whServerContext* server, whNvmId keyId) return wh_Server_KeystoreCommitKey(server, keyId); } +/* An erased or revoked key must not stay usable through a copy in a port key. + * On a port error the copies are unusable anyway, but the port may still hold + * them */ +static int _ClearPortKeyCopies(whServerContext* server, whKeyId keyId) +{ +#ifdef WOLFHSM_CFG_PORTKEY_LOAD + if (server->portKey != NULL) { + return wh_PortKey_ClearSource(server->portKey, server->devId, keyId); + } +#else + (void)server; + (void)keyId; +#endif + return WH_ERROR_OK; +} + int wh_Server_KeystoreEraseKey(whServerContext* server, whNvmId keyId) { + int ret; + if ((server == NULL) || (WH_KEYID_IS_UNASSIGNED(keyId))) { return WH_ERROR_BADARGS; } @@ -1304,12 +1326,12 @@ int wh_Server_KeystoreEraseKey(whServerContext* server, whNvmId keyId) return WH_ERROR_ABORTED; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys are not keystore-managed and cannot be erased */ - if (WH_KEYID_ISHW(keyId)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys are not keystore-managed and cannot be erased */ + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* remove the key from the cache if present */ (void)wh_Server_KeystoreEvictKey(server, keyId); @@ -1317,11 +1339,15 @@ int wh_Server_KeystoreEraseKey(whServerContext* server, whNvmId keyId) /* No NVM means there is nothing to destroy, same as erasing a key that * was never there. */ if (server->nvm == NULL) { - return WH_ERROR_OK; + return _ClearPortKeyCopies(server, keyId); } /* destroy the object */ - return wh_Nvm_DestroyObjects(server->nvm, 1, &keyId); + ret = wh_Nvm_DestroyObjects(server->nvm, 1, &keyId); + if (ret == WH_ERROR_OK) { + ret = _ClearPortKeyCopies(server, keyId); + } + return ret; } int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) @@ -1336,12 +1362,12 @@ int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) return WH_ERROR_ABORTED; } -#ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys are not keystore-managed and cannot be erased */ - if (WH_KEYID_ISHW(keyId)) { +#ifdef WOLFHSM_CFG_PORTKEY + /* Port keys are not keystore-managed and cannot be erased */ + if (WH_KEYID_ISPORT(keyId)) { return WH_ERROR_ACCESS; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ /* NOTFOUND means the key was not cached, whether it is absent entirely or * lives only in NVM; both are fine. Any other error must not be masked by @@ -1354,10 +1380,14 @@ int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) /* Nothing left to destroy is a successful erase, matching * wh_Server_KeystoreEraseKey */ if (server->nvm == NULL) { - return WH_ERROR_OK; + return _ClearPortKeyCopies(server, keyId); } - return wh_Nvm_DestroyObjectsChecked(server->nvm, 1, &keyId); + ret = wh_Nvm_DestroyObjectsChecked(server->nvm, 1, &keyId); + if (ret == WH_ERROR_OK) { + ret = _ClearPortKeyCopies(server, keyId); + } + return ret; } static void _revokeKey(whNvmMetadata* meta) @@ -1432,54 +1462,122 @@ int wh_Server_KeystoreRevokeKey(whServerContext* server, whNvmId keyId) _revokeKey(cacheMeta); } + if (ret == WH_ERROR_OK) { + ret = _ClearPortKeyCopies(server, keyId); + } + return ret; +} + +#ifdef WOLFHSM_CFG_PORTKEY_LOAD +int wh_Server_KeystorePortLoad(whServerContext* server, + whKeyId* inout_portKeyId, whKeyId srcId) +{ + uint8_t key[WOLFHSM_CFG_PORTKEY_MAX_KEY_SIZE]; + uint32_t keySz = sizeof(key); + whNvmMetadata meta[1]; + int ret; + + if ((server == NULL) || (inout_portKeyId == NULL) || + (WH_KEYID_TYPE(srcId) != WH_KEYTYPE_CRYPTO)) { + return WH_ERROR_BADARGS; + } + if (server->portKey == NULL) { + return WH_ERROR_NOTFOUND; + } + + /* Read the source with the policy checks every client keystore operation + * gets, which refuse trusted keys. Hold the NVM lock until the copy is + * loaded, so erasing or revoking the source can't miss it */ + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = _KeystoreCheckPolicy(server, WH_KS_OP_PORT_LOAD, srcId); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreReadKey(server, srcId, meta, key, &keySz); + } + + /* The loaded copy keeps the usage of the source. A key with none (a + * revoked key, for example) could not be used */ + if ((ret == WH_ERROR_OK) && + ((meta->flags & WH_NVM_FLAGS_USAGE_ANY) == 0)) { + ret = WH_ERROR_USAGE; + } + if (ret == WH_ERROR_OK) { + ret = + wh_PortKey_Load(server->portKey, server->devId, inout_portKeyId, + srcId, meta->flags, key, (uint16_t)keySz); + } + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + wh_Utils_ForceZero(key, sizeof(key)); return ret; } +#endif /* WOLFHSM_CFG_PORTKEY_LOAD */ #ifdef WOLFHSM_CFG_KEYWRAP #ifndef NO_AES #ifdef HAVE_AESGCM -/* Resolve the KEK for a keywrap operation. Hardware-only KEKs (TYPE=HW) - * are fetched from the server's hardware keystore into hwKekBuf, which the - * caller must keep local and zeroize after use; they carry no NVM metadata, - * so usage policy is delegated to the hardware keystore backend. All other - * KEKs are freshened into the key cache and must carry WH_NVM_FLAGS_USAGE_WRAP. +/* The KEK of a keywrap operation: key bytes from the keystore or a by-value + * port key, or a port key accessed by reference */ +typedef struct { + const uint8_t* key; + uint32_t keySz; + whKeyId portKey; /* WH_KEYID_ERASED unless used by reference */ +#ifdef WOLFHSM_CFG_PORTKEY + uint8_t buf[AES_256_KEY_SIZE]; /* A by-value port key's bytes */ +#endif +} whKeywrapKek; + +/* Resolve the KEK for a keywrap operation. A port KEK (TYPE=PORT) needs a + * record allowing WH_NVM_FLAGS_USAGE_WRAP. A port KEK accessed by reference + * never leaves the port, and a by-value one's bytes are copied into + * outKek->buf. All other KEKs are freshened into the key cache and must carry + * WH_NVM_FLAGS_USAGE_WRAP. * * When enforceTrustedKek is nonzero the KEK must be one the client cannot know - * or set: a hardware key (returned above) or a software key carrying - * WH_NVM_FLAGS_TRUSTED. This is the unified eligibility predicate - * isTrustedKek = WH_KEYID_ISHW(id) || (flags & WH_NVM_FLAGS_TRUSTED), required - * by the ops that move a server secret across the client boundary - * (KeyWrapExport, KeyUnwrapAndCache). */ + * or set: a fixed key (its port key record is WH_NVM_FLAGS_TRUSTED) or a + * software key carrying WH_NVM_FLAGS_TRUSTED. The ops that move a server + * secret across the client boundary (KeyWrapExport, KeyUnwrapAndCache) + * require this. */ static int _KeywrapResolveKek(whServerContext* server, whKeyId serverKeyId, - int enforceTrustedKek, uint8_t* hwKekBuf, - uint16_t hwKekBufSz, const uint8_t** outKek, - uint32_t* outKekSz) + int enforceTrustedKek, whKeywrapKek* outKek) { int ret; whNvmMetadata* kekMeta = NULL; uint8_t* kek = NULL; -#ifdef WOLFHSM_CFG_HWKEYSTORE - if (WH_KEYID_ISHW(serverKeyId)) { - uint16_t hwKekSz = hwKekBufSz; - if (server->hwKeystore == NULL) { - /* No hardware keystore bound to this server */ + outKek->key = NULL; + outKek->keySz = 0; + outKek->portKey = WH_KEYID_ERASED; + +#ifdef WOLFHSM_CFG_PORTKEY + if (WH_KEYID_ISPORT(serverKeyId)) { + whNvmFlags need = WH_NVM_FLAGS_USAGE_WRAP; + uint16_t keyLen = (uint16_t)sizeof(outKek->buf); + + if (server->portKey == NULL) { + /* No port keys */ return WH_ERROR_NOTFOUND; } - ret = wh_HwKeystore_GetKey(server->hwKeystore, serverKeyId, hwKekBuf, - &hwKekSz); - if (ret == WH_ERROR_OK) { - *outKek = hwKekBuf; - *outKekSz = hwKekSz; + if (enforceTrustedKek) { + need |= WH_NVM_FLAGS_TRUSTED; + } + ret = wh_PortKey_Resolve(server->portKey, serverKeyId, need, + &outKek->portKey, outKek->buf, &keyLen); + if ((ret == WH_ERROR_OK) && (outKek->portKey == WH_KEYID_ERASED)) { + /* A by-value key: use its bytes */ + outKek->key = outKek->buf; + outKek->keySz = keyLen; + } + else if ((ret == WH_ERROR_OK) && (server->devId == INVALID_DEVID)) { + /* No device to run the key by reference */ + outKek->portKey = WH_KEYID_ERASED; + ret = WH_ERROR_NOTFOUND; } return ret; } -#else - (void)hwKekBuf; - (void)hwKekBufSz; -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ ret = wh_Server_KeystoreFreshenKey(server, serverKeyId, &kek, &kekMeta); if (ret != WH_ERROR_OK) { @@ -1492,34 +1590,49 @@ static int _KeywrapResolveKek(whServerContext* server, whKeyId serverKeyId, return ret; } - /* A hardware KEK already returned above and is inherently trusted; a - * software KEK qualifies only if it was provisioned with - * WH_NVM_FLAGS_TRUSTED (which a client can never set). */ + /* A software KEK is trusted only if it was provisioned with + * WH_NVM_FLAGS_TRUSTED (which a client can never set) */ if (enforceTrustedKek && !(kekMeta->flags & WH_NVM_FLAGS_TRUSTED)) { return WH_ERROR_ACCESS; } - *outKek = kek; - *outKekSz = kekMeta->len; + outKek->key = kek; + outKek->keySz = kekMeta->len; return WH_ERROR_OK; } -/* Size of the local storage each keywrap helper provides for a hardware-only - * KEK. Minimal when no hardware keystore is configured, since the resolver - * then never writes to it */ -#ifdef WOLFHSM_CFG_HWKEYSTORE -#define WH_KEYWRAP_HWKEK_BUF_SIZE WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE -#else -#define WH_KEYWRAP_HWKEK_BUF_SIZE 1 -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +/* Set up aes with the KEK: by reference for a port key, else with its key + * bytes. Nothing needs freeing on failure */ +static int _KeywrapAesInit(whServerContext* server, Aes* aes, + const whKeywrapKek* kek) +{ + int ret; + +#ifdef WOLFHSM_CFG_PORTKEY + if (kek->portKey != WH_KEYID_ERASED) { + uint8_t ref[WH_PORTKEY_REF_SIZE]; + + wh_PortKey_KeyIdToRef(kek->portKey, ref); + return wc_AesInit_Id(aes, ref, (int)sizeof(ref), NULL, server->devId); + } +#endif /* WOLFHSM_CFG_PORTKEY */ + + ret = wc_AesInit(aes, NULL, server->devId); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, kek->key, kek->keySz); + if (ret != 0) { + wc_AesFree(aes); + } + } + return ret; +} static const uint8_t WH_KEYWRAP_AAD_KEY[] = WH_KEYWRAP_AAD_KEY_STR; static const uint8_t WH_KEYWRAP_AAD_DATA[] = WH_KEYWRAP_AAD_DATA_STR; -static int _AesGcmKeyWrapWithKek(whServerContext* server, - const uint8_t* serverKey, uint32_t serverKeySz, - uint8_t* keyIn, uint16_t keySz, - whNvmMetadata* metadataIn, +static int _AesGcmKeyWrapWithKek(whServerContext* server, + const whKeywrapKek* kek, uint8_t* keyIn, + uint16_t keySz, whNvmMetadata* metadataIn, uint8_t* wrappedKeyOut, uint16_t wrappedKeySz) { int ret = 0; @@ -1530,7 +1643,7 @@ static int _AesGcmKeyWrapWithKek(whServerContext* server, uint32_t plainBlobSz = sizeof(*metadataIn) + keySz; uint8_t* encBlob; - if (server == NULL || keyIn == NULL || metadataIn == NULL || + if (server == NULL || kek == NULL || keyIn == NULL || metadataIn == NULL || wrappedKeyOut == NULL || plainBlobSz > sizeof(plainBlob)) { return WH_ERROR_BADARGS; } @@ -1541,18 +1654,12 @@ static int _AesGcmKeyWrapWithKek(whServerContext* server, return WH_ERROR_BUFFER_SIZE; } - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { return ret; } - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); - if (ret != 0) { - wc_AesFree(aes); - return ret; - } - /* Generate the IV */ ret = wc_RNG_GenerateBlock(server->crypto->rng, iv, sizeof(iv)); if (ret != 0) { @@ -1590,33 +1697,27 @@ static int _AesGcmKeyWrap(whServerContext* server, whKeyId serverKeyId, whNvmMetadata* metadataIn, uint8_t* wrappedKeyOut, uint16_t wrappedKeySz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; } /* Get the server side key (KEK) */ - ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, &kek); if (ret == WH_ERROR_OK) { - ret = - _AesGcmKeyWrapWithKek(server, serverKey, serverKeySz, keyIn, keySz, - metadataIn, wrappedKeyOut, wrappedKeySz); + ret = _AesGcmKeyWrapWithKek(server, &kek, keyIn, keySz, metadataIn, + wrappedKeyOut, wrappedKeySz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - + /* A by-value port key's bytes may be in kek */ + wh_Utils_ForceZero(&kek, sizeof(kek)); return ret; } -static int _AesGcmKeyUnwrapWithKek(whServerContext* server, - const uint8_t* serverKey, - uint32_t serverKeySz, void* wrappedKeyIn, +static int _AesGcmKeyUnwrapWithKek(whServerContext* server, + const whKeywrapKek* kek, void* wrappedKeyIn, uint16_t wrappedKeySz, whNvmMetadata* metadataOut, void* keyOut, uint16_t keySz) @@ -1629,8 +1730,9 @@ static int _AesGcmKeyUnwrapWithKek(whServerContext* server, uint16_t encBlobSz; uint8_t plainBlob[sizeof(*metadataOut) + WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE]; - if (server == NULL || wrappedKeyIn == NULL || metadataOut == NULL || - keyOut == NULL || keySz > WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE) { + if (server == NULL || kek == NULL || wrappedKeyIn == NULL || + metadataOut == NULL || keyOut == NULL || + keySz > WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE) { return WH_ERROR_BADARGS; } @@ -1641,15 +1743,9 @@ static int _AesGcmKeyUnwrapWithKek(whServerContext* server, encBlob = (uint8_t*)wrappedKeyIn + sizeof(iv) + sizeof(authTag); encBlobSz = wrappedKeySz - sizeof(iv) - sizeof(authTag); - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); - if (ret != 0) { - return ret; - } - - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { - wc_AesFree(aes); return ret; } @@ -1680,33 +1776,27 @@ static int _AesGcmKeyUnwrap(whServerContext* server, uint16_t serverKeyId, uint16_t wrappedKeySz, whNvmMetadata* metadataOut, void* keyOut, uint16_t keySz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; } /* Get the server side key (KEK) */ - ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, &kek); if (ret == WH_ERROR_OK) { - ret = _AesGcmKeyUnwrapWithKek(server, serverKey, serverKeySz, - wrappedKeyIn, wrappedKeySz, metadataOut, - keyOut, keySz); + ret = _AesGcmKeyUnwrapWithKek(server, &kek, wrappedKeyIn, wrappedKeySz, + metadataOut, keyOut, keySz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - + /* A by-value port key's bytes may be in kek */ + wh_Utils_ForceZero(&kek, sizeof(kek)); return ret; } -static int _AesGcmDataWrapWithKek(whServerContext* server, - const uint8_t* serverKey, - uint32_t serverKeySz, uint8_t* dataIn, +static int _AesGcmDataWrapWithKek(whServerContext* server, + const whKeywrapKek* kek, uint8_t* dataIn, uint16_t dataSz, uint8_t* wrappedDataOut, uint16_t wrappedDataSz) { @@ -1716,8 +1806,8 @@ static int _AesGcmDataWrapWithKek(whServerContext* server, uint8_t iv[WH_KEYWRAP_AES_GCM_IV_SIZE]; uint8_t* encBlob; - if (server == NULL || dataIn == NULL || wrappedDataOut == NULL || - dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { + if (server == NULL || kek == NULL || dataIn == NULL || + wrappedDataOut == NULL || dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { return WH_ERROR_BADARGS; } @@ -1726,15 +1816,9 @@ static int _AesGcmDataWrapWithKek(whServerContext* server, return WH_ERROR_BUFFER_SIZE; } - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); - if (ret != 0) { - return ret; - } - - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { - wc_AesFree(aes); return ret; } @@ -1767,10 +1851,8 @@ static int _AesGcmDataWrap(whServerContext* server, whKeyId serverKeyId, uint8_t* dataIn, uint16_t dataSz, uint8_t* wrappedDataOut, uint16_t wrappedDataSz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; @@ -1779,24 +1861,21 @@ static int _AesGcmDataWrap(whServerContext* server, whKeyId serverKeyId, /* Get the server side key (KEK). Data wrap requires the KEK to carry * USAGE_WRAP, but not to be a trusted KEK (no server secret crosses the * boundary). */ - ret = _KeywrapResolveKek(server, serverKeyId, 0, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, 0, &kek); if (ret == WH_ERROR_OK) { - ret = _AesGcmDataWrapWithKek(server, serverKey, serverKeySz, dataIn, - dataSz, wrappedDataOut, wrappedDataSz); + ret = _AesGcmDataWrapWithKek(server, &kek, dataIn, dataSz, + wrappedDataOut, wrappedDataSz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - + /* A by-value port key's bytes may be in kek */ + wh_Utils_ForceZero(&kek, sizeof(kek)); return ret; } -static int _AesGcmDataUnwrapWithKek(whServerContext* server, - const uint8_t* serverKey, - uint32_t serverKeySz, void* wrappedDataIn, - uint16_t wrappedDataSz, void* dataOut, - uint16_t dataSz) +static int _AesGcmDataUnwrapWithKek(whServerContext* server, + const whKeywrapKek* kek, + void* wrappedDataIn, uint16_t wrappedDataSz, + void* dataOut, uint16_t dataSz) { int ret = 0; Aes aes[1]; @@ -1805,8 +1884,8 @@ static int _AesGcmDataUnwrapWithKek(whServerContext* server, uint8_t* encBlob; uint16_t encBlobSz; - if (server == NULL || wrappedDataIn == NULL || dataOut == NULL || - dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { + if (server == NULL || kek == NULL || wrappedDataIn == NULL || + dataOut == NULL || dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { return WH_ERROR_BADARGS; } @@ -1821,15 +1900,9 @@ static int _AesGcmDataUnwrapWithKek(whServerContext* server, return WH_ERROR_BUFFER_SIZE; } - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); - if (ret != 0) { - return ret; - } - - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { - wc_AesFree(aes); return ret; } @@ -1850,10 +1923,8 @@ static int _AesGcmDataUnwrap(whServerContext* server, uint16_t serverKeyId, void* wrappedDataIn, uint16_t wrappedDataSz, void* dataOut, uint16_t dataSz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; @@ -1862,17 +1933,14 @@ static int _AesGcmDataUnwrap(whServerContext* server, uint16_t serverKeyId, /* Get the server side key (KEK). Data unwrap requires the KEK to carry * USAGE_WRAP, but not to be a trusted KEK (no server secret crosses the * boundary). */ - ret = _KeywrapResolveKek(server, serverKeyId, 0, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, 0, &kek); if (ret == WH_ERROR_OK) { - ret = _AesGcmDataUnwrapWithKek(server, serverKey, serverKeySz, - wrappedDataIn, wrappedDataSz, dataOut, - dataSz); + ret = _AesGcmDataUnwrapWithKek(server, &kek, wrappedDataIn, + wrappedDataSz, dataOut, dataSz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - + /* A by-value port key's bytes may be in kek */ + wh_Utils_ForceZero(&kek, sizeof(kek)); return ret; } @@ -2075,8 +2143,8 @@ _HandleKeyWrapExportRequest(whServerContext* server, } /* Wrap the key with its real metadata. This extracts a server-held - * secret to the client, so the KEK must be a trusted (HW or - * WH_NVM_FLAGS_TRUSTED) key the client cannot know. */ + * secret to the client, so the KEK must be a trusted key (a fixed + * key or WH_NVM_FLAGS_TRUSTED) the client cannot know. */ ret = _AesGcmKeyWrap(server, serverKeyId, /*requireTrustedKek=*/1, key, (uint16_t)keySz, &metadata, wrappedKeyStage, wrappedKeySz); @@ -2321,8 +2389,8 @@ static int _HandleKeyUnwrapAndCacheRequest( } /* Unwrap-and-cache injects a key into the server keystore, so the - * KEK must be a trusted (HW or WH_NVM_FLAGS_TRUSTED) key, else a - * client could forge a blob under a KEK it knows. */ + * KEK must be a trusted key (a fixed key or WH_NVM_FLAGS_TRUSTED), + * else a client could forge a blob under a KEK it knows. */ ret = _AesGcmKeyUnwrap(server, serverKeyId, /*requireTrustedKek=*/1, wrappedKey, req->wrappedKeySz, &metadata, keyStage, @@ -3645,6 +3713,132 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, #endif /* WOLFHSM_CFG_KEYWRAP */ +#ifdef WOLFHSM_CFG_PORTKEY_LOAD + case WH_KEY_PORT_LOAD: { + whMessageKeystore_PortLoadRequest req = {0}; + whMessageKeystore_PortLoadResponse resp = {0}; + whKeyId portKeyId; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BADARGS; + } + + if (ret == WH_ERROR_OK) { + (void)wh_MessageKeystore_TranslatePortLoadRequest( + magic, (whMessageKeystore_PortLoadRequest*)req_packet, + &req); + + portKeyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_PORT, server->comm->client_id, + req.portKeyId | WH_KEYID_CLIENT_PORT_FLAG); + ret = wh_Server_KeystorePortLoad( + server, &portKeyId, + wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, server->comm->client_id, req.srcId)); + if (ret == WH_ERROR_OK) { + resp.portKeyId = wh_KeyId_TranslateToClient(portKeyId); + } + } + resp.rc = ret; + + (void)wh_MessageKeystore_TranslatePortLoadResponse( + magic, &resp, (whMessageKeystore_PortLoadResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; + + case WH_KEY_PORT_CLEAR: { + whMessageKeystore_PortClearRequest req = {0}; + whMessageKeystore_PortClearResponse resp = {0}; + whKeyId portKeyId; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BADARGS; + } + else if (server->portKey == NULL) { + ret = WH_ERROR_NOTFOUND; + } + + if (ret == WH_ERROR_OK) { + (void)wh_MessageKeystore_TranslatePortClearRequest( + magic, (whMessageKeystore_PortClearRequest*)req_packet, + &req); + + portKeyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_PORT, server->comm->client_id, + req.portKeyId | WH_KEYID_CLIENT_PORT_FLAG); + if (WH_KEYID_ISERASED(portKeyId)) { + /* ID 0 clears every key the client loaded */ + ret = wh_PortKey_ClearAll(server->portKey, server->devId, + server->comm->client_id); + } + else { + ret = wh_PortKey_Clear(server->portKey, server->devId, + portKeyId); + } + } + resp.rc = ret; + + (void)wh_MessageKeystore_TranslatePortClearResponse( + magic, &resp, + (whMessageKeystore_PortClearResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; + + case WH_KEY_PORT_INFO: { + whMessageKeystore_PortInfoRequest req = {0}; + whMessageKeystore_PortInfoResponse resp = {0}; + whPortKeyInfo info = {0}; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BADARGS; + } + else if (server->portKey == NULL) { + ret = WH_ERROR_NOTFOUND; + } + + if (ret == WH_ERROR_OK) { + (void)wh_MessageKeystore_TranslatePortInfoRequest( + magic, (whMessageKeystore_PortInfoRequest*)req_packet, + &req); + + ret = wh_PortKey_GetInfo( + server->portKey, + wh_KeyId_TranslateFromClient( + WH_KEYTYPE_PORT, server->comm->client_id, + req.portKeyId | WH_KEYID_CLIENT_PORT_FLAG), + &info); + if (ret == WH_ERROR_OK) { + resp.keyType = info.keyType; + resp.keyBits = info.keyBits; + resp.flags = info.flags; + resp.owner = info.owner; + } + } + resp.rc = ret; + + (void)wh_MessageKeystore_TranslatePortInfoResponse( + magic, &resp, (whMessageKeystore_PortInfoResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; +#else + case WH_KEY_PORT_LOAD: + case WH_KEY_PORT_CLEAR: + case WH_KEY_PORT_INFO: { + /* Not built in. rc comes first in all three responses, so the + * largest fits all */ + whMessageKeystore_PortInfoResponse resp = {0}; + + ret = WH_ERROR_NOTIMPL; + resp.rc = ret; + (void)wh_MessageKeystore_TranslatePortInfoResponse( + magic, &resp, (whMessageKeystore_PortInfoResponse*)resp_packet); + *out_resp_size = sizeof(resp); + } break; +#endif /* WOLFHSM_CFG_PORTKEY_LOAD */ + default: ret = WH_ERROR_NOHANDLER; break; diff --git a/src/wh_server_she.c b/src/wh_server_she.c index 88b025c63..c5ebbb8ea 100644 --- a/src/wh_server_she.c +++ b/src/wh_server_she.c @@ -861,10 +861,12 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, resp.messageFour[15] = ((_PopId(req.messageOne) << 4) | _PopAuthId(req.messageOne)); - /* Then encrypt counter into second half of messageFour */ - ret = wc_AesEncryptDirect(server->she->sheAes, - resp.messageFour + WH_SHE_KEY_SZ, - counter_buffer); + /* Then encrypt counter into second half of messageFour. One-block + * ECB, not AES-direct, which fails when a crypto callback owns the + * key */ + ret = wc_AesEcbEncrypt(server->she->sheAes, + resp.messageFour + WH_SHE_KEY_SZ, counter_buffer, + AES_BLOCK_SIZE); } /* free aes for protection */ wc_AesFree(server->she->sheAes); @@ -1075,10 +1077,10 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, memcpy(resp.messageFour + WH_SHE_KEY_SZ, &counter_val, sizeof(uint32_t)); resp.messageFour[WH_SHE_KEY_SZ + 3] |= 0x08; - /* encrypt the new counter */ - ret = wc_AesEncryptDirect(server->she->sheAes, - resp.messageFour + WH_SHE_KEY_SZ, - resp.messageFour + WH_SHE_KEY_SZ); + /* encrypt the new counter (one-block ECB, see above) */ + ret = wc_AesEcbEncrypt( + server->she->sheAes, resp.messageFour + WH_SHE_KEY_SZ, + resp.messageFour + WH_SHE_KEY_SZ, AES_BLOCK_SIZE); } /* free aes for protection */ wc_AesFree(server->she->sheAes); diff --git a/src/wh_she_crypto.c b/src/wh_she_crypto.c index 56cc2b4fd..5852f69cc 100644 --- a/src/wh_she_crypto.c +++ b/src/wh_she_crypto.c @@ -64,8 +64,8 @@ int wh_She_AesMp16_ex(Aes* aes, void* heap, int devid, ret = wc_AesInit(aes, heap, devid); /* do the first block with messageZero as the key */ if (ret == 0) { - ret = wc_AesSetKeyDirect(aes, messageZero, AES_BLOCK_SIZE, NULL, - AES_ENCRYPTION); + ret = wc_AesSetKey(aes, messageZero, AES_BLOCK_SIZE, NULL, + AES_ENCRYPTION); } while (ret == 0 && i < (int)inSz) { /* copy a block and pad it if we're short */ @@ -75,8 +75,9 @@ int wh_She_AesMp16_ex(Aes* aes, void* heap, int devid, } else memcpy(paddedInput, in + i, AES_BLOCK_SIZE); - /* encrypt this block */ - ret = wc_AesEncryptDirect(aes, out, paddedInput); + /* encrypt this block. One-block ECB, not AES-direct, which fails + * when a crypto callback owns the key */ + ret = wc_AesEcbEncrypt(aes, out, paddedInput, AES_BLOCK_SIZE); /* xor with the original message and then the previous block */ for (j = 0; j < (int)AES_BLOCK_SIZE; j++) { out[j] ^= paddedInput[j]; @@ -85,8 +86,7 @@ int wh_She_AesMp16_ex(Aes* aes, void* heap, int devid, } /* set the key for the next block */ if (ret == 0) { - ret = wc_AesSetKeyDirect(aes, out, AES_BLOCK_SIZE, - NULL, AES_ENCRYPTION); + ret = wc_AesSetKey(aes, out, AES_BLOCK_SIZE, NULL, AES_ENCRYPTION); } if (ret == 0) { /* store previous output in messageZero */ diff --git a/test-refactor/README.md b/test-refactor/README.md index 6dadc3f33..42e65f728 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -105,7 +105,7 @@ Translated tests: | `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | `server/wh_test_server_img_mgr.c::whTest_ServerImgMgr` | Server | Per-method subtests (ECC P256, AES-CMAC, RSA2048, wolfBoot RSA4096, wolfBoot RSAPSS4096 under `WC_RSA_PSS`, wolfBoot ECC256, and -- under `WOLFHSM_CFG_CERTIFICATE_MANAGER` -- wolfBoot cert chain for both RSA4096 and RSAPSS4096) run against the shared server context instead of each building its own server/NVM/transport. Each subtest scrubs the NVM objects and key-cache entries it creates so the group's shared NVM stays clean. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_she.c` (SHE<->keywrap interop + `wh_She_TestWrappedInterop`) | `misc/wh_test_she_keywrap.c::whTest_SheKeywrapInterop` | Misc | needs a server whose NVM carries a `WH_NVM_FLAGS_TRUSTED` KEK, so it runs its own client/server pair with a sequential split-API pump; covers wrap-export by id (incl. the SHE slot-0 gate regression), key-wrap vs data-wrap domain separation, the unwrap-and-export type gate (a SHE-typed blob must not export as plaintext), unwrap-and-cache priming, the counter rollback guard, the LoadKey stale-cache regression, and the two-session reboot interop. Secure boot is driven with raw INIT/UPDATE/FINISH messages (blocking-only client API) | | `wh_test_she_no_nvm.c::whTest_SheNoNvm` | `misc/wh_test_she_no_nvm.c::whTest_SheNoNvm` | Misc | NULL-NVM server (`whServerConfig.nvm == NULL`), so it runs its own client/server pair with a sequential split-API pump instead of the legacy pthread harness; same end-to-end flow (unwrap-and-cache provisioning incl. SHE slot 0, secure boot, LoadKey cache path, ECB/CBC/CMAC, ExportRamKey, PRNG, KEK immutability) | -| `wh_test_keywrap.c` (sw-KEK round trip, trusted-KEK policy negatives, data-wrap usage) | `client-server/wh_test_keywrap.c::whTest_KeyWrap` | Client | runs against any server: positive KeyWrap/KeyUnwrapAndExport under a plain client KEK, wrap-export/unwrap-and-cache refusal of a non-trusted KEK, the NVM-add / HKDF-cache / key-cache `WH_NVM_FLAGS_TRUSTED` forge negatives, the data-wrap `USAGE_WRAP` negative, and the key/data unwrap underflow bad-args checks. The trusted-KEK positive paths (wrap-export round-trip incl. NONEXPORTABLE enforcement, unwrap-and-cache, and the cache-only check that commit/erase reject an unwrapped key id) run in `misc/wh_test_hwkeystore.c` against the hardware KEK | +| `wh_test_keywrap.c` (sw-KEK round trip, trusted-KEK policy negatives, data-wrap usage) | `client-server/wh_test_keywrap.c::whTest_KeyWrap` | Client | runs against any server: positive KeyWrap/KeyUnwrapAndExport under a plain client KEK, wrap-export/unwrap-and-cache refusal of a non-trusted KEK, the NVM-add / HKDF-cache / key-cache `WH_NVM_FLAGS_TRUSTED` forge negatives, the data-wrap `USAGE_WRAP` negative, and the key/data unwrap underflow bad-args checks. The trusted-KEK positive paths (wrap-export round-trip incl. NONEXPORTABLE enforcement, unwrap-and-cache, and the cache-only check that commit/erase reject an unwrapped key id) run in `misc/wh_test_portkey.c` against the port KEK | | `wh_test_log.c::whTest_Log` (frontend, macros, ring buffer, mock/ringbuf generic harness) | `misc/wh_test_log.c::whTest_Log` | Misc | Portable subset: frontend API, log macros, formatted macros, ring buffer backend, and the backend-agnostic harness against the mock and ring buffer backends. The harness itself lives in `misc/wh_test_log_backend.h` so the POSIX port reuses it | | `wh_test_log.c` (POSIX file backend, concurrent, client/server) | `posix/wh_test_log_posix.c::{whTest_LogPosixFile_Generic, whTest_LogPosixFile, whTest_LogPosixFileConcurrent, whTest_LogClientServerMemTransport}` | POSIX port-specific (`whTestGroup_RunOne`) | POSIX file backend (via the shared harness and directly), multi-threaded concurrent writers, and a client/server log smoke test over the mem transport. Need pthreads and the host file system, so they run from the port main, not the portable registry | @@ -116,7 +116,7 @@ Not yet migrated (still live in `wolfHSM/test/`): | `wh_test_comm.c::whTest_Comm` | Pthread mem/tcp/shmem variants only; sequential mem variant has been ported | | `wh_test_clientserver.c::whTest_ClientServer` | Pthread variant: remaining coverage is the custom-callback round-trip (`_testCallbacks`) and the server-side DMA register/copy/allowlist exercise (`_testDma`). The sequential test is dropped, as is the FLASH_LOG NVM matrix variant. | | `wh_test_crypto.c::whTest_Crypto` | Remaining crypto coverage not yet split out: the AES async family (comm-buffer `whTest_CryptoAesAsync`/`AesAsyncKat` + DMA `whTest_CryptoAesDmaAsync`/`AesDmaAsyncKat`, round-trip & KAT). ECC DMA export-public and the ML-DSA wolfCrypt-API path are now migrated. | -| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | Policy coverage (sw-KEK round trip, trusted-KEK negatives, data-wrap usage) and the key/data unwrap underflow negatives are ported to `client-server/wh_test_keywrap.c`; the remaining legacy coverage (hardware-KEK cryptocb use of a cached wrapped key, unwrap oversize negatives) is not | +| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | Policy coverage (sw-KEK round trip, trusted-KEK negatives, data-wrap usage) and the key/data unwrap underflow negatives are ported to `client-server/wh_test_keywrap.c`; the unwrap oversize negatives are not. The legacy port-KEK tests, including cryptocb use of a cached wrapped key, no longer build: legacy `test/` does not enable `WOLFHSM_CFG_PORTKEY` | | `wh_test_timeout.c::whTest_TimeoutPosix` | | | `wh_test_nvmflags.c::whTest_NvmFlags` | | | `wh_test_flash_fault_inject.c` | | diff --git a/test-refactor/client-server/wh_test_keywrap.c b/test-refactor/client-server/wh_test_keywrap.c index 139ca0904..c6a876601 100644 --- a/test-refactor/client-server/wh_test_keywrap.c +++ b/test-refactor/client-server/wh_test_keywrap.c @@ -38,7 +38,7 @@ * The positive wrap/unwrap-and-export round trip under a plain client KEK * lives in wh_test_crypto_keywrap.c. The trusted-KEK positive paths * (wrap-export round-trip, unwrap-and-cache) live in - * misc/wh_test_hwkeystore.c against the hardware KEK, and in + * misc/wh_test_portkey.c against the port KEK, and in * misc/wh_test_multiclient.c against an NVM-provisioned KEK. */ @@ -109,12 +109,13 @@ static int _CacheSwKek(whClientContext* client, whKeyId* outKekId) return WH_ERROR_OK; } -/* The wrap-export and unwrap-and-cache operations require a trusted KEK (HW or - * WH_NVM_FLAGS_TRUSTED). Prove that (a) a plain client-cached USAGE_WRAP key is - * refused as their KEK, and that a client cannot forge a trusted KEK by - * setting WH_NVM_FLAGS_TRUSTED itself through (b) the checked NVM add path, (c) - * the HKDF cache-import path, (d) the key cache path, or (e) the cache-random - * path -- the server strips the flag on each, so the key is still refused */ +/* The wrap-export and unwrap-and-cache operations require a trusted KEK (a + * fixed port key or WH_NVM_FLAGS_TRUSTED). Prove that (a) a plain + * client-cached USAGE_WRAP key is refused as their KEK, and that a client + * cannot forge a trusted KEK by setting WH_NVM_FLAGS_TRUSTED itself through + * (b) the checked NVM add path, (c) the HKDF cache-import path, (d) the key + * cache path, or (e) the cache-random path -- the server strips the flag on + * each, so the key is still refused */ static int _whTest_KeywrapTrustedKekPolicy(whClientContext* client) { int ret; diff --git a/test-refactor/config/wolfhsm_cfg.h b/test-refactor/config/wolfhsm_cfg.h index 8f4eda94c..6a3c0d28b 100644 --- a/test-refactor/config/wolfhsm_cfg.h +++ b/test-refactor/config/wolfhsm_cfg.h @@ -52,7 +52,7 @@ #ifndef WOLFHSM_CFG_NO_CRYPTO #define WOLFHSM_CFG_KEYWRAP -#define WOLFHSM_CFG_HWKEYSTORE +#define WOLFHSM_CFG_PORTKEY #endif /* Test-only SHE key management APIs. Bypass authenticated update protocols. */ diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index 699d503ca..71a015512 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -72,6 +72,12 @@ whMessageKeystore_ExportResponse keyExportRes; whMessageKeystore_EraseResponse keyEraseRes; whMessageKeystore_ExportPublicRequest keyExportPublicReq; whMessageKeystore_ExportPublicResponse keyExportPublicRes; +whMessageKeystore_PortLoadRequest keyPortLoadReq; +whMessageKeystore_PortLoadResponse keyPortLoadRes; +whMessageKeystore_PortClearRequest keyPortClearReq; +whMessageKeystore_PortClearResponse keyPortClearRes; +whMessageKeystore_PortInfoRequest keyPortInfoReq; +whMessageKeystore_PortInfoResponse keyPortInfoRes; /* Include counter message header for new counter message structures */ #include "wolfhsm/wh_message_counter.h" diff --git a/test-refactor/misc/wh_test_multiclient.c b/test-refactor/misc/wh_test_multiclient.c index 9bf60ee69..e28bba532 100644 --- a/test-refactor/misc/wh_test_multiclient.c +++ b/test-refactor/misc/wh_test_multiclient.c @@ -2109,35 +2109,36 @@ static int _testNvmNonAddVerbRejections(whClientContext* client1, } /* - * Verify that WRAPPED and HW client flags cannot access differently typed + * Verify that WRAPPED and PORT client flags cannot access differently typed * objects through the NVM API. */ -static int _testNvmWrappedHwFlagIsolation(whClientContext* client1, - whServerContext* server1, - whClientContext* client2, - whServerContext* server2) +static int _testNvmWrappedPortFlagIsolation(whClientContext* client1, + whServerContext* server1, + whClientContext* client2, + whServerContext* server2) { const whNvmId planted_id = 9; const uint8_t secret[] = "planted-non-nvm-secret"; const whNvmSize secretSz = (whNvmSize)sizeof(secret); const whKeyId clientFlags[2] = {WH_KEYID_CLIENT_WRAPPED_FLAG, - WH_KEYID_CLIENT_HW_FLAG}; + WH_KEYID_CLIENT_PORT_FLAG}; int32_t out_rc = 0; whNvmSize out_len = 0; uint8_t buf[64] = {0}; whNvmMetadata meta = {0}; whKeyId wrappedId; - whKeyId hwId; + whKeyId portKeyId; int i; (void)client2; (void)server2; - WH_TEST_PRINT("Testing NVM WRAPPED/HW flag type isolation...\n"); + WH_TEST_PRINT("Testing NVM WRAPPED/PORT flag type isolation...\n"); wrappedId = WH_MAKE_KEYID(WH_KEYTYPE_WRAPPED, server1->comm->client_id, planted_id); - hwId = WH_MAKE_KEYID(WH_KEYTYPE_HW, server1->comm->client_id, planted_id); + portKeyId = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, server1->comm->client_id, planted_id); meta.access = WH_NVM_ACCESS_ANY; meta.flags = WH_NVM_FLAGS_NONE; @@ -2145,7 +2146,7 @@ static int _testNvmWrappedHwFlagIsolation(whClientContext* client1, meta.id = wrappedId; WH_TEST_ASSERT_RETURN( wh_Nvm_AddObject(server1->nvm, &meta, secretSz, secret) == WH_ERROR_OK); - meta.id = hwId; + meta.id = portKeyId; WH_TEST_ASSERT_RETURN( wh_Nvm_AddObject(server1->nvm, &meta, secretSz, secret) == WH_ERROR_OK); @@ -2171,13 +2172,13 @@ static int _testNvmWrappedHwFlagIsolation(whClientContext* client1, WH_TEST_ASSERT_RETURN(wh_Nvm_GetMetadata(server1->nvm, wrappedId, &meta) == WH_ERROR_OK); - WH_TEST_ASSERT_RETURN(wh_Nvm_GetMetadata(server1->nvm, hwId, &meta) == + WH_TEST_ASSERT_RETURN(wh_Nvm_GetMetadata(server1->nvm, portKeyId, &meta) == WH_ERROR_OK); (void)wh_Nvm_DestroyObjects(server1->nvm, 1, &wrappedId); - (void)wh_Nvm_DestroyObjects(server1->nvm, 1, &hwId); + (void)wh_Nvm_DestroyObjects(server1->nvm, 1, &portKeyId); - WH_TEST_PRINT(" NVM WRAPPED/HW flag isolation: PASS\n"); + WH_TEST_PRINT(" NVM WRAPPED/PORT flag isolation: PASS\n"); return WH_ERROR_OK; } @@ -2203,7 +2204,7 @@ static int _runNvmIdTranslationTests(whClientContext* client1, WH_TEST_RETURN_ON_FAIL( _testNvmNonAddVerbRejections(client1, server1, client2, server2)); WH_TEST_RETURN_ON_FAIL( - _testNvmWrappedHwFlagIsolation(client1, server1, client2, server2)); + _testNvmWrappedPortFlagIsolation(client1, server1, client2, server2)); WH_TEST_PRINT("All NVM Id Translation Tests PASSED ===\n"); return WH_ERROR_OK; } diff --git a/test-refactor/misc/wh_test_hwkeystore.c b/test-refactor/misc/wh_test_portkey.c similarity index 53% rename from test-refactor/misc/wh_test_hwkeystore.c rename to test-refactor/misc/wh_test_portkey.c index 1a1c31c91..aaa1c6788 100644 --- a/test-refactor/misc/wh_test_hwkeystore.c +++ b/test-refactor/misc/wh_test_portkey.c @@ -17,42 +17,50 @@ * along with wolfHSM. If not, see . */ /* - * test-refactor/misc/wh_test_hwkeystore.c + * test-refactor/misc/wh_test_portkey.c * - * End-to-end hardware-only key (WH_KEYTYPE_HW) coverage. Lives in the - * misc group because it requires a server with a known hardware keystore - * backend bound, which the port's shared server does not guarantee: this - * test spins up its own client/server pair over the mem transport, binds an - * emulated hardware keystore serving a single AES-256 KEK, and pumps the - * server inline between the split (non-blocking) client request/response - * calls so no threading is needed. + * End-to-end coverage of port keys (WH_KEYTYPE_PORT) as key wrap KEKs. Lives + * in the misc group because it needs a server with known port keys, + * which the port's shared server does not guarantee: this test spins up its + * own client/server pair over the mem transport and pumps the server inline + * between the split (non-blocking) client request/response calls, so no + * threading is needed. A mock wolfCrypt device holds one AES-256 KEK, which + * the port registers as port key WH_TEST_PORTKEK_ID, and runs AES-GCM with + * it when the server passes the key by reference. * - * _whTest_HwKeystoreKeyWrap - key wrap / unwrap-export / unwrap-and-cache - * with a hardware-only KEK; wrapped+hwonly - * flag precedence; unserved-id and wrong-KEK - * negative paths - * _whTest_HwKeystoreWrappedKeyNoNvm - an unwrapped key is cache-only, so - * commit and erase must reject a wrapped key - * id while eviction still works - * _whTest_HwKeystoreKeyWrapExport - wrap-export a server-held key by id - * (never presenting plaintext) under the - * hardware KEK, round-trip it, and enforce - * NONEXPORTABLE - * _whTest_HwKeystoreDataWrap - data wrap/unwrap roundtrip with a - * hardware-only KEK - * _whTest_HwKeystoreRejections - keystore operations on a hardware-only id - * must fail with WH_ERROR_ACCESS + * _whTest_PortKeyKeyWrap - key wrap / unwrap-export / + * unwrap-and-cache with the port KEK; + * wrapped+port key flag precedence; + * unregistered-id and wrong-KEK negative + * paths + * _whTest_PortKeyWrappedKeyNoNvm - an unwrapped key is cache-only, so + * commit and erase must reject a wrapped + * key id while eviction still works + * _whTest_PortKeyKeyWrapExport - wrap-export a server-held key by id + * (never presenting plaintext) under the + * port KEK, round-trip it, and enforce + * NONEXPORTABLE + * _whTest_PortKeyDataWrap - data wrap/unwrap roundtrip with the + * port KEK + * _whTest_PortKeyRejections - keystore operations on a port key id + * must fail with WH_ERROR_ACCESS + * _whTest_PortKeyKekPolicy - a port key without WRAP usage, or + * another client's, is refused as a KEK + * _whTest_PortKeyUnbound - a port KEK fails with NOTFOUND on a + * server with no port keys bound + * _whTest_PortKeyByValueKek - a by-value KEK, whose bytes the port + * supplies, in every wrap operation and + * without a crypto device * - * Crypto key use of a hardware-only id (rejected at the keystore freshen - * choke point) is covered server-side in server/wh_test_hwkeystore_server.c, - * since the blocking wolfCrypt callback cannot be pumped single-threaded. + * AES and CMAC with port keys, and client-loaded keys as KEKs, are covered + * in misc/wh_test_portkey_crypto.c. */ #include "wolfhsm/wh_settings.h" #if defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ defined(WOLFHSM_CFG_ENABLE_SERVER) && !defined(WOLFHSM_CFG_NO_CRYPTO) && \ - defined(WOLFHSM_CFG_KEYWRAP) && defined(WOLFHSM_CFG_HWKEYSTORE) && \ + defined(WOLFHSM_CFG_KEYWRAP) && defined(WOLFHSM_CFG_PORTKEY) && \ !defined(NO_AES) && defined(HAVE_AESGCM) #include @@ -60,12 +68,15 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/error-crypt.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/cryptocb.h" #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_common.h" #include "wolfhsm/wh_comm.h" #include "wolfhsm/wh_keyid.h" -#include "wolfhsm/wh_hwkeystore.h" +#include "wolfhsm/wh_portkey.h" #include "wolfhsm/wh_nvm.h" #include "wolfhsm/wh_nvm_flash.h" #include "wolfhsm/wh_flash_ramsim.h" @@ -81,9 +92,9 @@ #define FLASH_SECTOR_SIZE (128 * 1024) #define FLASH_PAGE_SIZE 8 -/* Id and material of the only KEK served by the emulated hardware keystore */ -#define WH_TEST_HWKEK_ID 3 -#define WH_TEST_HWKEK_SIZE 32 +/* Id and size of the mock device's KEK, which the port registers */ +#define WH_TEST_PORTKEK_ID 3 +#define WH_TEST_PORTKEK_SIZE 32 #define WH_TEST_AES_KEYSIZE 32 #define WH_TEST_WRAPPED_KEYID 20 @@ -93,52 +104,115 @@ (WH_KEYWRAP_AES_GCM_HEADER_SIZE + WH_TEST_AES_KEYSIZE + \ sizeof(whNvmMetadata)) -static const uint8_t _hwKekMaterial[WH_TEST_HWKEK_SIZE] = { +static const uint8_t _portKekMaterial[WH_TEST_PORTKEK_SIZE] = { 0x9a, 0x4e, 0x21, 0xc7, 0x5d, 0x10, 0xfb, 0x33, 0x6f, 0x82, 0xd4, 0x59, 0xee, 0x07, 0xb1, 0x2c, 0x48, 0x95, 0x3a, 0xc6, 0x71, 0x0d, 0xb8, 0xe5, 0x12, 0x6a, 0xf9, 0x84, 0x2f, 0xd0, 0x5b, 0xa7}; -static int _HwKeystoreGetKey(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len) +/* wolfCrypt device of the mock hardware */ +#define WH_TEST_PORT_DEVID 0x4D4B454B + +/* Port keys _whTest_PortKeyKekPolicy registers: one without WRAP usage, + * and one that belongs to another client */ +#define WH_TEST_PORT_NOWRAP_ID (WH_TEST_PORTKEK_ID + 2) +#define WH_TEST_PORT_OTHER_ID (WH_TEST_PORTKEK_ID + 3) + +/* A by-value KEK, whose bytes the port supplies through GetKey, and the + * keystore key _whTest_PortKeyByValueKek wraps with it */ +#define WH_TEST_BYVALUE_KEK_ID (WH_TEST_PORTKEK_ID + 4) +#define WH_TEST_BYVALUE_SRC_KEYID 23 + +static const uint8_t _byValueKekMaterial[WH_TEST_PORTKEK_SIZE] = { + 0x5d, 0x0e, 0xa3, 0x71, 0xc8, 0x2f, 0x94, 0x16, 0xeb, 0x38, 0x7a, + 0xd1, 0x05, 0xb6, 0x4c, 0x99, 0x23, 0xf0, 0x6e, 0x8a, 0x17, 0xc2, + 0x5b, 0x30, 0xad, 0x74, 0xe9, 0x02, 0x86, 0x3f, 0xd5, 0x61}; + +/* AES-GCM operations the mock device ran with the KEK */ +static int _portKekOps; + +/* Times the port supplied the by-value KEK */ +static int _byValueKekReads; + +static int _ByValueGetKey(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len) { (void)context; - /* Only hardware-only keyIds should ever reach a hardware keystore */ - if (WH_KEYID_TYPE(keyId) != WH_KEYTYPE_HW) { - return WH_ERROR_ACCESS; - } - - /* Serve only the known test KEK id, refuse everything else */ - if (WH_KEYID_ID(keyId) != WH_TEST_HWKEK_ID) { + if (WH_KEYID_ID(keyId) != WH_TEST_BYVALUE_KEK_ID) { return WH_ERROR_NOTFOUND; } - - if ((out == NULL) || (inout_len == NULL) || - (*inout_len < sizeof(_hwKekMaterial))) { + if (*inout_len < sizeof(_byValueKekMaterial)) { return WH_ERROR_BUFFER_SIZE; } - - memcpy(out, _hwKekMaterial, sizeof(_hwKekMaterial)); - *inout_len = sizeof(_hwKekMaterial); + memcpy(out, _byValueKekMaterial, sizeof(_byValueKekMaterial)); + *inout_len = sizeof(_byValueKekMaterial); + _byValueKekReads++; return WH_ERROR_OK; } /* clang-format off */ -static const whHwKeystoreCb _hwKeystoreCb = { - .Init = NULL, - .Cleanup = NULL, - .GetKey = _HwKeystoreGetKey, +static const whPortKeyCb _byValueCb = { + .GetKey = _ByValueGetKey, }; /* clang-format on */ +/* Runs AES-GCM with the KEK when the server passes it by reference. Software + * keys are left to wolfCrypt */ +static int _MockCryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + Aes* aes; + Aes sw[1]; + int ret; + + (void)devId; + (void)ctx; + + if ((info->algo_type != WC_ALGO_TYPE_CIPHER) || + (info->cipher.type != WC_CIPHER_AES_GCM)) { + return CRYPTOCB_UNAVAILABLE; + } + aes = (info->cipher.enc != 0) ? info->cipher.aesgcm_enc.aes + : info->cipher.aesgcm_dec.aes; + if (aes->idLen == 0) { + return CRYPTOCB_UNAVAILABLE; + } + /* No other port key holds a KEK */ + if (wh_PortKey_KeyRefToId(aes->id, (uint32_t)aes->idLen) != + WH_TEST_PORTKEK_ID) { + return WC_HW_E; + } + + ret = wc_AesInit(sw, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(sw, _portKekMaterial, sizeof(_portKekMaterial)); + if ((ret == 0) && (info->cipher.enc != 0)) { + wc_CryptoCb_AesAuthEnc* e = &info->cipher.aesgcm_enc; + ret = wc_AesGcmEncrypt(sw, e->out, e->in, e->sz, e->iv, e->ivSz, + e->authTag, e->authTagSz, e->authIn, + e->authInSz); + } + else if (ret == 0) { + wc_CryptoCb_AesAuthDec* d = &info->cipher.aesgcm_dec; + ret = wc_AesGcmDecrypt(sw, d->out, d->in, d->sz, d->iv, d->ivSz, + d->authTag, d->authTagSz, d->authIn, + d->authInSz); + } + wc_AesFree(sw); + } + if (ret == 0) { + _portKekOps++; + } + return ret; +} + /* Self-contained client/server pair over the mem transport. The server is * pumped inline (wh_Server_HandleRequestMessage) between each split client * request/response pair */ typedef struct { - whServerContext server[1]; - whClientContext client[1]; - whNvmContext nvm[1]; - whHwKeystoreContext hwKeystore[1]; + whServerContext server[1]; + whClientContext client[1]; + whNvmContext nvm[1]; + whPortKeyContext portKey[1]; #ifndef WOLFHSM_CFG_NO_CRYPTO whServerCryptoContext crypto[1]; #endif @@ -161,9 +235,9 @@ typedef struct { whNvmFlashContext nfc[1]; whNvmCb nfcb[1]; whNvmConfig n_conf[1]; - /* Hardware keystore */ - whHwKeystoreConfig hwks_conf[1]; - whServerConfig s_conf[1]; + /* Port keys */ + whPortKeyConfig pks_conf[1]; + whServerConfig s_conf[1]; } TestCtx; /* Static to keep the misc group's stack footprint small */ @@ -224,10 +298,9 @@ static int _SetupClientServer(TestCtx* t) .config = t->nf_conf, }; - /* Hardware keystore front-end backed by the test getKey callback */ - t->hwks_conf[0] = (whHwKeystoreConfig){ - .cb = &_hwKeystoreCb, - .context = NULL, + /* Port keys, with a GetKey hook for the by-value KEK */ + t->pks_conf[0] = (whPortKeyConfig){ + .cb = &_byValueCb, }; /* Server config */ @@ -236,14 +309,22 @@ static int _SetupClientServer(TestCtx* t) .nvm = t->nvm, #ifndef WOLFHSM_CFG_NO_CRYPTO .crypto = t->crypto, - .devId = INVALID_DEVID, + .devId = WH_TEST_PORT_DEVID, #endif - .hwKeystore = t->hwKeystore, + .portKey = t->portKey, }; WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(t->nvm, t->n_conf)); - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(t->hwKeystore, t->hwks_conf)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(t->portKey, t->pks_conf)); + /* The port's KEK, shared by all clients */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, WH_TEST_PORTKEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_REF)); WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL( + wc_CryptoCb_RegisterDevice(WH_TEST_PORT_DEVID, _MockCryptoCb, NULL)); WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(t->crypto->rng, NULL, INVALID_DEVID)); WH_TEST_RETURN_ON_FAIL(wh_Server_Init(t->server, t->s_conf)); WH_TEST_RETURN_ON_FAIL( @@ -265,8 +346,9 @@ static void _CleanupClientServer(TestCtx* t) (void)wh_Client_Cleanup(t->client); (void)wh_Server_Cleanup(t->server); (void)wh_Nvm_Cleanup(t->nvm); - (void)wh_HwKeystore_Cleanup(t->hwKeystore); + (void)wh_PortKey_Cleanup(t->portKey); (void)wc_FreeRng(t->crypto->rng); + wc_CryptoCb_UnRegisterDevice(WH_TEST_PORT_DEVID); (void)wolfCrypt_Cleanup(); } @@ -384,10 +466,10 @@ static int _KeyRevoke(TestCtx* t, uint16_t keyId) return wh_Client_KeyRevokeResponse(t->client); } -static int _whTest_HwKeystoreKeyWrap(TestCtx* t) +static int _whTest_PortKeyKeyWrap(TestCtx* t) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); uint8_t plainKey[WH_TEST_AES_KEYSIZE]; uint8_t tmpPlainKey[WH_TEST_AES_KEYSIZE]; uint16_t tmpPlainKeySz = sizeof(tmpPlainKey); @@ -396,71 +478,76 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) uint16_t unwrappedKeyId = WH_KEYID_ERASED; whNvmMetadata metadata = {0}; whNvmMetadata tmpMetadata = {0}; + int ops = _portKekOps; size_t i; metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(t->client->comm->client_id, WH_TEST_WRAPPED_KEYID); metadata.len = WH_TEST_AES_KEYSIZE; metadata.flags = WH_NVM_FLAGS_USAGE_ANY; - memcpy(metadata.label, "HW KEK wrapped key", sizeof("HW KEK wrapped key")); + memcpy(metadata.label, "port KEK wrapped key", + sizeof("port KEK wrapped key")); /* Fixed pattern; distinct from the KEK material */ for (i = 0; i < sizeof(plainKey); i++) { plainKey[i] = (uint8_t)(0xA0 ^ i); } - /* Wrap a key using the hardware-only KEK */ - ret = _KeyWrap(t, hwKekId, plainKey, sizeof(plainKey), &metadata, + /* Wrap a key using the port KEK */ + ret = _KeyWrap(t, portKekId, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to KeyWrap with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to KeyWrap with port KEK %d\n", ret); return ret; } - /* Unwrap and export with the hardware-only KEK, check the roundtrip */ - ret = _KeyUnwrapAndExport(t, hwKekId, wrappedKey, wrappedKeySz, + /* Unwrap and export with the port KEK, check the roundtrip */ + ret = _KeyUnwrapAndExport(t, portKekId, wrappedKey, wrappedKeySz, &tmpMetadata, tmpPlainKey, &tmpPlainKeySz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to KeyUnwrapAndExport with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to KeyUnwrapAndExport with port KEK %d\n", ret); return ret; } + /* The mock device ran both operations with the KEK, by reference */ + WH_TEST_ASSERT_RETURN(_portKekOps == ops + 2); + if (memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { - WH_ERROR_PRINT("HW KEK wrap/unwrap key failed to match\n"); + WH_ERROR_PRINT("port KEK wrap/unwrap key failed to match\n"); return WH_ERROR_ABORTED; } if (memcmp(&metadata, &tmpMetadata, sizeof(metadata)) != 0) { - WH_ERROR_PRINT("HW KEK wrap/unwrap metadata failed to match\n"); + WH_ERROR_PRINT("port KEK wrap/unwrap metadata failed to match\n"); return WH_ERROR_ABORTED; } - /* Unwrap-and-cache with the hardware-only KEK: the wrapped payload is an + /* Unwrap-and-cache with the port KEK: the wrapped payload is an * ordinary key and may enter the cache; only the KEK itself is - * hardware-resident */ - ret = _KeyUnwrapAndCache(t, hwKekId, wrappedKey, wrappedKeySz, + * held by the port */ + ret = _KeyUnwrapAndCache(t, portKekId, wrappedKey, wrappedKeySz, &unwrappedKeyId); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to KeyUnwrapAndCache with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to KeyUnwrapAndCache with port KEK %d\n", ret); return ret; } WH_TEST_RETURN_ON_FAIL(_KeyEvict(t, unwrappedKeyId)); - /* A wrapped+hardware-only KEK id must behave as hardware-only (the - * hardware-only flag takes precedence) */ + /* A KEK id with both the wrapped and port key flags is a port key + * (the port key flag takes precedence) */ wrappedKeySz = sizeof(wrappedKey); - ret = _KeyWrap(t, hwKekId | WH_KEYID_CLIENT_WRAPPED_FLAG, plainKey, - sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); + ret = _KeyWrap(t, portKekId | WH_KEYID_CLIENT_WRAPPED_FLAG, plainKey, + sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to KeyWrap with wrapped+HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to KeyWrap with wrapped+port KEK %d\n", ret); return ret; } - /* Unwrapping a hardware-KEK-wrapped blob with a different, cached KEK + /* Unwrapping a port-KEK-wrapped blob with a different, cached KEK * must fail authentication, proving distinct key material was used */ { uint16_t cachedKekId = WH_KEYID_ERASED; - uint8_t cachedKek[WH_TEST_HWKEK_SIZE]; + uint8_t cachedKek[WH_TEST_PORTKEK_SIZE]; uint8_t kekLabel[] = "cached-kek"; for (i = 0; i < sizeof(cachedKek); i++) { @@ -481,12 +568,13 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) } } - /* A hardware KEK id the backend does not serve must fail */ + /* A port key the server has no record of must fail */ wrappedKeySz = sizeof(wrappedKey); - ret = _KeyWrap(t, WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID + 1), plainKey, - sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); + ret = + _KeyWrap(t, WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID + 1), plainKey, + sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_NOTFOUND) { - WH_ERROR_PRINT("KeyWrap with unserved HW KEK expected NOTFOUND, " + WH_ERROR_PRINT("KeyWrap with unregistered port KEK expected NOTFOUND, " "got %d\n", ret); return WH_ERROR_ABORTED; @@ -497,12 +585,12 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) /* An unwrapped key is cache-only and must never reach NVM, so that it cannot * shadow a protected NVM entry. Commit and erase must reject a wrapped key id - * while eviction still works. Uses the hardware KEK, since unwrap-and-cache + * while eviction still works. Uses the port KEK, since unwrap-and-cache * requires a trusted KEK. */ -static int _whTest_HwKeystoreWrappedKeyNoNvm(TestCtx* t) +static int _whTest_PortKeyWrappedKeyNoNvm(TestCtx* t) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); uint8_t plainKey[WH_TEST_AES_KEYSIZE]; uint8_t wrappedKey[WH_TEST_WRAPPED_KEYSIZE]; uint16_t wrappedKeySz = sizeof(wrappedKey); @@ -520,11 +608,11 @@ static int _whTest_HwKeystoreWrappedKeyNoNvm(TestCtx* t) plainKey[i] = (uint8_t)(0x3B ^ i); } - WH_TEST_RETURN_ON_FAIL(_KeyWrap(t, hwKekId, plainKey, sizeof(plainKey), + WH_TEST_RETURN_ON_FAIL(_KeyWrap(t, portKekId, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz)); - WH_TEST_RETURN_ON_FAIL( - _KeyUnwrapAndCache(t, hwKekId, wrappedKey, wrappedKeySz, &cachedKeyId)); + WH_TEST_RETURN_ON_FAIL(_KeyUnwrapAndCache(t, portKekId, wrappedKey, + wrappedKeySz, &cachedKeyId)); /* Commit must refuse to persist a wrapped key to NVM */ ret = _KeyCommit(t, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedKeyId)); @@ -554,12 +642,12 @@ static int _whTest_HwKeystoreWrappedKeyNoNvm(TestCtx* t) /* Exercises wh_Client_KeyWrapExport: wrap a key the server already holds (by * id, never presenting plaintext), round-trip it through unwrap-and-cache, * confirm the cached and exported material match the source, and confirm - * NONEXPORTABLE is honored. Wrap-export requires a trusted KEK; the hardware + * NONEXPORTABLE is honored. Wrap-export requires a trusted KEK; the port * KEK qualifies. */ -static int _whTest_HwKeystoreKeyWrapExport(TestCtx* t) +static int _whTest_PortKeyKeyWrapExport(TestCtx* t) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); uint8_t srcKey[WH_TEST_AES_KEYSIZE]; uint16_t srcKeyId = WH_TEST_WRAPEXPORT_KEYID; uint8_t label[WH_NVM_LABEL_LEN] = "WrapExport Src"; @@ -581,16 +669,16 @@ static int _whTest_HwKeystoreKeyWrapExport(TestCtx* t) &srcKeyId)); /* Wrap-and-export the cached key by id; the client never sees plaintext */ - ret = _KeyWrapExport(t, srcKeyId, WH_KEYTYPE_CRYPTO, hwKekId, wrappedKey, + ret = _KeyWrapExport(t, srcKeyId, WH_KEYTYPE_CRYPTO, portKekId, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to KeyWrapExport with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to KeyWrapExport with port KEK %d\n", ret); return ret; } /* Round-trip: unwrap-and-cache the blob (comes back as a wrapped key) */ - ret = - _KeyUnwrapAndCache(t, hwKekId, wrappedKey, wrappedKeySz, &wrappedKeyId); + ret = _KeyUnwrapAndCache(t, portKekId, wrappedKey, wrappedKeySz, + &wrappedKeyId); if (ret != WH_ERROR_OK) { WH_ERROR_PRINT("Failed to unwrap-and-cache wrap-exported key %d\n", ret); @@ -613,7 +701,7 @@ static int _whTest_HwKeystoreKeyWrapExport(TestCtx* t) /* The exported material must match the original, and the embedded id must * have been normalized to the wrapped-key namespace */ tmpKeySz = sizeof(tmpKey); - ret = _KeyUnwrapAndExport(t, hwKekId, wrappedKey, wrappedKeySz, &tmpMeta, + ret = _KeyUnwrapAndExport(t, portKekId, wrappedKey, wrappedKeySz, &tmpMeta, tmpKey, &tmpKeySz); if (ret != WH_ERROR_OK) { WH_ERROR_PRINT("Failed to unwrap-and-export wrap-exported key %d\n", @@ -643,8 +731,8 @@ static int _whTest_HwKeystoreKeyWrapExport(TestCtx* t) WH_TEST_RETURN_ON_FAIL(_KeyCache( t, WH_NVM_FLAGS_USAGE_ANY | WH_NVM_FLAGS_NONEXPORTABLE, neLabel, sizeof(neLabel), srcKey, sizeof(srcKey), &neKeyId)); - ret = _KeyWrapExport(t, neKeyId, WH_KEYTYPE_CRYPTO, hwKekId, wrappedKey, - &neWrappedSz); + ret = _KeyWrapExport(t, neKeyId, WH_KEYTYPE_CRYPTO, portKekId, + wrappedKey, &neWrappedSz); WH_TEST_RETURN_ON_FAIL(_KeyEvict(t, neKeyId)); if (ret != WH_ERROR_ACCESS) { WH_ERROR_PRINT( @@ -657,104 +745,106 @@ static int _whTest_HwKeystoreKeyWrapExport(TestCtx* t) return WH_ERROR_OK; } -static int _whTest_HwKeystoreDataWrap(TestCtx* t) +static int _whTest_PortKeyDataWrap(TestCtx* t) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); - uint8_t data[] = "Example data!"; + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); + uint8_t data[] = "Example data!"; uint8_t unwrappedData[sizeof(data)] = {0}; uint32_t unwrappedDataSz = sizeof(unwrappedData); uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE] = {0}; uint32_t wrappedDataSz = sizeof(wrappedData); - ret = - _DataWrap(t, hwKekId, data, sizeof(data), wrappedData, &wrappedDataSz); + ret = _DataWrap(t, portKekId, data, sizeof(data), wrappedData, + &wrappedDataSz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to DataWrap with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to DataWrap with port KEK %d\n", ret); return ret; } - ret = _DataUnwrap(t, hwKekId, wrappedData, sizeof(wrappedData), + ret = _DataUnwrap(t, portKekId, wrappedData, sizeof(wrappedData), unwrappedData, &unwrappedDataSz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to DataUnwrap with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to DataUnwrap with port KEK %d\n", ret); return ret; } if (memcmp(data, unwrappedData, sizeof(data)) != 0) { - WH_ERROR_PRINT("HW KEK unwrapped data failed to match input data\n"); + WH_ERROR_PRINT("port KEK unwrapped data failed to match input data\n"); return WH_ERROR_ABORTED; } return WH_ERROR_OK; } -/* Hardware-only keys must be rejected by every keystore operation; only the - * keywrap KEK paths may resolve them. Crypto key use is rejected at the same - * keystore choke points, covered in server/wh_test_hwkeystore_server.c */ -static int _whTest_HwKeystoreRejections(TestCtx* t) +/* Port keys must be rejected by every keystore operation. Key wrap and + * the crypto handlers use them through the port key front end instead */ +static int _whTest_PortKeyRejections(TestCtx* t) { int ret; - whKeyId hwKeyId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); - uint8_t buf[WH_TEST_HWKEK_SIZE] = {0}; - uint16_t bufSz = sizeof(buf); - uint8_t label[WH_NVM_LABEL_LEN] = "hwonly reject"; - uint16_t cacheKeyId = hwKeyId; + whKeyId portKeyId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); + uint8_t buf[WH_TEST_PORTKEK_SIZE] = {0}; + uint16_t bufSz = sizeof(buf); + uint8_t label[WH_NVM_LABEL_LEN] = "hwonly reject"; + uint16_t cacheKeyId = portKeyId; - /* Caching key material under a hardware-only id must be rejected */ + /* Caching key material under a port key id must be rejected */ ret = _KeyCache(t, WH_NVM_FLAGS_NONE, label, sizeof(label), buf, sizeof(buf), &cacheKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyCache of HW-only id expected ACCESS, got %d\n", ret); + WH_ERROR_PRINT("KeyCache of port key id expected ACCESS, got %d\n", + ret); return WH_ERROR_ABORTED; } #ifdef WOLFHSM_CFG_DMA - /* The DMA cache path must reject hardware-only ids as well */ - cacheKeyId = hwKeyId; + /* The DMA cache path must reject port key ids as well */ + cacheKeyId = portKeyId; WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheDmaRequest( t->client, WH_NVM_FLAGS_NONE, label, sizeof(label), buf, sizeof(buf), cacheKeyId)); WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(t->server)); ret = wh_Client_KeyCacheDmaResponse(t->client, &cacheKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyCacheDma of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyCacheDma of port key id expected ACCESS, got %d\n", ret); return WH_ERROR_ABORTED; } #endif - /* Exporting a hardware-only key must be rejected */ - ret = _KeyExport(t, hwKeyId, label, sizeof(label), buf, &bufSz); + /* Exporting a port key must be rejected */ + ret = _KeyExport(t, portKeyId, label, sizeof(label), buf, &bufSz); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyExport of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyExport of port key id expected ACCESS, got %d\n", ret); return WH_ERROR_ABORTED; } - /* Commit/evict/erase/revoke of a hardware-only key must be rejected */ - ret = _KeyCommit(t, hwKeyId); + /* Commit/evict/erase/revoke of a port key must be rejected */ + ret = _KeyCommit(t, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyCommit of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyCommit of port key id expected ACCESS, got %d\n", ret); return WH_ERROR_ABORTED; } - ret = _KeyEvict(t, hwKeyId); + ret = _KeyEvict(t, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyEvict of HW-only id expected ACCESS, got %d\n", ret); + WH_ERROR_PRINT("KeyEvict of port key id expected ACCESS, got %d\n", + ret); return WH_ERROR_ABORTED; } - ret = _KeyErase(t, hwKeyId); + ret = _KeyErase(t, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyErase of HW-only id expected ACCESS, got %d\n", ret); + WH_ERROR_PRINT("KeyErase of port key id expected ACCESS, got %d\n", + ret); return WH_ERROR_ABORTED; } - ret = _KeyRevoke(t, hwKeyId); + ret = _KeyRevoke(t, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyRevoke of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyRevoke of port key id expected ACCESS, got %d\n", ret); return WH_ERROR_ABORTED; } @@ -762,15 +852,65 @@ static int _whTest_HwKeystoreRejections(TestCtx* t) return WH_ERROR_OK; } -/* A hardware-only KEK requested against a server that has no hardware keystore - * bound (HWKEYSTORE compiled in, whServerConfig.hwKeystore left NULL) must fail +/* A KEK must allow WRAP usage and belong to the caller or be shared, the same + * policy the server applies to every other use of a port key */ +static int _whTest_PortKeyKekPolicy(TestCtx* t) +{ + uint8_t plainKey[WH_TEST_AES_KEYSIZE] = {0}; + uint8_t wrappedKey[WH_TEST_WRAPPED_KEYSIZE]; + uint16_t wrappedKeySz = sizeof(wrappedKey); + uint8_t data[] = "Example data!"; + uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE]; + uint32_t wrappedDataSz = sizeof(wrappedData); + whNvmMetadata metadata = {0}; + uint16_t otherUser = (uint16_t)(t->client->comm->client_id + 1); + + metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(t->client->comm->client_id, + WH_TEST_WRAPPED_KEYID); + metadata.len = WH_TEST_AES_KEYSIZE; + metadata.flags = WH_NVM_FLAGS_USAGE_ANY; + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, + WH_TEST_PORT_NOWRAP_ID), + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, + WH_TEST_PORTKEK_SIZE, WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, otherUser, WH_TEST_PORT_OTHER_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_REF)); + + /* No WRAP usage */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_USAGE == + _KeyWrap(t, WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORT_NOWRAP_ID), plainKey, + sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_USAGE == + _DataWrap(t, WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORT_NOWRAP_ID), data, + sizeof(data), wrappedData, &wrappedDataSz)); + + /* Another client's key */ + wrappedKeySz = sizeof(wrappedKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + _KeyWrap(t, WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORT_OTHER_ID), plainKey, + sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz)); + + return WH_ERROR_OK; +} + +/* A port KEK requested against a server that has no port keys + * bound (PORTKEY compiled in, whServerConfig.portKey left NULL) must fail * cleanly with NOTFOUND from the keywrap KEK resolver. Simulate the unbound - * configuration by detaching the server's keystore for the duration of the + * configuration by detaching the server's port keys for the duration of the * checks, then restore it for teardown */ -static int _whTest_HwKeystoreUnbound(TestCtx* t) +static int _whTest_PortKeyUnbound(TestCtx* t) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); uint8_t plainKey[WH_TEST_AES_KEYSIZE] = {0}; uint8_t wrappedKey[WH_TEST_WRAPPED_KEYSIZE]; uint16_t wrappedKeySz = sizeof(wrappedKey); @@ -778,45 +918,153 @@ static int _whTest_HwKeystoreUnbound(TestCtx* t) uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE]; uint32_t wrappedDataSz = sizeof(wrappedData); whNvmMetadata metadata = {0}; - whHwKeystoreContext* saved = t->server->hwKeystore; + whPortKeyContext* saved = t->server->portKey; metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(t->client->comm->client_id, WH_TEST_WRAPPED_KEYID); metadata.len = WH_TEST_AES_KEYSIZE; metadata.flags = WH_NVM_FLAGS_USAGE_ANY; - /* Detach the hardware keystore to mimic a server without one bound */ - t->server->hwKeystore = NULL; + /* Detach the port keys to mimic a server without them bound */ + t->server->portKey = NULL; /* KeyWrap (usage-enforcing KEK path) must report NOTFOUND */ - ret = _KeyWrap(t, hwKekId, plainKey, sizeof(plainKey), &metadata, + ret = _KeyWrap(t, portKekId, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_NOTFOUND) { - t->server->hwKeystore = saved; - WH_ERROR_PRINT("KeyWrap with HW KEK and no bound keystore expected " + t->server->portKey = saved; + WH_ERROR_PRINT("KeyWrap with port KEK and no bound keystore expected " "NOTFOUND, got %d\n", ret); return WH_ERROR_ABORTED; } /* DataWrap (non-enforcing KEK path) must report NOTFOUND as well */ - ret = - _DataWrap(t, hwKekId, data, sizeof(data), wrappedData, &wrappedDataSz); + ret = _DataWrap(t, portKekId, data, sizeof(data), wrappedData, + &wrappedDataSz); if (ret != WH_ERROR_NOTFOUND) { - t->server->hwKeystore = saved; - WH_ERROR_PRINT("DataWrap with HW KEK and no bound keystore expected " + t->server->portKey = saved; + WH_ERROR_PRINT("DataWrap with port KEK and no bound keystore expected " "NOTFOUND, got %d\n", ret); return WH_ERROR_ABORTED; } /* Restore the keystore for teardown */ - t->server->hwKeystore = saved; + t->server->portKey = saved; return WH_ERROR_OK; } -int whTest_HwKeystore(void* ctx) +/* A by-value KEK: the server wraps with the bytes the port supplies, so the + * mock device never runs it. It is trusted, and it works without a crypto + * device, unlike a KEK used by reference */ +static int _whTest_PortKeyByValueKek(TestCtx* t) +{ + whKeyId kekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_BYVALUE_KEK_ID); + whKeyId refKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); + uint8_t srcKey[WH_TEST_AES_KEYSIZE]; + uint16_t srcKeyId = WH_TEST_BYVALUE_SRC_KEYID; + uint8_t label[WH_NVM_LABEL_LEN] = "By-value KEK Src"; + uint8_t wrappedKey[WH_TEST_WRAPPED_KEYSIZE]; + uint16_t wrappedKeySz = sizeof(wrappedKey); + uint16_t wrappedKeyId = WH_KEYID_ERASED; + uint8_t tmpKey[WH_TEST_AES_KEYSIZE]; + uint16_t tmpKeySz = sizeof(tmpKey); + uint8_t tmpLabel[WH_NVM_LABEL_LEN]; + uint8_t data[] = "Example data!"; + uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE]; + uint32_t wrappedDataSz = sizeof(wrappedData); + uint8_t dataOut[sizeof(data)]; + uint32_t dataOutSz = sizeof(dataOut); + whNvmMetadata metadata = {0}; + whNvmMetadata tmpMeta = {0}; + int ops = _portKekOps; + int reads = _byValueKekReads; + int devId; + int ret; + int refRet; + size_t i; + + for (i = 0; i < sizeof(srcKey); i++) { + srcKey[i] = (uint8_t)(0x5C ^ i); + } + metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(t->client->comm->client_id, + WH_TEST_WRAPPED_KEYID); + metadata.len = WH_TEST_AES_KEYSIZE; + metadata.flags = WH_NVM_FLAGS_USAGE_ANY; + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, + WH_TEST_BYVALUE_KEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_VALUE)); + + /* Wrap, then unwrap-and-export */ + WH_TEST_RETURN_ON_FAIL(_KeyWrap(t, kekId, srcKey, sizeof(srcKey), &metadata, + wrappedKey, &wrappedKeySz)); + WH_TEST_RETURN_ON_FAIL(_KeyUnwrapAndExport( + t, kekId, wrappedKey, wrappedKeySz, &tmpMeta, tmpKey, &tmpKeySz)); + WH_TEST_ASSERT_RETURN((tmpKeySz == sizeof(srcKey)) && + (memcmp(tmpKey, srcKey, sizeof(srcKey)) == 0)); + + /* The KEK the device holds can't open the blob */ + tmpKeySz = sizeof(tmpKey); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK != + _KeyUnwrapAndExport(t, refKekId, wrappedKey, + wrappedKeySz, &tmpMeta, tmpKey, + &tmpKeySz)); + + /* It is trusted: wrap-export a server-held key, then cache it back */ + WH_TEST_RETURN_ON_FAIL(_KeyCache(t, WH_NVM_FLAGS_USAGE_ANY, label, + sizeof(label), srcKey, sizeof(srcKey), + &srcKeyId)); + wrappedKeySz = sizeof(wrappedKey); + WH_TEST_RETURN_ON_FAIL(_KeyWrapExport(t, srcKeyId, WH_KEYTYPE_CRYPTO, kekId, + wrappedKey, &wrappedKeySz)); + WH_TEST_RETURN_ON_FAIL( + _KeyUnwrapAndCache(t, kekId, wrappedKey, wrappedKeySz, &wrappedKeyId)); + tmpKeySz = sizeof(tmpKey); + WH_TEST_RETURN_ON_FAIL( + _KeyExport(t, WH_CLIENT_KEYID_MAKE_WRAPPED(wrappedKeyId), tmpLabel, + sizeof(tmpLabel), tmpKey, &tmpKeySz)); + WH_TEST_ASSERT_RETURN((tmpKeySz == sizeof(srcKey)) && + (memcmp(tmpKey, srcKey, sizeof(srcKey)) == 0)); + WH_TEST_RETURN_ON_FAIL( + _KeyEvict(t, WH_CLIENT_KEYID_MAKE_WRAPPED(wrappedKeyId))); + WH_TEST_RETURN_ON_FAIL(_KeyEvict(t, srcKeyId)); + + /* Data wrap round trip */ + WH_TEST_RETURN_ON_FAIL( + _DataWrap(t, kekId, data, sizeof(data), wrappedData, &wrappedDataSz)); + WH_TEST_RETURN_ON_FAIL( + _DataUnwrap(t, kekId, wrappedData, wrappedDataSz, dataOut, &dataOutSz)); + WH_TEST_ASSERT_RETURN((dataOutSz == sizeof(data)) && + (memcmp(dataOut, data, sizeof(data)) == 0)); + + /* The port supplied the bytes for each of the 6 operations, and the device + * ran none of them */ + WH_TEST_ASSERT_RETURN(_byValueKekReads == reads + 6); + WH_TEST_ASSERT_RETURN(_portKekOps == ops); + + /* Without a crypto device only the by-value KEK works */ + devId = t->server->devId; + t->server->devId = INVALID_DEVID; + wrappedKeySz = sizeof(wrappedKey); + ret = _KeyWrap(t, kekId, srcKey, sizeof(srcKey), &metadata, wrappedKey, + &wrappedKeySz); + wrappedKeySz = sizeof(wrappedKey); + refRet = _KeyWrap(t, refKekId, srcKey, sizeof(srcKey), &metadata, + wrappedKey, &wrappedKeySz); + t->server->devId = devId; + WH_TEST_RETURN_ON_FAIL(ret); + WH_TEST_ASSERT_RETURN(refRet == WH_ERROR_NOTFOUND); + + return WH_ERROR_OK; +} + +int whTest_PortKey(void* ctx) { int ret; TestCtx* t = &_testCtx; @@ -825,21 +1073,27 @@ int whTest_HwKeystore(void* ctx) WH_TEST_RETURN_ON_FAIL(_SetupClientServer(t)); - ret = _whTest_HwKeystoreKeyWrap(t); + ret = _whTest_PortKeyKeyWrap(t); + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyWrappedKeyNoNvm(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyKeyWrapExport(t); + } if (ret == WH_ERROR_OK) { - ret = _whTest_HwKeystoreWrappedKeyNoNvm(t); + ret = _whTest_PortKeyDataWrap(t); } if (ret == WH_ERROR_OK) { - ret = _whTest_HwKeystoreKeyWrapExport(t); + ret = _whTest_PortKeyRejections(t); } if (ret == WH_ERROR_OK) { - ret = _whTest_HwKeystoreDataWrap(t); + ret = _whTest_PortKeyKekPolicy(t); } if (ret == WH_ERROR_OK) { - ret = _whTest_HwKeystoreRejections(t); + ret = _whTest_PortKeyUnbound(t); } if (ret == WH_ERROR_OK) { - ret = _whTest_HwKeystoreUnbound(t); + ret = _whTest_PortKeyByValueKek(t); } _CleanupClientServer(t); @@ -849,4 +1103,4 @@ int whTest_HwKeystore(void* ctx) #endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER && \ !WOLFHSM_CFG_NO_CRYPTO && WOLFHSM_CFG_KEYWRAP && \ - WOLFHSM_CFG_HWKEYSTORE && !NO_AES && HAVE_AESGCM */ + WOLFHSM_CFG_PORTKEY && !NO_AES && HAVE_AESGCM */ diff --git a/test-refactor/misc/wh_test_portkey_crypto.c b/test-refactor/misc/wh_test_portkey_crypto.c new file mode 100644 index 000000000..31c4d6c75 --- /dev/null +++ b/test-refactor/misc/wh_test_portkey_crypto.c @@ -0,0 +1,2147 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/misc/wh_test_portkey_crypto.c + * + * End-to-end test of port keys in AES and CMAC requests and as key wrap + * KEKs, with clients loading keys (WOLFHSM_CFG_PORTKEY_LOAD). Two clients + * talk to two servers that share one NVM and one port key context, like one + * HSM serving two cores. + * + * The servers' crypto callback is a mock AES engine with eight write-only + * AES-128 key slots with some arbitrary restrictions: + * K0, K1 device keys that no client can name + * K2 a key the port loads at boot. Clients can use it but not set or + * clear it + * K3, K4 loadable by clients for generic use + * K5-K7 used as scratch keys for port-internal functionality + * The mock runs each operation with wolfCrypt in software, using the key in + * the slot, and implements the wolfCrypt keystore callback that loads and + * clears slots. At boot it registers K2 as a fixed key and K3 and K4 as + * loadable keys. + * + * Each client talks to its server over the autopoll mem transport + * (wh_test_transport_mem_autopoll.h), so the blocking client APIs work without + * threads. + * + * _whTest_PortKeyMap - GetInfo for every port key ID + * _whTest_PortKeyFixed - both clients use K2 but can't set or clear it + * _whTest_PortKeyLoadAndUse - load a keystore key into a port key and use + * it in every AES mode and CMAC, standard and + * DMA + * _whTest_PortKeyAllocation - picking a free key, explicit IDs, running + * out, reloading, and another client's keys + * _whTest_PortKeyUsage - the copy keeps the source key's usage flags, + * and every AES and CMAC request checks them + * _whTest_PortKeySource - evict keeps the copy, erase and revoke clear + * it + * _whTest_PortKeyDeleteFails - a copy the port can't clear can't be used, + * and erase, revoke and Clear report the error + * _whTest_PortKeyRefused - sources and targets the server or port + * refuses + * _whTest_PortKeyClear - Clear and ClearAll + * _whTest_PortKeyShared - keys loaded with the global flag + * _whTest_PortKeyNoFallback - a key the port lost fails instead of running + * in software + * _whTest_PortKeyCmacAfterPort - a software key CMAC right after a port key + * one reaches the port with no key reference + * _whTest_PortKeyKek - the fixed key and client-loaded keys as key + * wrap KEKs: only fixed keys are trusted + * _whTest_PortKeyFixedOwner - a fixed key registered for one client + * _whTest_PortKeyByValue - by-value keys run in software in every mode + * and as KEKs, after the usual checks + */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ + defined(WOLFHSM_CFG_ENABLE_SERVER) && defined(WOLFHSM_CFG_PORTKEY_LOAD) && \ + defined(HAVE_AES_ECB) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_COUNTER) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_CMAC) && defined(WOLFSSL_AES_DIRECT) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/error-crypt.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/cmac.h" +#include "wolfssl/wolfcrypt/cryptocb.h" +#include "wolfssl/wolfcrypt/random.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_keyid.h" +#include "wolfhsm/wh_utils.h" +#include "wolfhsm/wh_portkey.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_keystore.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" +#include "wh_test_transport_mem_autopoll.h" + +#define BUFFER_SIZE 4096 +#define FLASH_RAM_SIZE (1024 * 1024) +#define FLASH_SECTOR_SIZE (128 * 1024) +#define FLASH_PAGE_SIZE 8 + +/* wolfCrypt devIds: the mock engine, and one per client */ +#define MOCK_DEVID 0x4D4B4553 +#define CLIENT1_DEVID 0x484B4331 +#define CLIENT2_DEVID 0x484B4332 + +#define CLIENT1_ID WH_TEST_DEFAULT_CLIENT_ID +#define CLIENT2_ID (WH_TEST_DEFAULT_CLIENT_ID + 1) + +/* Mock engine slots */ +#define MOCK_SLOTS 8 +#define MOCK_FIXED_SLOT 2 +#define MOCK_LOAD_FIRST 3 +#define MOCK_LOAD_LAST 4 + +/* By-value keys, which the mock port supplies through GetKey. Their IDs are + * outside the engine's slots, so the engine fails any reference to them */ +#define MOCK_BYVALUE_ID 9 /* AES-128 for AES and CMAC, shared */ +#define MOCK_BYVALUE_OWNED_ID 10 /* AES-128 encrypt only, client 1's */ +#define MOCK_BYVALUE_KEK_ID 11 /* AES-256 key wrap KEK, shared */ + +/* Client keyIds of the port keys */ +#define PORTKEY(id) ((whKeyId)WH_CLIENT_KEYID_MAKE_PORT(id)) + +#define USAGE_AES_CMAC \ + (WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT | \ + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY) + +/* Test data sizes */ +#define TEST_MSG_SZ 80 +#define TEST_BLOCKS_SZ (3 * AES_BLOCK_SIZE) /* ECB and CBC */ +#define TEST_CTR_SZ 37 +#define TEST_GCM_SZ 37 +#define TEST_CMAC_SZ 70 +#define TEST_GCM_IV_SZ 12 + +/* Keystore IDs used by the tests */ +#define SRC_SHARED_ID 0x20 +#define SRC_TRUSTED_ID 0x21 +#define SRC_MISSING_ID 0x22 + +/* ------------------------------------------------------------------------ + * Mock AES engine + * ------------------------------------------------------------------------ */ + +/* What each slot may hold */ +enum { + MOCK_NONE = 0, /* Not a port key */ + MOCK_LOAD, /* Loaded and cleared by clients */ + MOCK_PORT, /* Loaded by the port at boot */ +}; + +static const uint8_t _mockRole[MOCK_SLOTS] = { + MOCK_NONE, /* K0 */ + MOCK_NONE, /* K1 */ + MOCK_PORT, /* K2 */ + MOCK_LOAD, /* K3 */ + MOCK_LOAD, /* K4 */ + MOCK_NONE, /* K5 */ + MOCK_NONE, /* K6 */ + MOCK_NONE, /* K7 */ +}; + +typedef struct { + uint8_t key[MOCK_SLOTS][AES_128_KEY_SIZE]; + uint8_t loaded[MOCK_SLOTS]; + int ops; /* Operations run with a slot key */ + int failDelete; /* Fail WC_KEYSTORE_DELETE and keep the key */ +} MockEngine; + +static MockEngine _mock; + +/* Test data */ +static uint8_t _fixedKey[AES_128_KEY_SIZE]; +static uint8_t _keyA[AES_128_KEY_SIZE]; +static uint8_t _keyB[AES_128_KEY_SIZE]; +static uint8_t _keyC[AES_128_KEY_SIZE]; +static uint8_t _key256[AES_256_KEY_SIZE]; +static uint8_t _byValueKey[AES_128_KEY_SIZE]; +static uint8_t _msg[TEST_MSG_SZ]; +static uint8_t _iv[AES_BLOCK_SIZE]; +static uint8_t _aad[20]; + +/* Slot named by a key reference, or -1 */ +static int _MockRefSlot(const byte* ref, word32 refSz) +{ + int id = wh_PortKey_KeyRefToId(ref, refSz); + + /* Port key n is slot n */ + if ((id == 0) || (id >= MOCK_SLOTS) || (_mockRole[id] == MOCK_NONE)) { + return -1; + } + return id; +} + +/* Slot of a key set up by reference. A key wolfCrypt holds itself runs in + * software. A reference to a slot with no key fails, since software has no + * copy of it */ +static int _MockKeySlot(const unsigned char* id, int idLen, int* slot) +{ + if (idLen <= 0) { + return CRYPTOCB_UNAVAILABLE; + } + *slot = _MockRefSlot(id, (word32)idLen); + if ((*slot < 0) || (_mock.loaded[*slot] == 0)) { + return WC_HW_E; + } + return 0; +} + +static int _MockKeystore(wc_CryptoInfo* info) +{ + int slot; + + switch (info->keystore.type) { + case WC_KEYSTORE_IMPORT_PLAIN: + slot = _MockRefSlot(info->keystore.op.importPlain.keyRef, + info->keystore.op.importPlain.keyRefSz); + /* Only the loadable slots, and only AES-128 keys */ + if ((slot < 0) || (_mockRole[slot] != MOCK_LOAD) || + ((info->keystore.op.importPlain.keyType != + WC_KEYSTORE_KEY_AES) && + (info->keystore.op.importPlain.keyType != + WC_KEYSTORE_KEY_CMAC)) || + (info->keystore.op.importPlain.keySz != AES_128_KEY_SIZE)) { + return BAD_FUNC_ARG; + } + memcpy(_mock.key[slot], info->keystore.op.importPlain.key, + AES_128_KEY_SIZE); + _mock.loaded[slot] = 1; + return 0; + + case WC_KEYSTORE_DELETE: + slot = _MockRefSlot(info->keystore.op.deleteKey.keyRef, + info->keystore.op.deleteKey.keyRefSz); + if ((slot < 0) || (_mockRole[slot] != MOCK_LOAD)) { + return BAD_FUNC_ARG; + } + if (_mock.failDelete != 0) { + return WC_HW_E; + } + wh_Utils_ForceZero(_mock.key[slot], AES_128_KEY_SIZE); + _mock.loaded[slot] = 0; + return 0; + + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +static int _MockCipher(wc_CryptoInfo* info) +{ + Aes sw[1]; + Aes* aes; + int slot; + int ret; + + switch (info->cipher.type) { + case WC_CIPHER_AES_ECB: + aes = info->cipher.aesecb.aes; + break; + case WC_CIPHER_AES_CBC: + aes = info->cipher.aescbc.aes; + break; + case WC_CIPHER_AES_CTR: + aes = info->cipher.aesctr.aes; + break; + case WC_CIPHER_AES_GCM: + aes = (info->cipher.enc != 0) ? info->cipher.aesgcm_enc.aes + : info->cipher.aesgcm_dec.aes; + break; + default: + return CRYPTOCB_UNAVAILABLE; + } + + ret = _MockKeySlot(aes->id, aes->idLen, &slot); + if (ret != 0) { + return ret; + } + + ret = wc_AesInit(sw, NULL, INVALID_DEVID); + if (ret == 0) { + switch (info->cipher.type) { + case WC_CIPHER_AES_ECB: + ret = wc_AesSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE, NULL, + (info->cipher.enc != 0) ? AES_ENCRYPTION + : AES_DECRYPTION); + if ((ret == 0) && (info->cipher.enc != 0)) { + ret = wc_AesEcbEncrypt(sw, info->cipher.aesecb.out, + info->cipher.aesecb.in, + info->cipher.aesecb.sz); + } + else if (ret == 0) { + ret = wc_AesEcbDecrypt(sw, info->cipher.aesecb.out, + info->cipher.aesecb.in, + info->cipher.aesecb.sz); + } + break; + + case WC_CIPHER_AES_CBC: + ret = wc_AesSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE, + (const byte*)aes->reg, + (info->cipher.enc != 0) ? AES_ENCRYPTION + : AES_DECRYPTION); + if ((ret == 0) && (info->cipher.enc != 0)) { + ret = wc_AesCbcEncrypt(sw, info->cipher.aescbc.out, + info->cipher.aescbc.in, + info->cipher.aescbc.sz); + } + else if (ret == 0) { + ret = wc_AesCbcDecrypt(sw, info->cipher.aescbc.out, + info->cipher.aescbc.in, + info->cipher.aescbc.sz); + } + /* Hand back the chaining value */ + memcpy(aes->reg, sw->reg, AES_BLOCK_SIZE); + break; + + case WC_CIPHER_AES_CTR: + ret = wc_AesSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE, + (const byte*)aes->reg, AES_ENCRYPTION); + if (ret == 0) { + /* Continue from the caller's counter and unused + * keystream, and hand back the new state */ + memcpy(sw->tmp, aes->tmp, AES_BLOCK_SIZE); + sw->left = aes->left; + ret = wc_AesCtrEncrypt(sw, info->cipher.aesctr.out, + info->cipher.aesctr.in, + info->cipher.aesctr.sz); + memcpy(aes->reg, sw->reg, AES_BLOCK_SIZE); + memcpy(aes->tmp, sw->tmp, AES_BLOCK_SIZE); + aes->left = sw->left; + } + break; + + case WC_CIPHER_AES_GCM: + ret = wc_AesGcmSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE); + if ((ret == 0) && (info->cipher.enc != 0)) { + wc_CryptoCb_AesAuthEnc* e = &info->cipher.aesgcm_enc; + ret = wc_AesGcmEncrypt(sw, e->out, e->in, e->sz, e->iv, + e->ivSz, e->authTag, e->authTagSz, + e->authIn, e->authInSz); + } + else if (ret == 0) { + wc_CryptoCb_AesAuthDec* d = &info->cipher.aesgcm_dec; + ret = wc_AesGcmDecrypt(sw, d->out, d->in, d->sz, d->iv, + d->ivSz, d->authTag, d->authTagSz, + d->authIn, d->authInSz); + } + break; + + default: + break; + } + wc_AesFree(sw); + } + wh_Utils_ForceZero(sw, sizeof(sw)); + + if (ret == 0) { + _mock.ops++; + } + return ret; +} + +static int _MockCmac(wc_CryptoInfo* info) +{ + Cmac* cmac = info->cmac.cmac; + Cmac sw[1]; + int slot; + int ret; + + /* Key bytes passed in are a software key. The server clears cmac before + * sending them, so fail on a key reference to catch a stale one */ + if (info->cmac.key != NULL) { + return (cmac->idLen != 0) ? BAD_STATE_E : CRYPTOCB_UNAVAILABLE; + } + ret = _MockKeySlot(cmac->id, cmac->idLen, &slot); + if (ret != 0) { + return ret; + } + /* Init has nothing to set up: the running state lives in cmac */ + if ((info->cmac.in == NULL) && (info->cmac.out == NULL)) { + return 0; + } + + /* Run the step on a software copy keyed from the slot */ + ret = wc_InitCmac_ex(sw, _mock.key[slot], AES_128_KEY_SIZE, WC_CMAC_AES, + NULL, NULL, INVALID_DEVID); + if (ret == 0) { + memcpy(sw->buffer, cmac->buffer, sizeof(sw->buffer)); + memcpy(sw->digest, cmac->digest, sizeof(sw->digest)); + sw->bufferSz = cmac->bufferSz; + sw->totalSz = cmac->totalSz; + + if (info->cmac.in != NULL) { + ret = wc_CmacUpdate(sw, info->cmac.in, info->cmac.inSz); + } + if ((ret == 0) && (info->cmac.out != NULL)) { + ret = wc_CmacFinalNoFree(sw, info->cmac.out, info->cmac.outSz); + } + + memcpy(cmac->buffer, sw->buffer, sizeof(cmac->buffer)); + memcpy(cmac->digest, sw->digest, sizeof(cmac->digest)); + cmac->bufferSz = sw->bufferSz; + cmac->totalSz = sw->totalSz; + (void)wc_CmacFree(sw); + } + wh_Utils_ForceZero(sw, sizeof(sw)); + + if (ret == 0) { + _mock.ops++; + } + return ret; +} + +static int _MockCryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)devId; + (void)ctx; + + switch (info->algo_type) { + case WC_ALGO_TYPE_KEYSTORE: + return _MockKeystore(info); + case WC_ALGO_TYPE_CIPHER: + return _MockCipher(info); + case WC_ALGO_TYPE_CMAC: + return _MockCmac(info); + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +/* GetKey calls, and an error for GetKey to return instead, or 0 */ +static int _byValueCalls; +static int _byValueFail; + +/* Supply the bytes of the by-value keys */ +static int _MockGetKey(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len) +{ + const uint8_t* key = _byValueKey; + uint16_t keySz = sizeof(_byValueKey); + + (void)context; + _byValueCalls++; + if (_byValueFail != 0) { + return _byValueFail; + } + if (WH_KEYID_ID(keyId) == MOCK_BYVALUE_KEK_ID) { + key = _key256; + keySz = sizeof(_key256); + } + else if ((WH_KEYID_ID(keyId) != MOCK_BYVALUE_ID) && + (WH_KEYID_ID(keyId) != MOCK_BYVALUE_OWNED_ID)) { + return WH_ERROR_NOTFOUND; + } + if (*inout_len < keySz) { + return WH_ERROR_BUFFER_SIZE; + } + memcpy(out, key, keySz); + *inout_len = keySz; + return WH_ERROR_OK; +} + +/* clang-format off */ +static const whPortKeyCb _mockPortKeyCb = { + .GetKey = _MockGetKey, +}; +/* clang-format on */ + +/* Boot the mock engine: empty every slot, load the fixed key into K2, and + * register it and the loadable keys as port keys. owner is the client that may + * use the fixed key, or 0 for every client, and flags the usage it allows */ +static int _MockBoot(whPortKeyContext* portKey, uint16_t owner, + whNvmFlags flags) +{ + int ret; + int slot; + + memset(&_mock, 0, sizeof(_mock)); + memcpy(_mock.key[MOCK_FIXED_SLOT], _fixedKey, sizeof(_fixedKey)); + _mock.loaded[MOCK_FIXED_SLOT] = 1; + + ret = wh_PortKey_RegisterFixed( + portKey, WH_MAKE_KEYID(WH_KEYTYPE_PORT, owner, MOCK_FIXED_SLOT), flags, + AES_128_KEY_SIZE, WH_PORTKEY_ACCESS_BY_REF); + for (slot = 0; (ret == WH_ERROR_OK) && (slot < MOCK_SLOTS); slot++) { + if (_mockRole[slot] == MOCK_LOAD) { + ret = wh_PortKey_RegisterLoadable( + portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, slot)); + } + } + return ret; +} + +/* ------------------------------------------------------------------------ + * Client/server setup + * ------------------------------------------------------------------------ */ + +/* One client and the server it talks to */ +typedef struct { + whServerContext server[1]; + whClientContext client[1]; + whServerCryptoContext crypto[1]; + uint8_t reqBuf[BUFFER_SIZE]; + uint8_t respBuf[BUFFER_SIZE]; + whTransportMemConfig tmcf[1]; + whTransportServerCb tscb[1]; + whTransportMemServerContext tmsc[1]; + whCommServerConfig cs_conf[1]; + whTransportClientCb tccb[1]; + whTransportMemAutopollClientContext tmcc[1]; + whCommClientConfig cc_conf[1]; + whClientConfig c_conf[1]; + whServerConfig s_conf[1]; +} TestPair; + +typedef struct { + TestPair pair[2]; + /* Shared by both servers */ + whNvmContext nvm[1]; + whPortKeyContext portKey[1]; + whPortKeyConfig pks_conf[1]; + whFlashRamsimCtx fc[1]; + whFlashRamsimCfg fc_conf[1]; + whFlashCb fcb[1]; + whNvmFlashConfig nf_conf[1]; + whNvmFlashContext nfc[1]; + whNvmCb nfcb[1]; + whNvmConfig n_conf[1]; +} TestCtx; + +static TestCtx _testCtx; +static uint8_t _flashMemory[FLASH_RAM_SIZE]; + +static void _Pattern(uint8_t* buf, uint32_t sz, uint8_t seed) +{ + uint32_t i; + + for (i = 0; i < sz; i++) { + buf[i] = (uint8_t)(seed ^ (i * 0x1D)); + } +} + +static int _SetupPair(TestCtx* t, TestPair* p, uint16_t clientId, + int clientDevId, uint16_t serverId) +{ + uint32_t outClientId = 0; + uint32_t outServerId = 0; + + p->tmcf[0] = (whTransportMemConfig){ + .req = (whTransportMemCsr*)p->reqBuf, + .req_size = sizeof(p->reqBuf), + .resp = (whTransportMemCsr*)p->respBuf, + .resp_size = sizeof(p->respBuf), + }; + p->tscb[0] = (whTransportServerCb)WH_TRANSPORT_MEM_SERVER_CB; + p->cs_conf[0] = (whCommServerConfig){ + .transport_cb = p->tscb, + .transport_context = (void*)p->tmsc, + .transport_config = (void*)p->tmcf, + .server_id = serverId, + }; + p->tccb[0] = (whTransportClientCb)WH_TRANSPORT_MEM_AUTOPOLL_CLIENT_CB; + p->tmcc->server = p->server; + p->cc_conf[0] = (whCommClientConfig){ + .transport_cb = p->tccb, + .transport_context = (void*)p->tmcc, + .transport_config = (void*)p->tmcf, + .client_id = clientId, + }; + p->c_conf[0] = (whClientConfig){ + .comm = p->cc_conf, + .devId = clientDevId, + }; + p->s_conf[0] = (whServerConfig){ + .comm_config = p->cs_conf, + .nvm = t->nvm, + .crypto = p->crypto, + .devId = MOCK_DEVID, + .portKey = t->portKey, + }; + + WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(p->crypto->rng, NULL, INVALID_DEVID)); + WH_TEST_RETURN_ON_FAIL(wh_Server_Init(p->server, p->s_conf)); + WH_TEST_RETURN_ON_FAIL( + wh_Server_SetConnected(p->server, WH_COMM_CONNECTED)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(p->client, p->c_conf)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_CommInit(p->client, &outClientId, &outServerId)); + WH_TEST_ASSERT_RETURN(outClientId == clientId); + return WH_ERROR_OK; +} + +static int _Setup(TestCtx* t) +{ + memset(t, 0, sizeof(*t)); + memset(_flashMemory, 0, sizeof(_flashMemory)); + + _Pattern(_fixedKey, sizeof(_fixedKey), 0x2B); + _Pattern(_keyA, sizeof(_keyA), 0xA5); + _Pattern(_keyB, sizeof(_keyB), 0x5A); + _Pattern(_keyC, sizeof(_keyC), 0xC3); + _Pattern(_key256, sizeof(_key256), 0x77); + _Pattern(_byValueKey, sizeof(_byValueKey), 0x3C); + _Pattern(_msg, sizeof(_msg), 0x11); + _Pattern(_iv, sizeof(_iv), 0xE0); + _Pattern(_aad, sizeof(_aad), 0x42); + + t->fc_conf[0] = (whFlashRamsimCfg){ + .size = FLASH_RAM_SIZE, + .sectorSize = FLASH_SECTOR_SIZE, + .pageSize = FLASH_PAGE_SIZE, + .erasedByte = ~(uint8_t)0, + .memory = _flashMemory, + }; + t->fcb[0] = (whFlashCb)WH_FLASH_RAMSIM_CB; + t->nf_conf[0] = (whNvmFlashConfig){ + .cb = t->fcb, + .context = t->fc, + .config = t->fc_conf, + }; + t->nfcb[0] = (whNvmCb)WH_NVM_FLASH_CB; + t->n_conf[0] = (whNvmConfig){ + .cb = t->nfcb, + .context = t->nfc, + .config = t->nf_conf, + }; + t->pks_conf[0] = (whPortKeyConfig){ + .cb = &_mockPortKeyCb, + }; + + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL( + wc_CryptoCb_RegisterDevice(MOCK_DEVID, _MockCryptoCb, NULL)); + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(t->nvm, t->n_conf)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(t->portKey, t->pks_conf)); + WH_TEST_RETURN_ON_FAIL( + _MockBoot(t->portKey, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC)); + WH_TEST_RETURN_ON_FAIL( + _SetupPair(t, &t->pair[0], CLIENT1_ID, CLIENT1_DEVID, 201)); + WH_TEST_RETURN_ON_FAIL( + _SetupPair(t, &t->pair[1], CLIENT2_ID, CLIENT2_DEVID, 202)); + return WH_ERROR_OK; +} + +static void _Cleanup(TestCtx* t) +{ + int i; + + for (i = 0; i < 2; i++) { + (void)wh_Client_Cleanup(t->pair[i].client); + (void)wh_Server_Cleanup(t->pair[i].server); + (void)wc_FreeRng(t->pair[i].crypto->rng); + } + (void)wh_PortKey_Cleanup(t->portKey); + (void)wh_Nvm_Cleanup(t->nvm); + wc_CryptoCb_UnRegisterDevice(MOCK_DEVID); + (void)wolfCrypt_Cleanup(); + wh_Utils_ForceZero(&_mock, sizeof(_mock)); +} + +/* Simulate an HSM reset: the slots and the port key records start empty and + * the port registers its fixed key again, this time for owner with flags */ +static int _Reboot(TestCtx* t, uint16_t owner, whNvmFlags flags) +{ + (void)wh_PortKey_Cleanup(t->portKey); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(t->portKey, t->pks_conf)); + return _MockBoot(t->portKey, owner, flags); +} + +/* ------------------------------------------------------------------------ + * Helpers + * ------------------------------------------------------------------------ */ + +/* Cache an AES key in the client's keystore. *inout_id 0 picks a free ID */ +static int _Cache(whClientContext* c, whNvmFlags flags, const uint8_t* key, + uint16_t keySz, whKeyId* inout_id) +{ + uint8_t label[WH_NVM_LABEL_LEN] = "portkey source"; + uint16_t id = *inout_id; + int ret; + + ret = wh_Client_KeyCache(c, flags, label, sizeof(label), (uint8_t*)key, + keySz, &id); + if (ret == WH_ERROR_OK) { + *inout_id = id; + } + return ret; +} + +static int _Load(whClientContext* c, whKeyId portKeyId, whKeyId srcId, + whKeyId* outPortKeyId) +{ + return wh_Client_PortKeyLoadFromKeystore(c, portKeyId, srcId, outPortKeyId); +} + +/* Check what GetInfo reports for a port key */ +static int _CheckInfo(whClientContext* c, int id, int loaded, whNvmFlags flags, + uint16_t owner) +{ + whPortKeyInfo info; + + memset(&info, 0xFF, sizeof(info)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyGetInfo(c, PORTKEY(id), &info)); + if (loaded) { + WH_TEST_ASSERT_RETURN(info.keyType == WC_KEYSTORE_KEY_AES); + WH_TEST_ASSERT_RETURN(info.keyBits == 128); + WH_TEST_ASSERT_RETURN(info.flags == flags); + WH_TEST_ASSERT_RETURN(info.owner == owner); + } + else { + WH_TEST_ASSERT_RETURN(info.keyType == WC_KEYSTORE_KEY_NONE); + WH_TEST_ASSERT_RETURN(info.keyBits == 0); + WH_TEST_ASSERT_RETURN(info.flags == 0); + WH_TEST_ASSERT_RETURN(info.owner == 0); + } + return WH_ERROR_OK; +} + +/* Clear every key both clients loaded, and check the loadable slots are + * empty again */ +static int _ClearAll(TestCtx* t) +{ + int id; + + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAll(t->pair[0].client)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAll(t->pair[1].client)); + for (id = MOCK_LOAD_FIRST; id <= MOCK_LOAD_LAST; id++) { + WH_TEST_RETURN_ON_FAIL(_CheckInfo(t->pair[0].client, id, 0, 0, 0)); + } + return WH_ERROR_OK; +} + +/* Expected results, from software */ +typedef struct { + uint8_t ecb[TEST_BLOCKS_SZ]; + uint8_t cbc[TEST_BLOCKS_SZ]; + uint8_t ctr[TEST_CTR_SZ]; + uint8_t gcm[TEST_GCM_SZ]; + uint8_t gcmTag[AES_BLOCK_SIZE]; + uint8_t gcm16[TEST_GCM_SZ]; /* With a 16-byte IV */ + uint8_t gcm16Tag[AES_BLOCK_SIZE]; + uint8_t cmac[AES_BLOCK_SIZE]; +} Expected; + +static int _SwExpected(const uint8_t* key, Expected* e) +{ + Aes aes[1]; + word32 macSz = AES_BLOCK_SIZE; + int ret; + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesSetKey(aes, key, AES_128_KEY_SIZE, NULL, AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesEcbEncrypt(aes, e->ecb, _msg, TEST_BLOCKS_SZ); + } + if (ret == 0) { + ret = wc_AesSetKey(aes, key, AES_128_KEY_SIZE, _iv, AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesCbcEncrypt(aes, e->cbc, _msg, TEST_BLOCKS_SZ); + } + if (ret == 0) { + ret = wc_AesSetKey(aes, key, AES_128_KEY_SIZE, _iv, AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesCtrEncrypt(aes, e->ctr, _msg, TEST_CTR_SZ); + } + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, key, AES_128_KEY_SIZE); + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(aes, e->gcm, _msg, TEST_GCM_SZ, _iv, + TEST_GCM_IV_SZ, e->gcmTag, AES_BLOCK_SIZE, _aad, + sizeof(_aad)); + } + if (ret == 0) { + ret = + wc_AesGcmEncrypt(aes, e->gcm16, _msg, TEST_GCM_SZ, _iv, sizeof(_iv), + e->gcm16Tag, AES_BLOCK_SIZE, _aad, sizeof(_aad)); + } + wc_AesFree(aes); + if (ret == 0) { + ret = wc_AesCmacGenerate(e->cmac, &macSz, _msg, TEST_CMAC_SZ, key, + AES_128_KEY_SIZE); + } + return ret; +} + +/* Fresh Aes that names keyId, with iv if not NULL */ +static int _AesStart(Aes* aes, whKeyId keyId, const uint8_t* iv) +{ + int ret; + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_AesSetKeyId(aes, keyId); + } + if ((ret == 0) && (iv != NULL)) { + ret = wc_AesSetIV(aes, iv); + } + return ret; +} + +static int _Ecb(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesEcbDma(c, aes, enc, in, sz, out); + } +#endif + (void)dma; + return wh_Client_AesEcb(c, aes, enc, in, sz, out); +} + +static int _Cbc(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesCbcDma(c, aes, enc, in, sz, out); + } +#endif + (void)dma; + return wh_Client_AesCbc(c, aes, enc, in, sz, out); +} + +static int _Ctr(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesCtrDma(c, aes, enc, in, sz, out); + } +#endif + (void)dma; + return wh_Client_AesCtr(c, aes, enc, in, sz, out); +} + +static int _Gcm(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz, uint32_t ivSz, + const uint8_t* decTag, uint8_t* encTag) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesGcmDma(c, aes, enc, in, sz, _iv, ivSz, _aad, + sizeof(_aad), decTag, encTag, AES_BLOCK_SIZE, + out); + } +#endif + (void)dma; + return wh_Client_AesGcm(c, aes, enc, in, sz, _iv, ivSz, _aad, sizeof(_aad), + decTag, encTag, AES_BLOCK_SIZE, out); +} + +/* Fresh Cmac that names keyId (0 for key bytes in the request) */ +static int _CmacStart(whClientContext* c, Cmac* cmac, whKeyId keyId) +{ + int ret; + + ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, c->devId); + if (ret == 0) { + ret = wh_Client_CmacSetKeyId(cmac, keyId); + } + return ret; +} + +/* One-shot CMAC of the test message, with key in the request if not NULL */ +static int _CmacGenerate(whClientContext* c, Cmac* cmac, const uint8_t* key, + int dma, uint8_t* tag) +{ + uint32_t keySz = (key != NULL) ? AES_128_KEY_SIZE : 0; + uint32_t tagSz = AES_BLOCK_SIZE; + +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_CmacDma(c, cmac, WC_CMAC_AES, key, keySz, _msg, + TEST_CMAC_SZ, tag, &tagSz); + } +#endif + (void)dma; + return wh_Client_Cmac(c, cmac, WC_CMAC_AES, key, keySz, _msg, TEST_CMAC_SZ, + tag, &tagSz); +} + +/* One-shot CMAC of the test message */ +static int _Cmac(whClientContext* c, whKeyId keyId, int dma, uint8_t* tag) +{ + Cmac cmac[1]; + int ret; + + ret = _CmacStart(c, cmac, keyId); + if (ret == 0) { + ret = _CmacGenerate(c, cmac, NULL, dma, tag); + } + (void)wc_CmacFree(cmac); + return ret; +} + +/* One CMAC update request, standard or DMA */ +static int _CmacUpdate(whClientContext* c, Cmac* cmac, int dma, + const uint8_t* in, uint32_t sz) +{ + bool sent = false; + int ret; + +#ifdef WOLFHSM_CFG_DMA + if (dma) { + ret = wh_Client_CmacDmaUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, in, + sz, &sent); + if ((ret == 0) && sent) { + ret = wh_Client_CmacDmaUpdateResponse(c, cmac); + } + return ret; + } +#endif + (void)dma; + ret = wh_Client_CmacUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, in, sz, + &sent); + if ((ret == 0) && sent) { + ret = wh_Client_CmacUpdateResponse(c, cmac); + } + return ret; +} + +/* CMAC final request, standard or DMA */ +static int _CmacFinal(whClientContext* c, Cmac* cmac, int dma, uint8_t* tag, + uint32_t* tagSz) +{ + int ret; + +#ifdef WOLFHSM_CFG_DMA + if (dma) { + ret = wh_Client_CmacDmaFinalRequest(c, cmac); + if (ret == 0) { + ret = wh_Client_CmacDmaFinalResponse(c, cmac, tag, tagSz); + } + return ret; + } +#endif + (void)dma; + ret = wh_Client_CmacFinalRequest(c, cmac); + if (ret == 0) { + ret = wh_Client_CmacFinalResponse(c, cmac, tag, tagSz); + } + return ret; +} + +/* CMAC of the test message over several requests, so the server picks up the + * running state each time */ +static int _CmacSteps(whClientContext* c, whKeyId keyId, int dma, uint8_t* tag) +{ + Cmac cmac[1]; + uint32_t tagSz = AES_BLOCK_SIZE; + int ret; + + ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, c->devId); + if (ret == 0) { + ret = wh_Client_CmacSetKeyId(cmac, keyId); + } + if (ret == 0) { + ret = _CmacUpdate(c, cmac, dma, _msg, 40); + } + if (ret == 0) { + ret = _CmacUpdate(c, cmac, dma, _msg + 40, TEST_CMAC_SZ - 40); + } + if (ret == 0) { + ret = _CmacFinal(c, cmac, dma, tag, &tagSz); + } + (void)wc_CmacFree(cmac); + return ret; +} + +/* AES modes for _TryAes */ +enum { + MODE_ECB = 0, + MODE_CBC, + MODE_CTR, + MODE_GCM, + MODE_COUNT, +}; + +/* Result of one request in mode with keyId. A GCM decrypt uses the software + * ciphertext and tag in e, so it succeeds with the right key */ +static int _TryAes(whClientContext* c, whKeyId keyId, int mode, int dma, + int enc, const Expected* e) +{ + Aes aes[1]; + uint8_t out[TEST_GCM_SZ]; + uint8_t tag[AES_BLOCK_SIZE]; + int ret; + + ret = _AesStart(aes, keyId, + ((mode == MODE_CBC) || (mode == MODE_CTR)) ? _iv : NULL); + if (ret == 0) { + switch (mode) { + case MODE_ECB: + ret = _Ecb(c, aes, dma, enc, _msg, out, AES_BLOCK_SIZE); + break; + case MODE_CBC: + ret = _Cbc(c, aes, dma, enc, _msg, out, AES_BLOCK_SIZE); + break; + case MODE_CTR: + ret = _Ctr(c, aes, dma, enc, _msg, out, AES_BLOCK_SIZE); + break; + case MODE_GCM: + if (enc) { + ret = _Gcm(c, aes, dma, 1, _msg, out, TEST_GCM_SZ, + TEST_GCM_IV_SZ, NULL, tag); + } + else { + ret = _Gcm(c, aes, dma, 0, e->gcm, out, TEST_GCM_SZ, + TEST_GCM_IV_SZ, e->gcmTag, NULL); + } + break; + default: + ret = WH_ERROR_BADARGS; + break; + } + } + wc_AesFree(aes); + return ret; +} + +/* Result of a one-block ECB request with keyId */ +static int _TryEcb(whClientContext* c, whKeyId keyId, int enc) +{ + return _TryAes(c, keyId, MODE_ECB, 0, enc, NULL); +} + +/* Result of a one-shot CMAC request with keyId */ +static int _TryCmac(whClientContext* c, whKeyId keyId) +{ + uint8_t tag[AES_BLOCK_SIZE]; + + return _Cmac(c, keyId, 0, tag); +} + +/* One-block ECB with keyId must match software with key */ +static int _EcbMatches(whClientContext* c, whKeyId keyId, const uint8_t* key) +{ + Expected e; + Aes aes[1]; + uint8_t out[AES_BLOCK_SIZE]; + + WH_TEST_RETURN_ON_FAIL(_SwExpected(key, &e)); + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, NULL)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_AesEcb(c, aes, 1, _msg, AES_BLOCK_SIZE, out)); + wc_AesFree(aes); + WH_TEST_ASSERT_RETURN(memcmp(out, e.ecb, AES_BLOCK_SIZE) == 0); + return WH_ERROR_OK; +} + +/* The mock ran an operation since before (port key), or didn't (key held + * in software) */ +#define CHECK_RAN(before, hw) \ + WH_TEST_ASSERT_RETURN((hw) ? (_mock.ops > (before)) \ + : (_mock.ops == (before))) + +/* Run every AES mode and CMAC with keyId and compare with software results + * for key. hw says whether the mock engine must run them */ +static int _CheckModes(whClientContext* c, whKeyId keyId, const uint8_t* key, + int dma, int hw) +{ + Expected e; + Aes aes[1]; + uint8_t out[TEST_MSG_SZ]; + uint8_t back[TEST_MSG_SZ]; + uint8_t tag[AES_BLOCK_SIZE]; + int ops; + + WH_TEST_RETURN_ON_FAIL(_SwExpected(key, &e)); + + /* ECB */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, NULL)); + WH_TEST_RETURN_ON_FAIL(_Ecb(c, aes, dma, 1, _msg, out, TEST_BLOCKS_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.ecb, TEST_BLOCKS_SZ) == 0); + WH_TEST_RETURN_ON_FAIL(_Ecb(c, aes, dma, 0, out, back, TEST_BLOCKS_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_BLOCKS_SZ) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* CBC, encrypted in two requests to check the chaining value */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Cbc(c, aes, dma, 1, _msg, out, 32)); + WH_TEST_RETURN_ON_FAIL( + _Cbc(c, aes, dma, 1, _msg + 32, out + 32, TEST_BLOCKS_SZ - 32)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.cbc, TEST_BLOCKS_SZ) == 0); + wc_AesFree(aes); + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Cbc(c, aes, dma, 0, out, back, TEST_BLOCKS_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_BLOCKS_SZ) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* CTR, in two requests that split a block */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, 1, _msg, out, 20)); + WH_TEST_RETURN_ON_FAIL( + _Ctr(c, aes, dma, 1, _msg + 20, out + 20, TEST_CTR_SZ - 20)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.ctr, TEST_CTR_SZ) == 0); + wc_AesFree(aes); + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, 1, out, back, TEST_CTR_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_CTR_SZ) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* GCM with a 12-byte IV, a 16-byte IV, and a bad tag */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, NULL)); + WH_TEST_RETURN_ON_FAIL(_Gcm(c, aes, dma, 1, _msg, out, TEST_GCM_SZ, + TEST_GCM_IV_SZ, NULL, tag)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.gcm, TEST_GCM_SZ) == 0); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.gcmTag, AES_BLOCK_SIZE) == 0); + WH_TEST_RETURN_ON_FAIL(_Gcm(c, aes, dma, 0, out, back, TEST_GCM_SZ, + TEST_GCM_IV_SZ, tag, NULL)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_GCM_SZ) == 0); + tag[0] ^= 0x01; + WH_TEST_ASSERT_RETURN(AES_GCM_AUTH_E == _Gcm(c, aes, dma, 0, out, back, + TEST_GCM_SZ, TEST_GCM_IV_SZ, + tag, NULL)); + WH_TEST_RETURN_ON_FAIL( + _Gcm(c, aes, dma, 1, _msg, out, TEST_GCM_SZ, sizeof(_iv), NULL, tag)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.gcm16, TEST_GCM_SZ) == 0); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.gcm16Tag, AES_BLOCK_SIZE) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* CMAC in one request, and over several */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_Cmac(c, keyId, dma, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); + CHECK_RAN(ops, hw); + ops = _mock.ops; + memset(tag, 0, sizeof(tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, keyId, dma, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); + CHECK_RAN(ops, hw); + + return WH_ERROR_OK; +} + +/* ------------------------------------------------------------------------ + * Tests + * ------------------------------------------------------------------------ */ + +/* GetInfo for every port key ID */ +static int _whTest_PortKeyMap(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whPortKeyInfo info; + int id; + + for (id = 1; id <= MOCK_SLOTS; id++) { + if (id == MOCK_FIXED_SLOT) { + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c, id, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + } + else if ((id >= MOCK_LOAD_FIRST) && (id <= MOCK_LOAD_LAST)) { + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, id, 0, 0, 0)); + } + else { + /* The port didn't register this ID */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_Client_PortKeyGetInfo(c, PORTKEY(id), &info)); + } + } + + /* ID 0 names no key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_Client_PortKeyGetInfo(c, PORTKEY(0), &info)); + + /* The split request and response */ + memset(&info, 0, sizeof(info)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_PortKeyGetInfoRequest(c, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyGetInfoResponse(c, &info)); + WH_TEST_ASSERT_RETURN(info.keyType == WC_KEYSTORE_KEY_AES); + WH_TEST_ASSERT_RETURN(info.flags == USAGE_AES_CMAC); + + return WH_ERROR_OK; +} + +/* Both clients can use the fixed key in K2, but neither can set or clear it */ +static int _whTest_PortKeyFixed(TestCtx* t) +{ + whClientContext* c; + whKeyId src; + whKeyId portKeyId; + int i; + int dma; + + for (i = 0; i < 2; i++) { + c = t->pair[i].client; + + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL( + _CheckModes(c, PORTKEY(MOCK_FIXED_SLOT), _fixedKey, dma, 1)); + } + + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _Load(c, + PORTKEY(MOCK_FIXED_SLOT), + src, &portKeyId)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_Client_PortKeyClear(c, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAll(c)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_FIXED_SLOT, 1, USAGE_AES_CMAC, + WH_KEYUSER_GLOBAL)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + } + + return WH_ERROR_OK; +} + +/* Load a keystore key into a port key and use it in every mode */ +static int _whTest_PortKeyLoadAndUse(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId portKeyId; + Expected e; + Aes aes[1]; + uint8_t out[TEST_GCM_SZ]; + uint8_t tag[AES_BLOCK_SIZE]; + int dma; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + + /* The keystore key itself runs in software */ + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL(_CheckModes(c, src, _keyA, dma, 0)); + } + + /* ID 0 takes the first free port key */ + portKeyId = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + wh_Client_PortKeyLoadFromKeystoreRequest(c, PORTKEY(0), src)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_PortKeyLoadFromKeystoreResponse(c, &portKeyId)); + WH_TEST_ASSERT_RETURN(portKeyId == PORTKEY(MOCK_LOAD_FIRST)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(t->pair[1].client, MOCK_LOAD_FIRST, 1, + USAGE_AES_CMAC, CLIENT1_ID)); + + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL(_CheckModes(c, portKeyId, _keyA, dma, 1)); + } + + /* The wolfCrypt API works too: the client's crypto callback sends the + * port keyId */ + WH_TEST_RETURN_ON_FAIL(_SwExpected(_keyA, &e)); + WH_TEST_RETURN_ON_FAIL(wc_AesInit(aes, NULL, c->devId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_AesSetKeyId(aes, portKeyId)); + WH_TEST_RETURN_ON_FAIL(wc_AesGcmEncrypt(aes, out, _msg, TEST_GCM_SZ, _iv, + TEST_GCM_IV_SZ, tag, sizeof(tag), + _aad, sizeof(_aad))); + wc_AesFree(aes); + WH_TEST_ASSERT_RETURN(memcmp(out, e.gcm, TEST_GCM_SZ) == 0); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.gcmTag, AES_BLOCK_SIZE) == 0); + + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c, portKeyId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + return WH_ERROR_OK; +} + +/* Picking a free key, explicit IDs, running out, reloading, and keys that + * belong to the other client */ +static int _whTest_PortKeyAllocation(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src1 = WH_KEYID_ERASED; + whKeyId src2 = WH_KEYID_ERASED; + whKeyId src3 = WH_KEYID_ERASED; + whKeyId port1; + whKeyId port2; + whKeyId tmp; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c2, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src2)); + + /* Each client gets the next free key */ + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src1, &port1)); + WH_TEST_ASSERT_RETURN(port1 == PORTKEY(MOCK_LOAD_FIRST)); + WH_TEST_RETURN_ON_FAIL(_Load(c2, PORTKEY(0), src2, &port2)); + WH_TEST_ASSERT_RETURN(port2 == PORTKEY(MOCK_LOAD_LAST)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, port1, _keyA)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, port2, _keyB)); + + /* No loadable key is left */ + WH_TEST_ASSERT_RETURN(WH_ERROR_NOSPACE == + _Load(c1, PORTKEY(0), src1, &tmp)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOSPACE == + _Load(c2, PORTKEY(0), src2, &tmp)); + + /* A client can't use, replace or clear the other client's key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryEcb(c2, port1, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryCmac(c2, port1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryEcb(c1, port2, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _Load(c2, port1, src2, &tmp)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == wh_Client_PortKeyClear(c2, port1)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, CLIENT1_ID)); + + /* The owner can load its key again. The copy then follows the new + * source, so erasing the old one leaves it alone */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, WH_NVM_FLAGS_USAGE_ENCRYPT, _keyC, sizeof(_keyC), &src3)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, port1, src3, &tmp)); + WH_TEST_ASSERT_RETURN(tmp == port1); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, port1, _keyC)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, MOCK_LOAD_FIRST, 1, + WH_NVM_FLAGS_USAGE_ENCRYPT, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyErase(c1, src1)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, port1, _keyC)); + + /* ID 0 names no key, and the port didn't register ID 9 */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == _TryEcb(c1, PORTKEY(0), 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, PORTKEY(9), 1)); + + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src3)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c2, src2)); + return WH_ERROR_OK; +} + +/* The copy keeps the source key's usage flags. Each AES request needs the + * usage for its direction and CMAC needs sign or verify, standard and DMA */ +static int _whTest_PortKeyUsage(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + Expected e; + uint8_t tag[AES_BLOCK_SIZE]; + whKeyId src1; + whKeyId src2; + whKeyId port1; + whKeyId port2; + whKeyId src; + whKeyId portKeyId; + int mode; + int dma; + + WH_TEST_RETURN_ON_FAIL(_SwExpected(_keyA, &e)); + + /* Encrypt only and decrypt only: each works one way in every AES mode, and + * neither can do CMAC */ + src1 = WH_KEYID_ERASED; + src2 = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_ENCRYPT, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_DECRYPT, _keyA, sizeof(_keyA), &src2)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src1, &port1)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src2, &port2)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(port1), 1, + WH_NVM_FLAGS_USAGE_ENCRYPT, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(port2), 1, + WH_NVM_FLAGS_USAGE_DECRYPT, CLIENT1_ID)); + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + for (mode = 0; mode < MODE_COUNT; mode++) { + WH_TEST_RETURN_ON_FAIL(_TryAes(c, port1, mode, dma, 1, &e)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, port1, mode, dma, 0, &e)); + WH_TEST_RETURN_ON_FAIL(_TryAes(c, port2, mode, dma, 0, &e)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, port2, mode, dma, 1, &e)); + } + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _Cmac(c, port1, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _CmacSteps(c, port1, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _Cmac(c, port2, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _CmacSteps(c, port2, dma, tag)); + } + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src2)); + + /* Sign only and verify only: either can do CMAC but not AES */ + src1 = WH_KEYID_ERASED; + src2 = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_SIGN, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_VERIFY, _keyA, sizeof(_keyA), &src2)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src1, &port1)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src2, &port2)); + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL(_Cmac(c, port1, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, port1, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_Cmac(c, port2, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, port2, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, port1, MODE_ECB, dma, 1, &e)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, port2, MODE_ECB, dma, 0, &e)); + } + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src2)); + + /* A key with no usage could never be used, so it isn't loaded */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_NONE, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _Load(c, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + /* A non-exportable key can be loaded: the key stays in the HSM */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL(_Cache(c, + USAGE_AES_CMAC | WH_NVM_FLAGS_NONEXPORTABLE, + _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c, portKeyId, _keyA)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c, portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + return WH_ERROR_OK; +} + +/* Evicting the source keeps the copy. Erasing or revoking it clears every + * copy, but only copies of that client's key */ +static int _whTest_PortKeySource(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src; + whKeyId other; + whKeyId portKeyId; + + /* Evict */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, portKeyId, _keyA)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c1, portKeyId)); + + /* Erase */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyErase(c1, src)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(portKeyId), 0, 0, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, portKeyId, 1)); + + /* Revoke. A revoked key has no usage, so it can't be loaded again */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyRevoke(c1, src)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(portKeyId), 0, 0, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, portKeyId, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _Load(c1, PORTKEY(0), src, &portKeyId)); + + /* The other client's key with the same ID is a different key */ + src = SRC_SHARED_ID; + other = SRC_SHARED_ID; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c2, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &other)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyErase(c2, other)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, portKeyId, _keyA)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c1, portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + + return WH_ERROR_OK; +} + +/* If the port can't clear a copy, erase, revoke and Clear report the error, + * and the copy can't be used anyway. The server marks the key empty, and the + * key stays in the slot until a load replaces it */ +static int _whTest_PortKeyDeleteFails(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src1 = WH_KEYID_ERASED; + whKeyId src2 = WH_KEYID_ERASED; + whKeyId src3 = WH_KEYID_ERASED; + whKeyId port1; + whKeyId port2; + whKeyId tmp; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src2)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyC, sizeof(_keyC), &src3)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src1, &port1)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src2, &port2)); + + /* Erase. The key is still in the slot, but the server reports it empty */ + _mock.failDelete = 1; + WH_TEST_ASSERT_RETURN(WC_HW_E == wh_Client_KeyErase(c, src1)); + _mock.failDelete = 0; + WH_TEST_ASSERT_RETURN(_mock.loaded[WH_KEYID_ID(port1)] != 0); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c, port1, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryCmac(c, port1)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(port1), 0, 0, 0)); + + /* Revoke */ + _mock.failDelete = 1; + WH_TEST_ASSERT_RETURN(WC_HW_E == wh_Client_KeyRevoke(c, src2)); + _mock.failDelete = 0; + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c, port2, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryCmac(c, port2)); + + /* The ID 0 search takes the first of them again, and the load replaces + * the old key */ + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src3, &tmp)); + WH_TEST_ASSERT_RETURN(tmp == port1); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c, port1, _keyC)); + + /* Clear */ + _mock.failDelete = 1; + WH_TEST_ASSERT_RETURN(WC_HW_E == wh_Client_PortKeyClear(c, port1)); + _mock.failDelete = 0; + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c, port1, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Client_PortKeyClear(c, port1)); + + /* Load over both slots so they can be cleared. The revoked key can't be + * evicted */ + WH_TEST_RETURN_ON_FAIL(_Load(c, port1, src3, &tmp)); + WH_TEST_RETURN_ON_FAIL(_Load(c, port2, src3, &tmp)); + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src3)); + return WH_ERROR_OK; +} + +/* Sources and targets the server or the port refuses */ +static int _whTest_PortKeyRefused(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whServerContext* server = t->pair[0].server; + whKeyId src = WH_KEYID_ERASED; + whKeyId src256 = WH_KEYID_ERASED; + whKeyId portKeyId; + whNvmMetadata meta[1]; + int ret; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + + /* Sources: a port key, a missing key, and no key at all */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == _Load(c, PORTKEY(0), + PORTKEY(MOCK_FIXED_SLOT), + &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + _Load(c, PORTKEY(0), SRC_MISSING_ID, &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + _Load(c, PORTKEY(0), WH_KEYID_ERASED, &portKeyId)); + + /* The port takes only AES-128 keys */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _key256, sizeof(_key256), &src256)); + WH_TEST_ASSERT_RETURN( + BAD_FUNC_ARG == _Load(c, PORTKEY(MOCK_LOAD_FIRST), src256, &portKeyId)); + WH_TEST_ASSERT_RETURN(BAD_FUNC_ARG == + _Load(c, PORTKEY(0), src256, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src256)); + + /* Targets: IDs the port didn't register, and the fixed key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + _Load(c, PORTKEY(1), src, &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + _Load(c, PORTKEY(5), src, &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + _Load(c, PORTKEY(9), src, &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c, PORTKEY(MOCK_FIXED_SLOT), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_LAST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + /* A trusted key is refused like for every client keystore operation. Only + * the server can make one */ + memset(meta, 0, sizeof(meta)); + meta->id = WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, CLIENT1_ID, SRC_TRUSTED_ID); + meta->flags = WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_TRUSTED; + meta->len = AES_128_KEY_SIZE; + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreCacheKey(server, meta, _keyA); + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + WH_TEST_RETURN_ON_FAIL(ret); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c, PORTKEY(0), SRC_TRUSTED_ID, &portKeyId)); + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreEvictKey(server, meta->id); + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + WH_TEST_RETURN_ON_FAIL(ret); + + return WH_ERROR_OK; +} + +/* Clear and ClearAll */ +static int _whTest_PortKeyClear(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src1 = WH_KEYID_ERASED; + whKeyId src2 = WH_KEYID_ERASED; + whKeyId port1; + whKeyId port2; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c2, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src2)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src1, &port1)); + WH_TEST_RETURN_ON_FAIL(_Load(c2, PORTKEY(0), src2, &port2)); + + /* Only the owner can clear a key, nobody can clear the fixed key, and ID 0 + * isn't a key to clear */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == wh_Client_PortKeyClear(c1, port2)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_PortKeyClear(c1, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Client_PortKeyClear(c1, PORTKEY(9))); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_Client_PortKeyClearRequest(c1, PORTKEY(0))); + + /* The split request and response. The key is then gone */ + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearRequest(c1, port1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearResponse(c1)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(port1), 0, 0, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, port1, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Client_PortKeyClear(c1, port1)); + + /* ClearAll, here as a split request and response, clears only the + * caller's keys */ + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src1, &port1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAllRequest(c1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAllResponse(c1)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(port1), 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, WH_KEYID_ID(port2), 1, USAGE_AES_CMAC, CLIENT2_ID)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, MOCK_FIXED_SLOT, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, port2, _keyB)); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAll(c2)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(port2), 0, 0, 0)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c2, src2)); + return WH_ERROR_OK; +} + +#ifdef WOLFHSM_CFG_GLOBAL_KEYS +/* A key loaded with the global flag is shared: every client can use it, and + * clearing it takes the global flag too */ +static int _whTest_PortKeyShared(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId portKeyId; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL( + _Load(c1, WH_CLIENT_KEYID_MAKE_GLOBAL(PORTKEY(0)), src, &portKeyId)); + WH_TEST_ASSERT_RETURN( + portKeyId == WH_CLIENT_KEYID_MAKE_GLOBAL(PORTKEY(MOCK_LOAD_FIRST))); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + + /* Both clients can use it, by either form of the ID */ + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, portKeyId, _keyA)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, portKeyId, _keyA)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, PORTKEY(MOCK_LOAD_FIRST), _keyA)); + WH_TEST_RETURN_ON_FAIL(_TryCmac(c2, portKeyId)); + + /* ClearAll and a clear without the global flag leave it */ + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAll(c1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_PortKeyClear(c2, PORTKEY(MOCK_LOAD_FIRST))); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + + /* Any client can clear it with the global flag */ + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c2, portKeyId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, MOCK_LOAD_FIRST, 0, 0, 0)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + return WH_ERROR_OK; +} +#endif /* WOLFHSM_CFG_GLOBAL_KEYS */ + +/* If the port loses a key the server still records, requests fail rather than + * run in software without the key */ +static int _whTest_PortKeyNoFallback(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId portKeyId; + int slot; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c, PORTKEY(0), src, &portKeyId)); + slot = (int)WH_KEYID_ID(portKeyId); + + _mock.loaded[slot] = 0; + WH_TEST_ASSERT_RETURN(WC_HW_E == _TryEcb(c, portKeyId, 1)); + WH_TEST_ASSERT_RETURN(WC_HW_E == _TryCmac(c, portKeyId)); + _mock.loaded[slot] = 1; + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c, portKeyId, _keyA)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c, portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + return WH_ERROR_OK; +} + +/* A one-shot CMAC with a software key, in the request or in the keystore, right + * after one with a port key. Both requests take the same path with nothing + * in between, so the server's Cmac for the second starts out holding the + * first's key reference. The mock fails if it gets through */ +static int _whTest_PortKeyCmacAfterPort(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src = WH_KEYID_ERASED; + Expected e; + Cmac port[1]; + Cmac sw[1]; + uint8_t tag[AES_BLOCK_SIZE]; + int dma; + + WH_TEST_RETURN_ON_FAIL(_SwExpected(_keyA, &e)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + /* Key in the request */ + WH_TEST_RETURN_ON_FAIL(_CmacStart(c, port, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_RETURN_ON_FAIL(_CmacStart(c, sw, WH_KEYID_ERASED)); + WH_TEST_RETURN_ON_FAIL(_CmacGenerate(c, port, NULL, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_CmacGenerate(c, sw, _keyA, dma, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); + (void)wc_CmacFree(port); + (void)wc_CmacFree(sw); + + /* Key in the keystore */ + WH_TEST_RETURN_ON_FAIL(_CmacStart(c, port, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_RETURN_ON_FAIL(_CmacStart(c, sw, src)); + WH_TEST_RETURN_ON_FAIL(_CmacGenerate(c, port, NULL, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_CmacGenerate(c, sw, NULL, dma, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); + (void)wc_CmacFree(port); + (void)wc_CmacFree(sw); + } + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + return WH_ERROR_OK; +} + +#ifdef WOLFHSM_CFG_KEYWRAP +/* Wrapped size of an AES-128 key */ +#define TEST_WRAPPED_SZ \ + (WH_KEYWRAP_AES_GCM_HEADER_SIZE + sizeof(whNvmMetadata) + AES_128_KEY_SIZE) + +/* Wrap _keyB under kekId, then unwrap it and check the round trip */ +static int _WrapRoundTrip(whClientContext* c, whKeyId kekId) +{ + whNvmMetadata meta; + whNvmMetadata outMeta; + uint8_t wrapped[TEST_WRAPPED_SZ]; + uint16_t wrappedSz = sizeof(wrapped); + uint8_t key[AES_128_KEY_SIZE]; + uint16_t keySz = sizeof(key); + int ret; + + memset(&meta, 0, sizeof(meta)); + meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(c->comm->client_id, 0x30); + meta.len = AES_128_KEY_SIZE; + meta.flags = WH_NVM_FLAGS_USAGE_ENCRYPT; + + ret = wh_Client_KeyWrap(c, WC_CIPHER_AES_GCM, kekId, _keyB, sizeof(_keyB), + &meta, wrapped, &wrappedSz); + if (ret == WH_ERROR_OK) { + ret = wh_Client_KeyUnwrapAndExport(c, WC_CIPHER_AES_GCM, kekId, wrapped, + wrappedSz, &outMeta, key, &keySz); + } + if (ret == WH_ERROR_OK) { + WH_TEST_ASSERT_RETURN(keySz == sizeof(_keyB)); + WH_TEST_ASSERT_RETURN(memcmp(key, _keyB, sizeof(_keyB)) == 0); + } + return ret; +} + +/* Port keys as key wrap KEKs. The fixed key is trusted, since no client can + * set or read it, so it also works for wrap-export and unwrap-and-cache. A + * client-loaded key is an ordinary KEK, since the client may know its bytes. + * Either needs WRAP usage, and a WRAP-only key is not a cipher key */ +static int _whTest_PortKeyKek(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId plainSrc = WH_KEYID_ERASED; + whKeyId cachedId = WH_KEYID_ERASED; + whKeyId portKeyId; + whNvmMetadata meta; + uint8_t wrapped[TEST_WRAPPED_SZ]; + uint16_t wrappedSz = sizeof(wrapped); + uint8_t data[] = "port KEK data"; + uint8_t wrappedData[WH_KEYWRAP_AES_GCM_HEADER_SIZE + sizeof(data)]; + uint32_t wrappedDataSz = sizeof(wrappedData); + uint8_t dataOut[sizeof(data)]; + uint32_t dataOutSz = sizeof(dataOut); + int ops; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &plainSrc)); + + /* The fixed key as a WRAP-only KEK. The engine runs every wrap step with + * the key in its slot */ + WH_TEST_RETURN_ON_FAIL( + _Reboot(t, WH_KEYUSER_GLOBAL, WH_NVM_FLAGS_USAGE_WRAP)); + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_WrapRoundTrip(c2, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_ASSERT_RETURN(_mock.ops == ops + 2); + + /* It is trusted: wrap-export a server-held key, then cache it back */ + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExport( + c1, WC_CIPHER_AES_GCM, plainSrc, WH_KEYTYPE_CRYPTO, + PORTKEY(MOCK_FIXED_SLOT), wrapped, &wrappedSz)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndCache( + c1, WC_CIPHER_AES_GCM, PORTKEY(MOCK_FIXED_SLOT), wrapped, wrappedSz, + &cachedId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, cachedId)); + + /* WRAP-only, so not a cipher key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryEcb(c1, PORTKEY(MOCK_FIXED_SLOT), 1)); + + /* The usual fixed key has no WRAP usage */ + WH_TEST_RETURN_ON_FAIL(_Reboot(t, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _WrapRoundTrip(c1, PORTKEY(MOCK_FIXED_SLOT))); + + /* A client-loaded WRAP key is an ordinary KEK */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, WH_NVM_FLAGS_USAGE_WRAP, _keyC, sizeof(_keyC), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, PORTKEY(0), src, &portKeyId)); + WH_TEST_RETURN_ON_FAIL(_WrapRoundTrip(c1, portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_DataWrap(c1, WC_CIPHER_AES_GCM, portKeyId, + data, sizeof(data), wrappedData, + &wrappedDataSz)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_DataUnwrap(c1, WC_CIPHER_AES_GCM, portKeyId, wrappedData, + wrappedDataSz, dataOut, &dataOutSz)); + WH_TEST_ASSERT_RETURN((dataOutSz == sizeof(data)) && + (memcmp(dataOut, data, sizeof(data)) == 0)); + + /* Not trusted: the client may know its bytes */ + wrappedSz = sizeof(wrapped); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_KeyWrapExport(c1, WC_CIPHER_AES_GCM, + plainSrc, WH_KEYTYPE_CRYPTO, + portKeyId, wrapped, + &wrappedSz)); + memset(&meta, 0, sizeof(meta)); + meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(c1->comm->client_id, 0x30); + meta.len = AES_128_KEY_SIZE; + wrappedSz = sizeof(wrapped); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrap(c1, WC_CIPHER_AES_GCM, portKeyId, + _keyB, sizeof(_keyB), &meta, + wrapped, &wrappedSz)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_KeyUnwrapAndCache(c1, WC_CIPHER_AES_GCM, + portKeyId, wrapped, + wrappedSz, &cachedId)); + + /* WRAP-only, and only for its owner */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c1, portKeyId, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _WrapRoundTrip(c2, portKeyId)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClear(c1, portKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, plainSrc)); + return WH_ERROR_OK; +} +#endif /* WOLFHSM_CFG_KEYWRAP */ + +/* A fixed key registered for one client: only that client can use it, and no + * client can set or clear it */ +static int _whTest_PortKeyFixedOwner(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId portKeyId; + + WH_TEST_RETURN_ON_FAIL(_Reboot(t, CLIENT1_ID, USAGE_AES_CMAC)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_FIXED_SLOT, 1, USAGE_AES_CMAC, CLIENT1_ID)); + + WH_TEST_RETURN_ON_FAIL( + _CheckModes(c1, PORTKEY(MOCK_FIXED_SLOT), _fixedKey, 0, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _TryEcb(c2, PORTKEY(MOCK_FIXED_SLOT), 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _TryCmac(c2, PORTKEY(MOCK_FIXED_SLOT))); + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c1, PORTKEY(MOCK_FIXED_SLOT), src, &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_PortKeyClear(c1, PORTKEY(MOCK_FIXED_SLOT))); + WH_TEST_RETURN_ON_FAIL(wh_Client_PortKeyClearAll(c1)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, MOCK_FIXED_SLOT, 1, USAGE_AES_CMAC, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + + /* Back to a shared fixed key */ + return _Reboot(t, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC); +} + +/* By-value keys: the port supplies their bytes through GetKey, and the server + * runs every operation with them like a keystore key, so the engine never + * sees them. The usual checks still come first */ +static int _whTest_PortKeyByValue(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId portKeyId; + int dma; + int calls; + + /* Shared by both clients, for AES and CMAC */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, MOCK_BYVALUE_ID), + USAGE_AES_CMAC, AES_128_KEY_SIZE, WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_BYVALUE_ID, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + + /* Every mode matches software, and the engine runs none of them */ + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL( + _CheckModes(c1, PORTKEY(MOCK_BYVALUE_ID), _byValueKey, dma, 0)); + WH_TEST_RETURN_ON_FAIL( + _CheckModes(c2, PORTKEY(MOCK_BYVALUE_ID), _byValueKey, dma, 0)); + } + + /* Clients can't load a key into it or clear it */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c1, PORTKEY(MOCK_BYVALUE_ID), src, &portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_PortKeyClear(c1, PORTKEY(MOCK_BYVALUE_ID))); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + WH_TEST_RETURN_ON_FAIL( + _EcbMatches(c1, PORTKEY(MOCK_BYVALUE_ID), _byValueKey)); + + /* The port's error comes back to the client */ + _byValueFail = WH_ERROR_ABORTED; + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == + _TryEcb(c1, PORTKEY(MOCK_BYVALUE_ID), 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == + _TryCmac(c1, PORTKEY(MOCK_BYVALUE_ID))); + _byValueFail = 0; + + /* A refused request never reaches GetKey */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, CLIENT1_ID, MOCK_BYVALUE_OWNED_ID), + WH_NVM_FLAGS_USAGE_ENCRYPT, AES_128_KEY_SIZE, + WH_PORTKEY_ACCESS_BY_VALUE)); + calls = _byValueCalls; + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryEcb(c1, PORTKEY(MOCK_BYVALUE_OWNED_ID), 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryCmac(c1, PORTKEY(MOCK_BYVALUE_OWNED_ID))); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _TryEcb(c2, PORTKEY(MOCK_BYVALUE_OWNED_ID), 1)); + WH_TEST_ASSERT_RETURN(_byValueCalls == calls); + WH_TEST_RETURN_ON_FAIL(_TryEcb(c1, PORTKEY(MOCK_BYVALUE_OWNED_ID), 1)); + WH_TEST_ASSERT_RETURN(_byValueCalls == calls + 1); + +#ifdef WOLFHSM_CFG_CRYPTO_AFFINITY + /* With software affinity a by-value key still runs, but a key used by + * reference can't */ + WH_TEST_RETURN_ON_FAIL( + wh_Client_SetCryptoAffinity(c1, WH_CRYPTO_AFFINITY_SW)); + WH_TEST_RETURN_ON_FAIL( + _EcbMatches(c1, PORTKEY(MOCK_BYVALUE_ID), _byValueKey)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + _TryEcb(c1, PORTKEY(MOCK_FIXED_SLOT), 1)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_SetCryptoAffinity(c1, WH_CRYPTO_AFFINITY_HW)); +#endif /* WOLFHSM_CFG_CRYPTO_AFFINITY */ + +#ifdef WOLFHSM_CFG_KEYWRAP + { + whKeyId plainSrc = WH_KEYID_ERASED; + whKeyId cachedId = WH_KEYID_ERASED; + uint8_t wrapped[TEST_WRAPPED_SZ]; + uint16_t wrappedSz = sizeof(wrapped); + int ops; + + /* A by-value WRAP key is a trusted KEK, run in software too */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + t->portKey, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, + MOCK_BYVALUE_KEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, AES_256_KEY_SIZE, + WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &plainSrc)); + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL( + _WrapRoundTrip(c2, PORTKEY(MOCK_BYVALUE_KEK_ID))); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExport( + c1, WC_CIPHER_AES_GCM, plainSrc, WH_KEYTYPE_CRYPTO, + PORTKEY(MOCK_BYVALUE_KEK_ID), wrapped, &wrappedSz)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndCache( + c1, WC_CIPHER_AES_GCM, PORTKEY(MOCK_BYVALUE_KEK_ID), wrapped, + wrappedSz, &cachedId)); + WH_TEST_ASSERT_RETURN(_mock.ops == ops); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, cachedId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, plainSrc)); + + /* WRAP only, so not a cipher key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryEcb(c1, PORTKEY(MOCK_BYVALUE_KEK_ID), 1)); + } +#endif /* WOLFHSM_CFG_KEYWRAP */ + + /* Drop the by-value keys */ + return _Reboot(t, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC); +} + +int whTest_PortKeyCrypto(void* ctx) +{ + TestCtx* t = &_testCtx; + int ret; + + (void)ctx; + + ret = _Setup(t); + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyMap(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyFixed(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyLoadAndUse(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyAllocation(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyUsage(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeySource(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyDeleteFails(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyRefused(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyClear(t); + } +#ifdef WOLFHSM_CFG_GLOBAL_KEYS + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyShared(t); + } +#endif + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyNoFallback(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyCmacAfterPort(t); + } +#ifdef WOLFHSM_CFG_KEYWRAP + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyKek(t); + } +#endif + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyFixedOwner(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_PortKeyByValue(t); + } + + _Cleanup(t); + + return ret; +} + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER && \ + WOLFHSM_CFG_PORTKEY_LOAD && HAVE_AES_ECB && HAVE_AES_CBC && \ + WOLFSSL_AES_COUNTER && HAVE_AESGCM && WOLFSSL_CMAC && \ + WOLFSSL_AES_DIRECT */ diff --git a/test-refactor/misc/wh_test_transport_mem_autopoll.c b/test-refactor/misc/wh_test_transport_mem_autopoll.c new file mode 100644 index 000000000..1164d4873 --- /dev/null +++ b/test-refactor/misc/wh_test_transport_mem_autopoll.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/misc/wh_test_transport_mem_autopoll.c + * + * Client mem transport that has the server handle each request while the + * client waits for the reply. See wh_test_transport_mem_autopoll.h + */ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) + +#include +#include /* For NULL */ + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_transport_mem.h" + +#include "wh_test_transport_mem_autopoll.h" + +int whTransportMemAutopoll_Init(void* c, const void* cf, + whCommSetConnectedCb connectcb, + void* connectcb_arg) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + return wh_TransportMem_InitClear(ctx->mem, cf, connectcb, connectcb_arg); +} + +int whTransportMemAutopoll_Send(void* c, uint16_t len, const void* data) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + return wh_TransportMem_SendRequest(ctx->mem, len, data); +} + +int whTransportMemAutopoll_Recv(void* c, uint16_t* out_len, void* data) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + int ret; + int serverRet; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_TransportMem_RecvResponse(ctx->mem, out_len, data); + if ((ret == WH_ERROR_NOTREADY) && (ctx->server != NULL)) { + serverRet = wh_Server_HandleRequestMessage(ctx->server); + ret = wh_TransportMem_RecvResponse(ctx->mem, out_len, data); + /* The server replies to a request at once, so no reply means it failed + * or had no request to handle. Stop instead of waiting forever */ + if (ret == WH_ERROR_NOTREADY) { + ret = + ((serverRet != WH_ERROR_OK) && (serverRet != WH_ERROR_NOTREADY)) + ? serverRet + : WH_ERROR_ABORTED; + } + } + return ret; +} + +int whTransportMemAutopoll_Cleanup(void* c) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + return wh_TransportMem_Cleanup(ctx->mem); +} + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ diff --git a/test-refactor/misc/wh_test_transport_mem_autopoll.h b/test-refactor/misc/wh_test_transport_mem_autopoll.h new file mode 100644 index 000000000..33e25a079 --- /dev/null +++ b/test-refactor/misc/wh_test_transport_mem_autopoll.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/misc/wh_test_transport_mem_autopoll.h + * + * Client mem transport for misc tests that run a client and its server in one + * thread. While the client waits for a reply, the transport has the server + * handle the request, so blocking client APIs work without a server thread. + */ +#ifndef WH_TEST_TRANSPORT_MEM_AUTOPOLL_H_ +#define WH_TEST_TRANSPORT_MEM_AUTOPOLL_H_ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) + +#include + +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_transport_mem.h" + +typedef struct { + whTransportMemClientContext mem[1]; + /* Server to run while waiting for a reply. Set it before the client's + * first request. NULL makes this a plain mem transport */ + whServerContext* server; +} whTransportMemAutopollClientContext; + +int whTransportMemAutopoll_Init(void* c, const void* cf, + whCommSetConnectedCb connectcb, + void* connectcb_arg); +int whTransportMemAutopoll_Send(void* c, uint16_t len, const void* data); +int whTransportMemAutopoll_Recv(void* c, uint16_t* out_len, void* data); +int whTransportMemAutopoll_Cleanup(void* c); + +/* clang-format off */ +#define WH_TRANSPORT_MEM_AUTOPOLL_CLIENT_CB \ + { \ + .Init = whTransportMemAutopoll_Init, \ + .Send = whTransportMemAutopoll_Send, \ + .Recv = whTransportMemAutopoll_Recv, \ + .Cleanup = whTransportMemAutopoll_Cleanup, \ + } +/* clang-format on */ + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ + +#endif /* !WH_TEST_TRANSPORT_MEM_AUTOPOLL_H_ */ diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index f0217de3c..c9ac95c8c 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -202,6 +202,11 @@ ifeq ($(CERT_VERIFY_CACHE_FULLCHAIN),1) DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_FULLCHAIN endif +# Let clients load keys into port keys, and enable its unit test +ifeq ($(PORTKEY_LOAD),1) + DEF += -DWOLFHSM_CFG_PORTKEY_LOAD -DWOLF_CRYPTO_CB_KEYSTORE +endif + # Build the wolfCrypt test suite as a wolfHSM client ifeq ($(TESTWOLFCRYPT),1) DEF += -DWOLFHSM_CFG_TEST_WOLFCRYPTTEST diff --git a/test-refactor/server/wh_test_hwkeystore_server.c b/test-refactor/server/wh_test_hwkeystore_server.c deleted file mode 100644 index 5a642fd03..000000000 --- a/test-refactor/server/wh_test_hwkeystore_server.c +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2026 wolfSSL Inc. - * - * This file is part of wolfHSM. - * - * wolfHSM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfHSM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with wolfHSM. If not, see . - */ -/* - * test-refactor/server/wh_test_hwkeystore_server.c - * - * Server-side hardware keystore coverage that needs no client: - * - * _whTest_HwKeystoreModule - wh_HwKeystore_Init/GetKey/Cleanup argument - * validation and GetKey dispatch, using a - * local context (the shared server context - * is not touched) - * _whTest_HwKeystoreLifecycle - optional backend Init/Cleanup callback - * dispatch and the Init-failure return path, - * using a backend that records its calls - * _whTest_KeystoreHwOnlyReject - every public keystore entry point must - * reject a hardware-only keyId with - * WH_ERROR_ACCESS before reaching the cache, - * NVM, or any hardware backend (none is - * bound to the shared server). FreshenKey is - * also the choke point through which all - * crypto handlers resolve keys, so this - * covers crypto key use of hardware-only ids - * - * End-to-end keywrap use of hardware-only KEKs is covered by the misc group - * test misc/wh_test_hwkeystore.c, which binds a hardware keystore to its own - * private client/server pair. - */ - -#include "wolfhsm/wh_settings.h" - -#if defined(WOLFHSM_CFG_ENABLE_SERVER) && defined(WOLFHSM_CFG_HWKEYSTORE) - -#include -#include - -#include "wolfhsm/wh_error.h" -#include "wolfhsm/wh_common.h" -#include "wolfhsm/wh_keyid.h" -#include "wolfhsm/wh_hwkeystore.h" -#include "wolfhsm/wh_nvm.h" -#include "wolfhsm/wh_server.h" -#include "wolfhsm/wh_server_keystore.h" - -#include "wh_test_common.h" -#include "wh_test_list.h" - -/* Id and material of the only key served by the local test backend */ -#define WH_TEST_HWKEK_ID 3 -#define WH_TEST_HWKEK_SIZE 32 - -/* USER field value for constructing server-internal hardware-only keyIds */ -#define WH_TEST_HW_USER 1 - -static const uint8_t _hwKekMaterial[WH_TEST_HWKEK_SIZE] = { - 0x9a, 0x4e, 0x21, 0xc7, 0x5d, 0x10, 0xfb, 0x33, 0x6f, 0x82, 0xd4, - 0x59, 0xee, 0x07, 0xb1, 0x2c, 0x48, 0x95, 0x3a, 0xc6, 0x71, 0x0d, - 0xb8, 0xe5, 0x12, 0x6a, 0xf9, 0x84, 0x2f, 0xd0, 0x5b, 0xa7}; - -static int _HwKeystoreGetKey(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len) -{ - (void)context; - - /* Only hardware-only keyIds should ever reach a hardware keystore */ - if (WH_KEYID_TYPE(keyId) != WH_KEYTYPE_HW) { - return WH_ERROR_ACCESS; - } - - /* Serve only the known test KEK id, refuse everything else */ - if (WH_KEYID_ID(keyId) != WH_TEST_HWKEK_ID) { - return WH_ERROR_NOTFOUND; - } - - if ((out == NULL) || (inout_len == NULL) || - (*inout_len < sizeof(_hwKekMaterial))) { - return WH_ERROR_BUFFER_SIZE; - } - - memcpy(out, _hwKekMaterial, sizeof(_hwKekMaterial)); - *inout_len = sizeof(_hwKekMaterial); - return WH_ERROR_OK; -} - -/* clang-format off */ -static const whHwKeystoreCb _hwKeystoreCb = { - .Init = NULL, - .Cleanup = NULL, - .GetKey = _HwKeystoreGetKey, -}; -/* clang-format on */ - -/* Backend context recording optional Init/Cleanup callback dispatch. initRet - * and cleanupRet let a test force the Init/Cleanup failure paths; initConfig - * captures the config pointer forwarded to Init to prove it is plumbed - * through */ -typedef struct { - int initCalls; - int cleanupCalls; - int initRet; - int cleanupRet; - const void* initConfig; -} HwKeystoreLifecycle; - -static int _HwKeystoreInit(void* context, const void* config) -{ - HwKeystoreLifecycle* lc = (HwKeystoreLifecycle*)context; - if (lc != NULL) { - lc->initCalls++; - lc->initConfig = config; - return lc->initRet; - } - return WH_ERROR_OK; -} - -static int _HwKeystoreCleanup(void* context) -{ - HwKeystoreLifecycle* lc = (HwKeystoreLifecycle*)context; - if (lc != NULL) { - lc->cleanupCalls++; - return lc->cleanupRet; - } - return WH_ERROR_OK; -} - -/* clang-format off */ -static const whHwKeystoreCb _hwKeystoreLifecycleCb = { - .Init = _HwKeystoreInit, - .Cleanup = _HwKeystoreCleanup, - .GetKey = _HwKeystoreGetKey, -}; -/* clang-format on */ - -/* wh_HwKeystore module front-end in isolation: argument validation, callback - * dispatch, and lifecycle */ -static int _whTest_HwKeystoreModule(void) -{ - int ret; - whHwKeystoreContext hwks[1] = {{0}}; - whHwKeystoreConfig conf[1] = {{0}}; - whHwKeystoreConfig badConf[1] = {{0}}; - whKeyId servedId = - WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID); - uint8_t out[WH_TEST_HWKEK_SIZE] = {0}; - uint16_t outLen = sizeof(out); - - conf->cb = &_hwKeystoreCb; - - /* Init argument validation: NULL context/config/getKey are rejected */ - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_HwKeystore_Init(NULL, conf)); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_HwKeystore_Init(hwks, NULL)); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_Init(hwks, badConf)); - - /* GetKey must reject NULL and uninitialized contexts */ - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(NULL, servedId, out, &outLen)); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwks, conf)); - - /* GetKey argument validation on an initialized context */ - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, NULL, &outLen)); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, NULL)); - - /* Served id: material and reported length must match the backend's */ - ret = wh_HwKeystore_GetKey(hwks, servedId, out, &outLen); - WH_TEST_ASSERT_RETURN(WH_ERROR_OK == ret); - WH_TEST_ASSERT_RETURN(outLen == sizeof(_hwKekMaterial)); - WH_TEST_ASSERT_RETURN(0 == - memcmp(out, _hwKekMaterial, sizeof(_hwKekMaterial))); - - /* Backend policy: unserved id, undersized buffer, non-HW type */ - outLen = sizeof(out); - WH_TEST_ASSERT_RETURN( - WH_ERROR_NOTFOUND == - wh_HwKeystore_GetKey( - hwks, - WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID + 1), - out, &outLen)); - outLen = WH_TEST_HWKEK_SIZE - 1; - WH_TEST_ASSERT_RETURN(WH_ERROR_BUFFER_SIZE == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - outLen = sizeof(out); - WH_TEST_ASSERT_RETURN( - WH_ERROR_ACCESS == - wh_HwKeystore_GetKey( - hwks, - WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_HW_USER, WH_TEST_HWKEK_ID), - out, &outLen)); - - /* Cleanup zeroizes the context, after which GetKey must reject it */ - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_HwKeystore_Cleanup(NULL)); - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Cleanup(hwks)); - outLen = sizeof(out); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - - return WH_ERROR_OK; -} - -/* Optional backend Init/Cleanup dispatch and the Init-failure return path, - * using a backend that records its callback invocations */ -static int _whTest_HwKeystoreLifecycle(void) -{ - HwKeystoreLifecycle lc = {0}; - whHwKeystoreContext hwks[1] = {{0}}; - whHwKeystoreConfig conf[1] = {{0}}; - const int backendConfig = 0; /* sentinel forwarded to Init */ - whKeyId servedId = - WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID); - uint8_t out[WH_TEST_HWKEK_SIZE] = {0}; - uint16_t outLen = sizeof(out); - - conf->cb = &_hwKeystoreLifecycleCb; - conf->context = &lc; - conf->config = &backendConfig; - - /* Init-failure path: the backend Init rc is propagated, the config pointer - * was forwarded, and the context is left uninitialized (GetKey rejects it). - * Cleanup must NOT have run for a backend whose Init never succeeded */ - lc.initRet = WH_ERROR_ABORTED; - WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == wh_HwKeystore_Init(hwks, conf)); - WH_TEST_ASSERT_RETURN(lc.initCalls == 1); - WH_TEST_ASSERT_RETURN(lc.initConfig == &backendConfig); - WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 0); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - - /* Init-success path: the backend Init is dispatched exactly once and the - * context becomes usable */ - lc.initRet = WH_ERROR_OK; - lc.initCalls = 0; - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwks, conf)); - WH_TEST_ASSERT_RETURN(lc.initCalls == 1); - - outLen = sizeof(out); - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - WH_TEST_ASSERT_RETURN(outLen == sizeof(_hwKekMaterial)); - - /* Cleanup dispatches the backend Cleanup exactly once */ - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Cleanup(hwks)); - WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 1); - - /* Cleanup-failure path: a backend Cleanup error is surfaced to the caller, - * yet the context is still torn down (a subsequent GetKey rejects it) */ - lc.initRet = WH_ERROR_OK; - lc.initCalls = 0; - lc.cleanupCalls = 0; - lc.cleanupRet = WH_ERROR_ABORTED; - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwks, conf)); - WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == wh_HwKeystore_Cleanup(hwks)); - WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 1); - outLen = sizeof(out); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - - return WH_ERROR_OK; -} - -/* Hardware-only keyIds must be rejected by every public keystore entry point - * with WH_ERROR_ACCESS (or WH_ERROR_BADARGS for id generation). These checks - * are pure keyId-type checks: they fire whether or not a hardware keystore - * is bound to the server, and before the cache or NVM is consulted */ -static int _whTest_KeystoreHwOnlyReject(whServerContext* server) -{ - whKeyId hwId = - WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID); - uint8_t keyBuf[WH_TEST_HWKEK_SIZE] = {0}; - uint8_t* outBuf = NULL; - whNvmMetadata* outMeta = NULL; - whNvmMetadata meta = {0}; - uint32_t outSz = sizeof(keyBuf); - whNvmId uniqueId; - - /* Id generation: hardware-only ids are assigned by the backend, never by - * the keystore */ - uniqueId = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, 0); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_Server_KeystoreGetUniqueId(server, &uniqueId)); - - /* Cache slot allocation (also the sole guard on the DMA cache path) */ - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreGetCacheSlot( - server, hwId, sizeof(keyBuf), &outBuf, &outMeta)); - - /* Caching key material under a hardware-only id */ - meta.id = hwId; - meta.len = sizeof(keyBuf); - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreCacheKey(server, &meta, keyBuf)); - - /* Freshen: the resolution path used by all crypto handlers, so this also - * proves crypto operations cannot use hardware-only keys */ - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == wh_Server_KeystoreFreshenKey( - server, hwId, NULL, NULL)); - - /* Reading hardware-only key material out of the keystore */ - WH_TEST_ASSERT_RETURN( - WH_ERROR_ACCESS == - wh_Server_KeystoreReadKey(server, hwId, NULL, keyBuf, &outSz)); - - /* Lifecycle operations */ - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreEvictKey(server, hwId)); - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreCommitKey(server, hwId)); - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreEraseKey(server, hwId)); - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreEraseKeyChecked(server, hwId)); - WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == - wh_Server_KeystoreRevokeKey(server, hwId)); - - return WH_ERROR_OK; -} - -int whTest_HwKeystoreServer(whServerContext* ctx) -{ - WH_TEST_RETURN_ON_FAIL(_whTest_HwKeystoreModule()); - WH_TEST_RETURN_ON_FAIL(_whTest_HwKeystoreLifecycle()); - WH_TEST_RETURN_ON_FAIL(_whTest_KeystoreHwOnlyReject(ctx)); - - return WH_ERROR_OK; -} - -#endif /* WOLFHSM_CFG_ENABLE_SERVER && WOLFHSM_CFG_HWKEYSTORE */ diff --git a/test-refactor/server/wh_test_portkey_server.c b/test-refactor/server/wh_test_portkey_server.c new file mode 100644 index 000000000..7b51b6896 --- /dev/null +++ b/test-refactor/server/wh_test_portkey_server.c @@ -0,0 +1,796 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/server/wh_test_portkey_server.c + * + * Server-side port key coverage that needs no client: + * + * _whTest_PortKeyModule - wh_PortKey_Init/RegisterFixed/CheckUse/ + * KeyIdToRef/KeyRefToId/Cleanup: argument + * validation, the records, and the owner, + * usage and trust checks, using a local + * context (the shared server context is + * not touched) + * _whTest_PortKeyByValue - by-value fixed keys and wh_PortKey_Resolve/ + * Read: an unknown access mode is refused, + * and the GetKey hook runs only after the + * usual checks and leaves no key bytes + * behind when it fails + * _whTest_PortKeyLoadable - wh_PortKey_RegisterLoadable, and what + * the front end does with an empty key + * before it would call the port + * _whTest_PortKeyLifecycle - optional backend Init/Cleanup hook + * dispatch and the Init-failure return + * path, using a backend that records its + * calls + * _whTest_KeystorePortKeyReject - every public keystore entry point must + * reject a port keyId with WH_ERROR_ACCESS + * before reaching the cache or NVM + * + * End-to-end use of port keys is covered by the misc group tests + * misc/wh_test_portkey.c (key wrap KEKs) and + * misc/wh_test_portkey_crypto.c (AES, CMAC and client loading), which bind + * port keys to their own client/server pairs. + */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_SERVER) && defined(WOLFHSM_CFG_PORTKEY) + +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_keyid.h" +#include "wolfhsm/wh_portkey.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_keystore.h" + +#ifdef WOLFHSM_CFG_PORTKEY_LOAD +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/cryptocb.h" +#endif + +#include "wh_test_common.h" +#include "wh_test_list.h" + +/* ID and size of the port key the module tests register first */ +#define WH_TEST_PORTKEK_ID 3 +#define WH_TEST_PORTKEK_SIZE 32 + +/* USER field value for constructing server-internal port keyIds */ +#define WH_TEST_PORT_USER 1 + +/* A port's own error code, for the GetKey hook to fail with */ +#define WH_TEST_PORT_ERROR (-30001) + +/* Backend context recording optional Init/Cleanup hook dispatch. initRet and + * cleanupRet let a test force the Init/Cleanup failure paths; initConfig + * captures the config pointer forwarded to Init to prove it is plumbed + * through */ +typedef struct { + int initCalls; + int cleanupCalls; + int initRet; + int cleanupRet; + const void* initConfig; +} PortKeyLifecycle; + +static int _PortKeyInit(void* context, const void* config) +{ + PortKeyLifecycle* lc = (PortKeyLifecycle*)context; + if (lc != NULL) { + lc->initCalls++; + lc->initConfig = config; + return lc->initRet; + } + return WH_ERROR_OK; +} + +static int _PortKeyCleanup(void* context) +{ + PortKeyLifecycle* lc = (PortKeyLifecycle*)context; + if (lc != NULL) { + lc->cleanupCalls++; + return lc->cleanupRet; + } + return WH_ERROR_OK; +} + +/* clang-format off */ +static const whPortKeyCb _portKeyLifecycleCb = { + .Init = _PortKeyInit, + .Cleanup = _PortKeyCleanup, +}; +/* clang-format on */ + +/* GetKey state for _whTest_PortKeyByValue. ret makes the hook fail, and extra + * makes it report the wrong key size */ +typedef struct { + int calls; + whKeyId lastKeyId; + int ret; + int extra; +} PortKeyByValue; + +static const uint8_t _byValueKey[16] = { + 0x3c, 0x81, 0x5e, 0x09, 0xd2, 0x47, 0xa8, 0x6b, + 0x10, 0xf5, 0x92, 0x2d, 0xc4, 0x7e, 0x33, 0xb9, +}; + +static int _PortKeyGetKey(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len) +{ + PortKeyByValue* rd = (PortKeyByValue*)context; + + rd->calls++; + rd->lastKeyId = keyId; + if (rd->ret != WH_ERROR_OK) { + /* Write part of a key, which must not be left behind */ + out[0] = 0xA5; + return rd->ret; + } + if (*inout_len < sizeof(_byValueKey)) { + return WH_ERROR_BUFFER_SIZE; + } + memcpy(out, _byValueKey, sizeof(_byValueKey)); + *inout_len = (uint16_t)(sizeof(_byValueKey) + rd->extra); + return WH_ERROR_OK; +} + +/* clang-format off */ +static const whPortKeyCb _portKeyByValueCb = { + .GetKey = _PortKeyGetKey, +}; +/* clang-format on */ + +/* wh_PortKey front end in isolation: argument validation, the records, and + * the checks the server makes on every use of a port key */ +static int _whTest_PortKeyModule(void) +{ + whPortKeyContext pks[1] = {{0}}; + whPortKeyConfig conf[1] = {{0}}; + /* A shared fixed key, and a fixed key for one client */ + whKeyId shared = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, WH_TEST_PORTKEK_ID); + whKeyId owned = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID + 1); + /* The same keys as the client and another client name them */ + whKeyId sharedByUser = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, WH_TEST_PORTKEK_ID); + whKeyId ownedByOther = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER + 1, + WH_TEST_PORTKEK_ID + 1); + whKeyId out = WH_KEYID_ERASED; + uint8_t ref[WH_PORTKEY_REF_SIZE]; + int i; + + /* Init needs a context and a config; the hook table is optional */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_PortKey_Init(NULL, conf)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_PortKey_Init(pks, NULL)); + + /* An uninitialized context is refused */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed( + pks, shared, WH_NVM_FLAGS_USAGE_WRAP, + WH_TEST_PORTKEK_SIZE, WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_CheckUse(pks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP, &out)); + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(pks, conf)); + + /* The key reference is the server keyId, big-endian */ + wh_PortKey_KeyIdToRef(shared, ref); + WH_TEST_ASSERT_RETURN((ref[0] == (uint8_t)(shared >> 8)) && + (ref[1] == (uint8_t)shared)); + + /* The port reads the port key number from it, whoever owns the key */ + WH_TEST_ASSERT_RETURN(wh_PortKey_KeyRefToId(ref, sizeof(ref)) == + WH_TEST_PORTKEK_ID); + wh_PortKey_KeyIdToRef(owned, ref); + WH_TEST_ASSERT_RETURN(wh_PortKey_KeyRefToId(ref, sizeof(ref)) == + WH_TEST_PORTKEK_ID + 1); + + /* Not a port key reference: the wrong size, no reference, or another + * key type */ + WH_TEST_ASSERT_RETURN(wh_PortKey_KeyRefToId(ref, sizeof(ref) - 1) == 0); + WH_TEST_ASSERT_RETURN(wh_PortKey_KeyRefToId(NULL, sizeof(ref)) == 0); + wh_PortKey_KeyIdToRef( + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_KEYUSER_GLOBAL, WH_TEST_PORTKEK_ID), + ref); + WH_TEST_ASSERT_RETURN(wh_PortKey_KeyRefToId(ref, sizeof(ref)) == 0); + + /* Only port keyIds with a nonzero ID can be registered */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed(pks, + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, + WH_KEYUSER_GLOBAL, + WH_TEST_PORTKEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 0), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_REF)); + + /* A fixed key needs a size */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed(pks, shared, + WH_NVM_FLAGS_USAGE_WRAP, 0, + WH_PORTKEY_ACCESS_BY_REF)); + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + pks, shared, WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + pks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, 16, + WH_PORTKEY_ACCESS_BY_REF)); + + /* One record per port key number, whoever owns it */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_RegisterFixed( + pks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP, + WH_TEST_PORTKEK_SIZE, WH_PORTKEY_ACCESS_BY_REF)); + + /* A shared key: any client can use it, and the recorded keyId comes back + * for the key reference */ + WH_TEST_RETURN_ON_FAIL( + wh_PortKey_CheckUse(pks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP, &out)); + WH_TEST_ASSERT_RETURN(out == shared); + + /* Fixed keys are trusted */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_CheckUse( + pks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP | WH_NVM_FLAGS_TRUSTED, + &out)); + + /* The usage must match, and some usage is needed */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + wh_PortKey_CheckUse(pks, sharedByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, + &out)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + wh_PortKey_CheckUse(pks, sharedByUser, 0, &out)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_USAGE == + wh_PortKey_CheckUse(pks, sharedByUser, WH_NVM_FLAGS_TRUSTED, &out)); + + /* A key for one client: only that client can use it */ + WH_TEST_RETURN_ON_FAIL( + wh_PortKey_CheckUse(pks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT, &out)); + WH_TEST_ASSERT_RETURN(out == owned); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_CheckUse(pks, ownedByOther, + WH_NVM_FLAGS_USAGE_ENCRYPT, + &out)); + + /* No record, a non-port keyId, ID 0, and no output */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_PortKey_CheckUse( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, 9), + WH_NVM_FLAGS_USAGE_ENCRYPT, &out)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_CheckUse(pks, + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, &out)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_CheckUse( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, 0), + WH_NVM_FLAGS_USAGE_WRAP, &out)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_CheckUse(pks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP, NULL)); + + /* The records run out */ + for (i = 2; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 10 + i), + WH_NVM_FLAGS_USAGE_ENCRYPT, 16, WH_PORTKEY_ACCESS_BY_REF)); + } + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOSPACE == + wh_PortKey_RegisterFixed( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 9), + WH_NVM_FLAGS_USAGE_ENCRYPT, 16, WH_PORTKEY_ACCESS_BY_REF)); + + /* Cleanup zeroizes the context, after which it is refused */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_PortKey_Cleanup(NULL)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Cleanup(pks)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_CheckUse(pks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP, &out)); + + return WH_ERROR_OK; +} + +/* By-value keys: the front end gets their bytes from the GetKey hook, but only + * after the same checks as wh_PortKey_CheckUse() */ +static int _whTest_PortKeyByValue(void) +{ + PortKeyByValue rd = {0}; + whPortKeyContext pks[1] = {{0}}; + whPortKeyConfig conf[1] = {{0}}; + /* A shared by-value key, one for one client, and a key used by reference */ + whKeyId byValue = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, WH_TEST_PORTKEK_ID); + whKeyId owned = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID + 1); + whKeyId byRef = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, + WH_TEST_PORTKEK_ID + 2); + /* The same keys as the client and another client name them */ + whKeyId byValueByUser = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, WH_TEST_PORTKEK_ID); + whKeyId ownedByOther = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER + 1, + WH_TEST_PORTKEK_ID + 1); + whKeyId byRefByUser = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID + 2); + whKeyId out = WH_KEYID_ERASED; + uint8_t key[32]; + uint16_t keySz; + int i; + + /* Without a GetKey hook no key can be accessed by value */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(pks, conf)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed(pks, byValue, WH_NVM_FLAGS_USAGE_ENCRYPT, 16, + WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Cleanup(pks)); + + conf->cb = &_portKeyByValueCb; + conf->context = &rd; + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(pks, conf)); + + /* An unknown access mode is refused, also one that is 0 in a byte */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed(pks, byValue, WH_NVM_FLAGS_USAGE_ENCRYPT, 16, + WH_PORTKEY_ACCESS_BY_VALUE + 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed(pks, byValue, + WH_NVM_FLAGS_USAGE_ENCRYPT, + 16, 0x100)); + + /* A by-value key needs a size, and shares the records with other keys */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed(pks, byValue, WH_NVM_FLAGS_USAGE_ENCRYPT, 0, + WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed( + pks, + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_KEYUSER_GLOBAL, + WH_TEST_PORTKEK_ID), + WH_NVM_FLAGS_USAGE_ENCRYPT, 16, WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + pks, byValue, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DERIVE, + 16, WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_RETURN_ON_FAIL( + wh_PortKey_RegisterFixed(pks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT, 16, + WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + pks, byRef, WH_NVM_FLAGS_USAGE_ENCRYPT, 16, WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_PortKey_RegisterFixed(pks, byValueByUser, WH_NVM_FLAGS_USAGE_ENCRYPT, + 16, WH_PORTKEY_ACCESS_BY_VALUE)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_PortKey_RegisterFixed(pks, byValueByUser, WH_NVM_FLAGS_USAGE_ENCRYPT, + 16, WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_ASSERT_RETURN(rd.calls == 0); + + /* The bytes come from the hook, which gets the registered keyId */ + keySz = sizeof(key); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Read( + pks, byValueByUser, WH_NVM_FLAGS_USAGE_DERIVE, key, &keySz)); + WH_TEST_ASSERT_RETURN((keySz == sizeof(_byValueKey)) && + (memcmp(key, _byValueKey, keySz) == 0)); + WH_TEST_ASSERT_RETURN((rd.calls == 1) && (rd.lastKeyId == byValue)); + + /* Resolve returns a by-value key's bytes, with no key reference */ + memset(key, 0, sizeof(key)); + keySz = sizeof(key); + out = byRef; + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Resolve( + pks, byValueByUser, WH_NVM_FLAGS_USAGE_ENCRYPT, &out, key, &keySz)); + WH_TEST_ASSERT_RETURN((out == WH_KEYID_ERASED) && + (keySz == sizeof(_byValueKey)) && + (memcmp(key, _byValueKey, keySz) == 0)); + /* and the recorded keyId, with no bytes, for a key used by reference */ + keySz = sizeof(key); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Resolve( + pks, byRefByUser, WH_NVM_FLAGS_USAGE_ENCRYPT, &out, key, &keySz)); + WH_TEST_ASSERT_RETURN((out == byRef) && (keySz == 0)); + WH_TEST_ASSERT_RETURN(rd.calls == 2); + + /* Read refuses a key used by reference */ + keySz = sizeof(key); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_Read(pks, byRefByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + + /* CheckUse accepts a by-value key without reading it. It is trusted */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_CheckUse( + pks, byValueByUser, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_TRUSTED, + &out)); + WH_TEST_ASSERT_RETURN(out == byValue); + WH_TEST_ASSERT_RETURN(rd.calls == 2); + + /* Refused requests never reach the hook */ + keySz = sizeof(key); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_DECRYPT, key, + &keySz)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_Read(pks, ownedByOther, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_PortKey_Read(pks, + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, 9), + WH_NVM_FLAGS_USAGE_ENCRYPT, key, &keySz)); + keySz = sizeof(_byValueKey) - 1; + WH_TEST_ASSERT_RETURN(WH_ERROR_BUFFER_SIZE == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + WH_TEST_ASSERT_RETURN(rd.calls == 2); + + /* Only its client can read the key it owns */ + keySz = sizeof(key); + WH_TEST_RETURN_ON_FAIL( + wh_PortKey_Read(pks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT, key, &keySz)); + WH_TEST_ASSERT_RETURN((rd.calls == 3) && (rd.lastKeyId == owned)); + + /* A failed hook leaves no key bytes behind, and its error comes back */ + rd.ret = WH_TEST_PORT_ERROR; + memset(key, 0x5A, sizeof(key)); + keySz = sizeof(key); + WH_TEST_ASSERT_RETURN(WH_TEST_PORT_ERROR == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + for (i = 0; i < (int)sizeof(key); i++) { + WH_TEST_ASSERT_RETURN(key[i] == 0); + } + + /* except NOTREADY, which a blocking client would wait on forever */ + rd.ret = WH_ERROR_NOTREADY; + keySz = sizeof(key); + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + rd.ret = WH_ERROR_OK; + + /* Nor does a key of the wrong size */ + rd.extra = 1; + memset(key, 0x5A, sizeof(key)); + keySz = sizeof(key); + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + for (i = 0; i < (int)sizeof(key); i++) { + WH_TEST_ASSERT_RETURN(key[i] == 0); + } + rd.extra = 0; + + /* Bad arguments */ + keySz = sizeof(key); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_Read(NULL, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + &keySz)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, + &keySz)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_Read(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + NULL)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_Resolve(pks, byValueByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, NULL, + key, &keySz)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_Read(pks, + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID), + WH_NVM_FLAGS_USAGE_ENCRYPT, key, &keySz)); + WH_TEST_ASSERT_RETURN(rd.calls == 6); + +#ifdef WOLFHSM_CFG_PORTKEY_LOAD + /* Like any fixed key, clients can't clear or replace it, and it describes + * from its record */ + { + whPortKeyInfo info; + whKeyId keyId = byValueByUser; + + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_Clear(pks, INVALID_DEVID, byValue)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_PortKey_Load(pks, INVALID_DEVID, &keyId, WH_KEYID_ERASED, + WH_NVM_FLAGS_USAGE_ENCRYPT, _byValueKey, + sizeof(_byValueKey))); + WH_TEST_RETURN_ON_FAIL( + wh_PortKey_ClearAll(pks, INVALID_DEVID, WH_TEST_PORT_USER)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_GetInfo(pks, byValue, &info)); + WH_TEST_ASSERT_RETURN((info.keyType == WC_KEYSTORE_KEY_AES) && + (info.keyBits == 128) && + (info.flags == (WH_NVM_FLAGS_USAGE_ENCRYPT | + WH_NVM_FLAGS_USAGE_DERIVE)) && + (info.owner == WH_KEYUSER_GLOBAL)); + keySz = sizeof(key); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Read( + pks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT, key, &keySz)); + } +#endif /* WOLFHSM_CFG_PORTKEY_LOAD */ + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Cleanup(pks)); + return WH_ERROR_OK; +} + +#ifdef WOLFHSM_CFG_PORTKEY_LOAD +/* Registering loadable keys, and the requests on an empty one that the front + * end answers without calling the port. No device is registered for devId, + * so a call into the port would fail */ +static int _whTest_PortKeyLoadable(void) +{ + whPortKeyContext pks[1] = {{0}}; + whPortKeyConfig conf[1] = {{0}}; + whKeyId fixedKey = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, WH_TEST_PORTKEK_ID); + whKeyId loadable = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID + 1); + whKeyId unregistered = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, 9); + whKeyId keyId; + whKeyId out = WH_KEYID_ERASED; + whPortKeyInfo info; + uint8_t key[16] = {0}; + int devId = INVALID_DEVID; + int i; + + /* An uninitialized context is refused */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_RegisterLoadable(pks, loadable)); + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(pks, conf)); + + /* Only port keyIds with a nonzero ID can be registered */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterLoadable( + pks, WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_PORT_USER, + WH_TEST_PORTKEK_ID + 1))); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_RegisterLoadable( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, 0))); + + /* One record per port key number, whichever kind it is */ + WH_TEST_RETURN_ON_FAIL( + wh_PortKey_RegisterFixed(pks, fixedKey, WH_NVM_FLAGS_USAGE_ENCRYPT, 16, + WH_PORTKEY_ACCESS_BY_REF)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterLoadable(pks, loadable)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_RegisterLoadable(pks, fixedKey)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_RegisterLoadable(pks, loadable)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_PortKey_RegisterFixed(pks, loadable, WH_NVM_FLAGS_USAGE_ENCRYPT, 16, + WH_PORTKEY_ACCESS_BY_REF)); + + /* An empty key can't be used or cleared, and describes as all zeros */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_PortKey_CheckUse(pks, loadable, WH_NVM_FLAGS_USAGE_ENCRYPT, &out)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_PortKey_Clear(pks, devId, loadable)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_ClearAll(pks, devId, WH_TEST_PORT_USER)); + memset(&info, 0xFF, sizeof(info)); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_GetInfo(pks, loadable, &info)); + WH_TEST_ASSERT_RETURN((info.keyType == 0) && (info.keyBits == 0) && + (info.flags == 0) && (info.owner == 0)); + + /* A fixed key describes from its record */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_GetInfo(pks, fixedKey, &info)); + WH_TEST_ASSERT_RETURN((info.keyType == WC_KEYSTORE_KEY_AES) && + (info.keyBits == 128) && + (info.flags == WH_NVM_FLAGS_USAGE_ENCRYPT) && + (info.owner == WH_KEYUSER_GLOBAL)); + + /* A key the port didn't register can't be described or loaded, and a + * fixed key can't be loaded */ + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_PortKey_GetInfo(pks, unregistered, &info)); + keyId = unregistered; + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_PortKey_Load(pks, devId, &keyId, WH_KEYID_ERASED, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + sizeof(key))); + keyId = fixedKey; + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_PortKey_Load(pks, devId, &keyId, WH_KEYID_ERASED, + WH_NVM_FLAGS_USAGE_ENCRYPT, key, + sizeof(key))); + + /* Both kinds share the records */ + for (i = 2; i < WOLFHSM_CFG_PORTKEY_MAX_KEYS; i++) { + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterLoadable( + pks, WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, 10 + i))); + } + WH_TEST_ASSERT_RETURN(WH_ERROR_NOSPACE == + wh_PortKey_RegisterLoadable(pks, unregistered)); + + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Cleanup(pks)); + return WH_ERROR_OK; +} +#endif /* WOLFHSM_CFG_PORTKEY_LOAD */ + +/* Optional backend Init/Cleanup hook dispatch and the Init-failure return + * path, using a backend that records its hook calls */ +static int _whTest_PortKeyLifecycle(void) +{ + PortKeyLifecycle lc = {0}; + whPortKeyContext pks[1] = {{0}}; + whPortKeyConfig conf[1] = {{0}}; + const int backendConfig = 0; /* sentinel forwarded to Init */ + whKeyId keyId = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_KEYUSER_GLOBAL, WH_TEST_PORTKEK_ID); + whKeyId out = WH_KEYID_ERASED; + + conf->cb = &_portKeyLifecycleCb; + conf->context = &lc; + conf->config = &backendConfig; + + /* Init-failure path: the backend Init rc is propagated, the config pointer + * was forwarded, and the context is left uninitialized (Register refuses + * it). Cleanup must NOT have run for a backend whose Init never + * succeeded */ + lc.initRet = WH_ERROR_ABORTED; + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == wh_PortKey_Init(pks, conf)); + WH_TEST_ASSERT_RETURN(lc.initCalls == 1); + WH_TEST_ASSERT_RETURN(lc.initConfig == &backendConfig); + WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 0); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_PortKey_RegisterFixed( + pks, keyId, WH_NVM_FLAGS_USAGE_WRAP, + WH_TEST_PORTKEK_SIZE, WH_PORTKEY_ACCESS_BY_REF)); + + /* Init-success path: the backend Init is dispatched exactly once and the + * context becomes usable */ + lc.initRet = WH_ERROR_OK; + lc.initCalls = 0; + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(pks, conf)); + WH_TEST_ASSERT_RETURN(lc.initCalls == 1); + WH_TEST_RETURN_ON_FAIL(wh_PortKey_RegisterFixed( + pks, keyId, WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_PORTKEK_SIZE, + WH_PORTKEY_ACCESS_BY_REF)); + + /* Cleanup dispatches the backend Cleanup exactly once */ + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Cleanup(pks)); + WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 1); + + /* Cleanup-failure path: a backend Cleanup error is surfaced to the caller, + * yet the context is still torn down (it is refused afterwards) */ + lc.initRet = WH_ERROR_OK; + lc.initCalls = 0; + lc.cleanupCalls = 0; + lc.cleanupRet = WH_ERROR_ABORTED; + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(pks, conf)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == wh_PortKey_Cleanup(pks)); + WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 1); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_PortKey_CheckUse(pks, keyId, WH_NVM_FLAGS_USAGE_WRAP, &out)); + + return WH_ERROR_OK; +} + +/* Port keyIds must be rejected by every public keystore entry point + * with WH_ERROR_ACCESS (or WH_ERROR_BADARGS for id generation). These checks + * are pure keyId-type checks: they fire whether or not port keys + * are bound to the server, and before the cache or NVM is consulted */ +static int _whTest_KeystorePortKeyReject(whServerContext* server) +{ + whKeyId portKeyId = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, WH_TEST_PORTKEK_ID); + uint8_t keyBuf[WH_TEST_PORTKEK_SIZE] = {0}; + uint8_t* outBuf = NULL; + whNvmMetadata* outMeta = NULL; + whNvmMetadata meta = {0}; + uint32_t outSz = sizeof(keyBuf); + whNvmId uniqueId; + + /* Id generation: the port defines port key ids, so the keystore never + * assigns one */ + uniqueId = WH_MAKE_KEYID(WH_KEYTYPE_PORT, WH_TEST_PORT_USER, 0); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_Server_KeystoreGetUniqueId(server, &uniqueId)); + + /* Cache slot allocation (also the sole guard on the DMA cache path) */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreGetCacheSlot(server, portKeyId, + sizeof(keyBuf), + &outBuf, &outMeta)); + + /* Caching key material under a port key id */ + meta.id = portKeyId; + meta.len = sizeof(keyBuf); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreCacheKey(server, &meta, keyBuf)); + + /* Freshen: how crypto handlers resolve keystore keys. A port key never + * resolves here; the handlers use it by reference instead */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_Server_KeystoreFreshenKey(server, portKeyId, NULL, NULL)); + + /* Reading port key material out of the keystore */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + wh_Server_KeystoreReadKey(server, portKeyId, NULL, keyBuf, &outSz)); + + /* Lifecycle operations */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreEvictKey(server, portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreCommitKey(server, portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreEraseKey(server, portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreEraseKeyChecked(server, portKeyId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreRevokeKey(server, portKeyId)); + + return WH_ERROR_OK; +} + +int whTest_PortKeyServer(whServerContext* ctx) +{ + WH_TEST_RETURN_ON_FAIL(_whTest_PortKeyModule()); + WH_TEST_RETURN_ON_FAIL(_whTest_PortKeyByValue()); +#ifdef WOLFHSM_CFG_PORTKEY_LOAD + WH_TEST_RETURN_ON_FAIL(_whTest_PortKeyLoadable()); +#endif + WH_TEST_RETURN_ON_FAIL(_whTest_PortKeyLifecycle()); + WH_TEST_RETURN_ON_FAIL(_whTest_KeystorePortKeyReject(ctx)); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_ENABLE_SERVER && WOLFHSM_CFG_PORTKEY */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 731fd3000..926c777d3 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -48,7 +48,8 @@ WH_TEST_DECL(whTest_CryptoAffinity); WH_TEST_DECL(whTest_CryptoAesGcmDmaAad); WH_TEST_DECL(whTest_Dma); WH_TEST_DECL(whTest_DmaNonCoherent); -WH_TEST_DECL(whTest_HwKeystore); +WH_TEST_DECL(whTest_PortKey); +WH_TEST_DECL(whTest_PortKeyCrypto); WH_TEST_DECL(whTest_KeystoreReqSize); WH_TEST_DECL(whTest_MessageNvmTranslate); WH_TEST_DECL(whTest_MultiClient); @@ -61,7 +62,7 @@ WH_TEST_DECL(whTest_CertEraseCannotReachNonCert); WH_TEST_DECL(whTest_CertPerClientIsolation); WH_TEST_DECL(whTest_CertVerifyCacheFullChain); WH_TEST_DECL(whTest_CertReadTrusted); -WH_TEST_DECL(whTest_HwKeystoreServer); +WH_TEST_DECL(whTest_PortKeyServer); WH_TEST_DECL(whTest_ServerImgMgr); WH_TEST_DECL(whTest_NvmOptional); WH_TEST_DECL(whTest_NvmPolicyChecked); @@ -118,44 +119,45 @@ WH_TEST_DECL(whTest_AuthSetCredentials); WH_TEST_DECL(whTest_AuthRequestAuthorization); const whTestCase whTestsMisc[] = { - {"whTest_CacheHelpers", whTest_CacheHelpers}, - {"whTest_ClientDevId", whTest_ClientDevId}, - {"whTest_ClientIdBinding", whTest_ClientIdBinding}, - {"whTest_Comm", whTest_Comm}, - {"whTest_Dma", whTest_Dma}, - {"whTest_DmaNonCoherent", whTest_DmaNonCoherent}, - {"whTest_CryptoAffinity", whTest_CryptoAffinity}, - {"whTest_CryptoAesGcmDmaAad", whTest_CryptoAesGcmDmaAad}, - {"whTest_KeystoreReqSize", whTest_KeystoreReqSize}, + {"whTest_CacheHelpers", whTest_CacheHelpers }, + {"whTest_ClientDevId", whTest_ClientDevId }, + {"whTest_ClientIdBinding", whTest_ClientIdBinding }, + {"whTest_Comm", whTest_Comm }, + {"whTest_Dma", whTest_Dma }, + {"whTest_DmaNonCoherent", whTest_DmaNonCoherent }, + {"whTest_CryptoAffinity", whTest_CryptoAffinity }, + {"whTest_CryptoAesGcmDmaAad", whTest_CryptoAesGcmDmaAad }, + {"whTest_KeystoreReqSize", whTest_KeystoreReqSize }, {"whTest_MessageNvmTranslate", whTest_MessageNvmTranslate}, - {"whTest_MultiClient", whTest_MultiClient}, - {"whTest_HwKeystore", whTest_HwKeystore}, - {"whTest_Lock", whTest_Lock}, - {"whTest_Log", whTest_Log}, - {"whTest_SheKeywrapInterop", whTest_SheKeywrapInterop}, - {"whTest_SheNoNvm", whTest_SheNoNvm}, - {"whTest_SheUidCb", whTest_SheUidCb}, + {"whTest_MultiClient", whTest_MultiClient }, + {"whTest_PortKey", whTest_PortKey }, + {"whTest_PortKeyCrypto", whTest_PortKeyCrypto }, + {"whTest_Lock", whTest_Lock }, + {"whTest_Log", whTest_Log }, + {"whTest_SheKeywrapInterop", whTest_SheKeywrapInterop }, + {"whTest_SheNoNvm", whTest_SheNoNvm }, + {"whTest_SheUidCb", whTest_SheUidCb }, }; const size_t whTestsMiscCount = ARRAY_SIZE(whTestsMisc); const whTestCase whTestsServer[] = { - {"whTest_CertVerify", whTest_CertVerify}, - {"whTest_CertNvmPolicy", whTest_CertNvmPolicy}, - {"whTest_CertReadRejectsServerOnly", whTest_CertReadRejectsServerOnly}, + {"whTest_CertVerify", whTest_CertVerify }, + {"whTest_CertNvmPolicy", whTest_CertNvmPolicy }, + {"whTest_CertReadRejectsServerOnly", whTest_CertReadRejectsServerOnly }, {"whTest_CertEraseCannotReachNonCert", whTest_CertEraseCannotReachNonCert}, - {"whTest_CertPerClientIsolation", whTest_CertPerClientIsolation}, - {"whTest_CertVerifyCacheFullChain", whTest_CertVerifyCacheFullChain}, - {"whTest_ServerImgMgr", whTest_ServerImgMgr}, - {"whTest_CertReadTrusted", whTest_CertReadTrusted}, - {"whTest_NvmOptional", whTest_NvmOptional}, - {"whTest_NvmPolicyChecked", whTest_NvmPolicyChecked}, - {"whTest_SheMasterEcuKeyFallback", whTest_SheMasterEcuKeyFallback}, - {"whTest_SheReqSizeChecking", whTest_SheReqSizeChecking}, - {"whTest_SheLoadKeyOversizedSlot", whTest_SheLoadKeyOversizedSlot}, - {"whTest_HwKeystoreServer", whTest_HwKeystoreServer}, - {"whTest_SheStateGate", whTest_SheStateGate}, - {"whTest_ShePrngSeedPersistence", whTest_ShePrngSeedPersistence}, - {"whTest_SheSaveReclaim", whTest_SheSaveReclaim}, + {"whTest_CertPerClientIsolation", whTest_CertPerClientIsolation }, + {"whTest_CertVerifyCacheFullChain", whTest_CertVerifyCacheFullChain }, + {"whTest_ServerImgMgr", whTest_ServerImgMgr }, + {"whTest_CertReadTrusted", whTest_CertReadTrusted }, + {"whTest_NvmOptional", whTest_NvmOptional }, + {"whTest_NvmPolicyChecked", whTest_NvmPolicyChecked }, + {"whTest_SheMasterEcuKeyFallback", whTest_SheMasterEcuKeyFallback }, + {"whTest_SheReqSizeChecking", whTest_SheReqSizeChecking }, + {"whTest_SheLoadKeyOversizedSlot", whTest_SheLoadKeyOversizedSlot }, + {"whTest_PortKeyServer", whTest_PortKeyServer }, + {"whTest_SheStateGate", whTest_SheStateGate }, + {"whTest_ShePrngSeedPersistence", whTest_ShePrngSeedPersistence }, + {"whTest_SheSaveReclaim", whTest_SheSaveReclaim }, }; const size_t whTestsServerCount = ARRAY_SIZE(whTestsServer); diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 669706220..d872ee488 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -37,6 +37,11 @@ /** wolfHSM required settings for wolfCrypt */ #define WOLF_CRYPTO_CB + +/* Port keys are used by reference (WOLFHSM_CFG_PORTKEY) */ +#ifndef WOLF_PRIVATE_KEY_ID +#define WOLF_PRIVATE_KEY_ID +#endif /* Allow test makefile to turn keygen on/off */ #ifndef WOLFHSM_CFG_TEST_NO_KEY_GEN #define WOLFSSL_KEY_GEN diff --git a/test/config/wolfhsm_cfg.h b/test/config/wolfhsm_cfg.h index 64d2d39d4..56a0d6c0f 100644 --- a/test/config/wolfhsm_cfg.h +++ b/test/config/wolfhsm_cfg.h @@ -55,9 +55,9 @@ /* Enable Image Manager feature */ #define WOLFHSM_CFG_SERVER_IMG_MGR +/* Port keys (WOLFHSM_CFG_PORTKEY) are tested in test-refactor */ #ifndef WOLFHSM_CFG_NO_CRYPTO #define WOLFHSM_CFG_KEYWRAP -#define WOLFHSM_CFG_HWKEYSTORE #endif /* Test log-based NVM flash backend */ diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index f716566b6..59cc29568 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -70,6 +70,12 @@ whMessageKeystore_ExportResponse keyExportRes; whMessageKeystore_EraseResponse keyEraseRes; whMessageKeystore_ExportPublicRequest keyExportPublicReq; whMessageKeystore_ExportPublicResponse keyExportPublicRes; +whMessageKeystore_PortLoadRequest keyPortLoadReq; +whMessageKeystore_PortLoadResponse keyPortLoadRes; +whMessageKeystore_PortClearRequest keyPortClearReq; +whMessageKeystore_PortClearResponse keyPortClearRes; +whMessageKeystore_PortInfoRequest keyPortInfoReq; +whMessageKeystore_PortInfoResponse keyPortInfoRes; /* Include counter message header for new counter message structures */ #include "wolfhsm/wh_message_counter.h" diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 30f0d535a..3d2cca254 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -18813,11 +18813,11 @@ int whTest_CryptoClientConfig(whClientConfig* config) if (ret == 0) { ret = whTest_Client_DataWrap(client); } -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_ENABLE_SERVER) - /* Hardware-only KEK tests need the in-process test server, which binds - * the test hardware keystore; external (client-only) servers may not */ +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_ENABLE_SERVER) + /* Port KEK tests need the in-process test server, which binds + * the test port keys; external (client-only) servers may not */ if (ret == 0) { - ret = whTest_Client_HwKeystore(client); + ret = whTest_Client_PortKey(client); } #endif #endif @@ -19585,34 +19585,38 @@ static int wh_ClientServer_MemThreadTest(whTestNvmBackendType nvmType) .cb = whTestDma_BounceServerCb, }; #endif -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_KEYWRAP) - /* Hardware keystore front-end backed by the test getKey callback. The +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_KEYWRAP) + /* Port key front end backed by the test getKey callback. The * callback is defined in wh_test_keywrap.c, so only bind it when the * keywrap suite (its sole consumer) is compiled in */ - static const whHwKeystoreCb hwksCb = WH_TEST_HWKEYSTORE_CB; - whHwKeystoreContext hwKeystore[1] = {{0}}; - whHwKeystoreConfig hwksConf[1] = {{ - .cb = &hwksCb, - .context = NULL, - }}; + static const whPortKeyCb pksCb = WH_TEST_PORTKEY_CB; + whPortKeyContext portKey[1] = {{0}}; + whPortKeyConfig pksConf[1] = { + { + .cb = &pksCb, + .context = NULL, + } + }; #endif - whServerConfig s_conf[1] = {{ - .comm_config = cs_conf, - .nvm = nvm, - .crypto = crypto, - .devId = INVALID_DEVID, + whServerConfig s_conf[1] = { + { + .comm_config = cs_conf, + .nvm = nvm, + .crypto = crypto, + .devId = INVALID_DEVID, #ifdef WOLFHSM_CFG_DMA - .dmaConfig = &serverDmaConfig, + .dmaConfig = &serverDmaConfig, #endif -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_KEYWRAP) - .hwKeystore = hwKeystore, +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_KEYWRAP) + .portKey = portKey, #endif - }}; + } + }; WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf)); -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_KEYWRAP) - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwKeystore, hwksConf)); +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_KEYWRAP) + WH_TEST_RETURN_ON_FAIL(wh_PortKey_Init(portKey, pksConf)); #endif #ifdef WOLFHSM_CFG_DMA @@ -19650,8 +19654,8 @@ static int wh_ClientServer_MemThreadTest(whTestNvmBackendType nvmType) } wh_Nvm_Cleanup(nvm); -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_KEYWRAP) - (void)wh_HwKeystore_Cleanup(hwKeystore); +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_KEYWRAP) + (void)wh_PortKey_Cleanup(portKey); #endif wc_FreeRng(crypto->rng); wolfCrypt_Cleanup(); diff --git a/test/wh_test_keywrap.c b/test/wh_test_keywrap.c index 4f079a438..e566a05f1 100644 --- a/test/wh_test_keywrap.c +++ b/test/wh_test_keywrap.c @@ -35,45 +35,45 @@ #include "wh_test_common.h" -#ifdef WOLFHSM_CFG_HWKEYSTORE +#ifdef WOLFHSM_CFG_PORTKEY #include "wolfhsm/wh_keyid.h" -/* Hardware keystore test fixture: the id and material of the only KEK served - * by whTest_HwKeystoreGetKeyCb, which emulates a hardware keystore backend +/* Port key test fixture: the id and material of the only KEK served + * by whTest_PortKeyGetKeyCb, which emulates a port key backend * for the in-process test servers */ -#define WH_TEST_HWKEK_ID 3 -#define WH_TEST_HWKEK_SIZE 32 +#define WH_TEST_PORTKEK_ID 3 +#define WH_TEST_PORTKEK_SIZE 32 -static const uint8_t _hwKekMaterial[WH_TEST_HWKEK_SIZE] = { +static const uint8_t _portKekMaterial[WH_TEST_PORTKEK_SIZE] = { 0x9a, 0x4e, 0x21, 0xc7, 0x5d, 0x10, 0xfb, 0x33, 0x6f, 0x82, 0xd4, 0x59, 0xee, 0x07, 0xb1, 0x2c, 0x48, 0x95, 0x3a, 0xc6, 0x71, 0x0d, 0xb8, 0xe5, 0x12, 0x6a, 0xf9, 0x84, 0x2f, 0xd0, 0x5b, 0xa7}; -int whTest_HwKeystoreGetKeyCb(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len) +int whTest_PortKeyGetKeyCb(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len) { (void)context; - /* Only hardware-only keyIds should ever reach a hardware keystore */ - if (WH_KEYID_TYPE(keyId) != WH_KEYTYPE_HW) { + /* Only port keyIds should ever reach a port key backend */ + if (WH_KEYID_TYPE(keyId) != WH_KEYTYPE_PORT) { return WH_ERROR_ACCESS; } /* Serve only the known test KEK id, refuse everything else */ - if (WH_KEYID_ID(keyId) != WH_TEST_HWKEK_ID) { + if (WH_KEYID_ID(keyId) != WH_TEST_PORTKEK_ID) { return WH_ERROR_NOTFOUND; } if ((out == NULL) || (inout_len == NULL) || - (*inout_len < sizeof(_hwKekMaterial))) { + (*inout_len < sizeof(_portKekMaterial))) { return WH_ERROR_BUFFER_SIZE; } - memcpy(out, _hwKekMaterial, sizeof(_hwKekMaterial)); - *inout_len = sizeof(_hwKekMaterial); + memcpy(out, _portKekMaterial, sizeof(_portKekMaterial)); + *inout_len = sizeof(_portKekMaterial); return WH_ERROR_OK; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ #ifdef WOLFHSM_CFG_ENABLE_CLIENT #include "wolfhsm/wh_client.h" @@ -129,14 +129,14 @@ static int _CleanupServerKek(whClientContext* client) #ifdef HAVE_AESGCM /* The wrap-export and unwrap-and-cache round trips below require a trusted KEK - * (HW or WH_NVM_FLAGS_TRUSTED). They use the hardware KEK fixture, which is + * (port key or WH_NVM_FLAGS_TRUSTED). They use the port KEK fixture, which is * bound only by the in-process test server, so compile them only when the * server is in this binary. */ -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_ENABLE_SERVER) +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_ENABLE_SERVER) /* kekId names the KEK used for every wrap/unwrap step. The unwrap-and-cache - * step requires a trusted KEK (HW or WH_NVM_FLAGS_TRUSTED), so callers pass - * the hardware KEK fixture id here. */ + * step requires a trusted KEK (port key or WH_NVM_FLAGS_TRUSTED), so callers + * pass the port KEK fixture id here. */ static int _AesGcm_TestKeyWrap(whClientContext* client, WC_RNG* rng, whKeyId kekId) { @@ -485,13 +485,13 @@ static int _AesGcm_TestKeyWrapNoNvm(whClientContext* client, whKeyId kekId) return WH_ERROR_OK; } -#endif /* WOLFHSM_CFG_HWKEYSTORE && WOLFHSM_CFG_ENABLE_SERVER */ +#endif /* WOLFHSM_CFG_PORTKEY && WOLFHSM_CFG_ENABLE_SERVER */ /* Positive software-KEK round trip; needs no trusted KEK, so it runs in every * build. Wrap a plaintext key under the plain software KEK (WH_TEST_KEKID from * _InitServerKek), unwrap-and-export the blob, and confirm the key material and * metadata come back unchanged. Only the unwrap-and-cache path (covered by the - * hardware-KEK tests) requires a trusted KEK. */ + * port-KEK tests) requires a trusted KEK. */ static int _AesGcm_TestSwKekRoundTrip(whClientContext* client, WC_RNG* rng) { int ret; @@ -551,7 +551,7 @@ static int _AesGcm_TestSwKekRoundTrip(whClientContext* client, WC_RNG* rng) * that (a) a plain client-cached USAGE_WRAP key (WH_TEST_KEKID, provisioned by * _InitServerKek) is refused as their KEK, and (b) a client cannot forge a * trusted KEK by setting WH_NVM_FLAGS_TRUSTED itself -- the server strips the - * flag, so the key is still refused. Needs no hardware keystore, so it runs + * flag, so the key is still refused. Needs no port keys, so it runs * against any server. */ static int _AesGcm_TestTrustedKekPolicy(whClientContext* client, WC_RNG* rng) { @@ -869,12 +869,12 @@ static int _AesGcm_TestDataUnwrapUnderflow(whClientContext* client) return WH_ERROR_OK; } -#ifdef WOLFHSM_CFG_HWKEYSTORE +#ifdef WOLFHSM_CFG_PORTKEY -static int _AesGcm_TestHwKeystoreKeyWrap(whClientContext* client, WC_RNG* rng) +static int _AesGcm_TestPortKeyKeyWrap(whClientContext* client, WC_RNG* rng) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); uint8_t plainKey[WH_TEST_AES_KEYSIZE]; uint8_t tmpPlainKey[WH_TEST_AES_KEYSIZE]; uint16_t tmpPlainKeySz = sizeof(tmpPlainKey); @@ -884,7 +884,7 @@ static int _AesGcm_TestHwKeystoreKeyWrap(whClientContext* client, WC_RNG* rng) whNvmMetadata metadata = { .id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, WH_TEST_AESGCM_KEYID), - .label = "HW KEK wrapped key", + .label = "port KEK wrapped key", .len = WH_TEST_AES_KEYSIZE, .flags = WH_NVM_FLAGS_USAGE_ANY, }; @@ -896,61 +896,61 @@ static int _AesGcm_TestHwKeystoreKeyWrap(whClientContext* client, WC_RNG* rng) return ret; } - /* Wrap a key using the hardware-only KEK */ - ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, hwKekId, plainKey, + /* Wrap a key using the port KEK */ + ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, portKekId, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_KeyWrap with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to wh_Client_KeyWrap with port KEK %d\n", ret); return ret; } - /* Unwrap and export with the hardware-only KEK, check the roundtrip */ - ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, hwKekId, + /* Unwrap and export with the port KEK, check the roundtrip */ + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, portKekId, wrappedKey, wrappedKeySz, &tmpMetadata, tmpPlainKey, &tmpPlainKeySz); if (ret != WH_ERROR_OK) { WH_ERROR_PRINT( - "Failed to wh_Client_KeyUnwrapAndExport with HW KEK %d\n", ret); + "Failed to wh_Client_KeyUnwrapAndExport with port KEK %d\n", ret); return ret; } if (memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { - WH_ERROR_PRINT("HW KEK wrap/unwrap key failed to match\n"); + WH_ERROR_PRINT("port KEK wrap/unwrap key failed to match\n"); return WH_TEST_FAIL; } if (memcmp(&metadata, &tmpMetadata, sizeof(metadata)) != 0) { - WH_ERROR_PRINT("HW KEK wrap/unwrap metadata failed to match\n"); + WH_ERROR_PRINT("port KEK wrap/unwrap metadata failed to match\n"); return WH_TEST_FAIL; } - /* Unwrap-and-cache with the hardware-only KEK: the wrapped payload is an + /* Unwrap-and-cache with the port KEK: the wrapped payload is an * ordinary key and may enter the cache; only the KEK itself is - * hardware-resident */ + * held by the port */ ret = - wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, hwKekId, + wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, portKekId, wrappedKey, wrappedKeySz, &unwrappedKeyId); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_KeyUnwrapAndCache with HW KEK %d\n", - ret); + WH_ERROR_PRINT( + "Failed to wh_Client_KeyUnwrapAndCache with port KEK %d\n", ret); return ret; } WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, unwrappedKeyId)); - /* A wrapped+hardware-only KEK id must behave as hardware-only (the - * hardware-only flag takes precedence) */ + /* A wrapped+port KEK id must behave as a port key (the + * port key flag takes precedence) */ wrappedKeySz = sizeof(wrappedKey); ret = wh_Client_KeyWrap( - client, WC_CIPHER_AES_GCM, hwKekId | WH_KEYID_CLIENT_WRAPPED_FLAG, + client, WC_CIPHER_AES_GCM, portKekId | WH_KEYID_CLIENT_WRAPPED_FLAG, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_KeyWrap with wrapped+HW KEK %d\n", + WH_ERROR_PRINT("Failed to wh_Client_KeyWrap with wrapped+port KEK %d\n", ret); return ret; } - /* Unwrapping a hardware-KEK-wrapped blob with a different, cached KEK + /* Unwrapping a port-KEK-wrapped blob with a different, cached KEK * must fail authentication, proving distinct key material was used */ WH_TEST_RETURN_ON_FAIL(_InitServerKek(client)); tmpPlainKeySz = sizeof(tmpPlainKey); @@ -963,14 +963,14 @@ static int _AesGcm_TestHwKeystoreKeyWrap(whClientContext* client, WC_RNG* rng) return WH_TEST_FAIL; } - /* A hardware KEK id the backend does not serve must fail */ + /* A port KEK id the backend does not serve must fail */ wrappedKeySz = sizeof(wrappedKey); ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, - WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID + 1), + WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID + 1), plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_NOTFOUND) { - WH_ERROR_PRINT("KeyWrap with unserved HW KEK expected NOTFOUND, " + WH_ERROR_PRINT("KeyWrap with unserved port KEK expected NOTFOUND, " "got %d\n", ret); return WH_TEST_FAIL; @@ -979,107 +979,111 @@ static int _AesGcm_TestHwKeystoreKeyWrap(whClientContext* client, WC_RNG* rng) return WH_ERROR_OK; } -static int _AesGcm_TestHwKeystoreDataWrap(whClientContext* client) +static int _AesGcm_TestPortKeyDataWrap(whClientContext* client) { int ret; - whKeyId hwKekId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId portKekId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); uint8_t data[] = "Example data spanning several AES blocks to catch overlap bugs!"; uint8_t unwrappedData[sizeof(data)] = {0}; uint32_t unwrappedDataSz = sizeof(unwrappedData); uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE] = {0}; uint32_t wrappedDataSz = sizeof(wrappedData); - ret = wh_Client_DataWrap(client, WC_CIPHER_AES_GCM, hwKekId, data, + ret = wh_Client_DataWrap(client, WC_CIPHER_AES_GCM, portKekId, data, sizeof(data), wrappedData, &wrappedDataSz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_DataWrap with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to wh_Client_DataWrap with port KEK %d\n", ret); return ret; } - ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, hwKekId, wrappedData, - sizeof(wrappedData), unwrappedData, + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, portKekId, + wrappedData, sizeof(wrappedData), unwrappedData, &unwrappedDataSz); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_DataUnwrap with HW KEK %d\n", ret); + WH_ERROR_PRINT("Failed to wh_Client_DataUnwrap with port KEK %d\n", + ret); return ret; } if (memcmp(data, unwrappedData, sizeof(data)) != 0) { - WH_ERROR_PRINT("HW KEK unwrapped data failed to match input data\n"); + WH_ERROR_PRINT("port KEK unwrapped data failed to match input data\n"); return WH_TEST_FAIL; } return WH_ERROR_OK; } -/* Hardware-only keys must be rejected by every keystore operation and by +/* Port keys must be rejected by every keystore operation and by * crypto key use; only the keywrap KEK paths may resolve them */ -static int _TestHwKeystoreKeystoreRejections(whClientContext* client) +static int _TestPortKeyKeystoreRejections(whClientContext* client) { int ret; - whKeyId hwKeyId = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); - uint8_t buf[WH_TEST_HWKEK_SIZE] = {0}; - uint16_t bufSz = sizeof(buf); - uint8_t label[WH_NVM_LABEL_LEN] = "hwonly reject"; - whKeyId cacheKeyId = hwKeyId; + whKeyId portKeyId = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); + uint8_t buf[WH_TEST_PORTKEK_SIZE] = {0}; + uint16_t bufSz = sizeof(buf); + uint8_t label[WH_NVM_LABEL_LEN] = "portkey reject"; + whKeyId cacheKeyId = portKeyId; - /* Caching key material under a hardware-only id must be rejected */ + /* Caching key material under a port key id must be rejected */ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE, label, sizeof(label), buf, sizeof(buf), &cacheKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyCache of HW-only id expected ACCESS, got %d\n", ret); + WH_ERROR_PRINT("KeyCache of port key id expected ACCESS, got %d\n", + ret); return WH_TEST_FAIL; } #ifdef WOLFHSM_CFG_DMA - /* The DMA cache path must reject hardware-only ids as well */ - cacheKeyId = hwKeyId; + /* The DMA cache path must reject port key ids as well */ + cacheKeyId = portKeyId; ret = wh_Client_KeyCacheDma(client, WH_NVM_FLAGS_NONE, label, sizeof(label), buf, sizeof(buf), &cacheKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyCacheDma of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyCacheDma of port key id expected ACCESS, got %d\n", ret); return WH_TEST_FAIL; } #endif - /* Exporting a hardware-only key must be rejected */ - ret = - wh_Client_KeyExport(client, hwKeyId, label, sizeof(label), buf, &bufSz); + /* Exporting a port key must be rejected */ + ret = wh_Client_KeyExport(client, portKeyId, label, sizeof(label), buf, + &bufSz); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyExport of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyExport of port key id expected ACCESS, got %d\n", ret); return WH_TEST_FAIL; } - /* Commit/evict/erase/revoke of a hardware-only key must be rejected */ - ret = wh_Client_KeyCommit(client, hwKeyId); + /* Commit/evict/erase/revoke of a port key must be rejected */ + ret = wh_Client_KeyCommit(client, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyCommit of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyCommit of port key id expected ACCESS, got %d\n", ret); return WH_TEST_FAIL; } - ret = wh_Client_KeyEvict(client, hwKeyId); + ret = wh_Client_KeyEvict(client, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyEvict of HW-only id expected ACCESS, got %d\n", ret); + WH_ERROR_PRINT("KeyEvict of port key id expected ACCESS, got %d\n", + ret); return WH_TEST_FAIL; } - ret = wh_Client_KeyErase(client, hwKeyId); + ret = wh_Client_KeyErase(client, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyErase of HW-only id expected ACCESS, got %d\n", ret); + WH_ERROR_PRINT("KeyErase of port key id expected ACCESS, got %d\n", + ret); return WH_TEST_FAIL; } - ret = wh_Client_KeyRevoke(client, hwKeyId); + ret = wh_Client_KeyRevoke(client, portKeyId); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("KeyRevoke of HW-only id expected ACCESS, got %d\n", + WH_ERROR_PRINT("KeyRevoke of port key id expected ACCESS, got %d\n", ret); return WH_TEST_FAIL; } - /* Crypto operations must not be able to use hardware-only keys */ + /* Crypto operations must not be able to use port keys */ { Aes aes[1]; const uint8_t plaintext[16] = {0}; @@ -1088,13 +1092,13 @@ static int _TestHwKeystoreKeystoreRejections(whClientContext* client) uint8_t tag[WH_KEYWRAP_AES_GCM_TAG_SIZE] = {0}; WH_TEST_RETURN_ON_FAIL(wc_AesInit(aes, NULL, WH_DEV_ID)); - WH_TEST_RETURN_ON_FAIL(wh_Client_AesSetKeyId(aes, hwKeyId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_AesSetKeyId(aes, portKeyId)); ret = wc_AesGcmEncrypt(aes, ciphertext, plaintext, sizeof(plaintext), iv, sizeof(iv), tag, sizeof(tag), NULL, 0); wc_AesFree(aes); if (ret != WH_ERROR_ACCESS) { - WH_ERROR_PRINT("AES-GCM with HW-only key expected ACCESS, " + WH_ERROR_PRINT("AES-GCM with port key expected ACCESS, " "got %d\n", ret); return WH_TEST_FAIL; @@ -1104,7 +1108,7 @@ static int _TestHwKeystoreKeystoreRejections(whClientContext* client) return WH_ERROR_OK; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ #endif /* HAVE_AESGCM */ @@ -1126,11 +1130,11 @@ int whTest_Client_KeyWrap(whClientContext* client) } #ifdef HAVE_AESGCM -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_ENABLE_SERVER) - /* Wrap-export and unwrap-and-cache need a trusted KEK; use the hardware KEK +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_ENABLE_SERVER) + /* Wrap-export and unwrap-and-cache need a trusted KEK; use the port KEK * fixture bound by the in-process test server. */ { - whKeyId trustedKek = WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID); + whKeyId trustedKek = WH_CLIENT_KEYID_MAKE_PORT(WH_TEST_PORTKEK_ID); ret = _AesGcm_TestKeyWrap(client, rng, trustedKek); if (ret != WH_ERROR_OK) { @@ -1151,7 +1155,7 @@ int whTest_Client_KeyWrap(whClientContext* client) } } } -#endif /* WOLFHSM_CFG_HWKEYSTORE && WOLFHSM_CFG_ENABLE_SERVER */ +#endif /* WOLFHSM_CFG_PORTKEY && WOLFHSM_CFG_ENABLE_SERVER */ /* Positive software-KEK round trip, runs in every build. */ if (ret == WH_ERROR_OK) { @@ -1213,10 +1217,10 @@ int whTest_Client_DataWrap(whClientContext* client) return ret; } -#ifdef WOLFHSM_CFG_HWKEYSTORE -/* Requires a server with a hardware keystore backed by - * whTest_HwKeystoreGetKeyCb */ -int whTest_Client_HwKeystore(whClientContext* client) +#ifdef WOLFHSM_CFG_PORTKEY +/* Requires a server with port keys backed by + * whTest_PortKeyGetKeyCb */ +int whTest_Client_PortKey(whClientContext* client) { int ret = 0; WC_RNG rng[1]; @@ -1228,36 +1232,35 @@ int whTest_Client_HwKeystore(whClientContext* client) } #ifdef HAVE_AESGCM - ret = _AesGcm_TestHwKeystoreKeyWrap(client, rng); + ret = _AesGcm_TestPortKeyKeyWrap(client, rng); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _AesGcm_TestHwKeystoreKeyWrap %d\n", ret); + WH_ERROR_PRINT("Failed to _AesGcm_TestPortKeyKeyWrap %d\n", ret); } if (ret == WH_ERROR_OK) { - ret = _AesGcm_TestHwKeystoreDataWrap(client); + ret = _AesGcm_TestPortKeyDataWrap(client); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _AesGcm_TestHwKeystoreDataWrap %d\n", - ret); + WH_ERROR_PRINT("Failed to _AesGcm_TestPortKeyDataWrap %d\n", ret); } } if (ret == WH_ERROR_OK) { - ret = _TestHwKeystoreKeystoreRejections(client); + ret = _TestPortKeyKeystoreRejections(client); if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _TestHwKeystoreKeystoreRejections %d\n", + WH_ERROR_PRINT("Failed to _TestPortKeyKeystoreRejections %d\n", ret); } } #endif if (ret == WH_ERROR_OK) { - WH_TEST_PRINT("HW KEYSTORE KEYWRAP TESTS SUCCESS\n"); + WH_TEST_PRINT("PORT KEY KEYWRAP TESTS SUCCESS\n"); } (void)wc_FreeRng(rng); return ret; } -#endif /* WOLFHSM_CFG_HWKEYSTORE */ +#endif /* WOLFHSM_CFG_PORTKEY */ int whTest_KeyWrapClientConfig(whClientConfig* clientCfg) { diff --git a/test/wh_test_keywrap.h b/test/wh_test_keywrap.h index 92d91245e..5f3f164f4 100644 --- a/test/wh_test_keywrap.h +++ b/test/wh_test_keywrap.h @@ -26,26 +26,26 @@ int whTest_Client_KeyWrap(whClientContext* ctx); int whTest_Client_DataWrap(whClientContext* ctx); int whTest_KeyWrapClientConfig(whClientConfig* cf); -#if defined(WOLFHSM_CFG_HWKEYSTORE) && defined(WOLFHSM_CFG_KEYWRAP) -#include "wolfhsm/wh_hwkeystore.h" +#if defined(WOLFHSM_CFG_PORTKEY) && defined(WOLFHSM_CFG_KEYWRAP) +#include "wolfhsm/wh_portkey.h" -/* Test getKey callback emulating a hardware keystore backend; bind this to - * the server's whHwKeystoreContext when running whTest_Client_HwKeystore. +/* Test getKey callback emulating a port key backend; bind this to + * the server's whPortKeyContext when running whTest_Client_PortKey. * Defined in wh_test_keywrap.c, so only available when keywrap is enabled */ -int whTest_HwKeystoreGetKeyCb(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len); -int whTest_Client_HwKeystore(whClientContext* ctx); +int whTest_PortKeyGetKeyCb(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len); +int whTest_Client_PortKey(whClientContext* ctx); /* Convenience callback-table initializer for the emulated test backend. The * backend needs no setup/teardown, so Init and Cleanup are NULL */ /* clang-format off */ -#define WH_TEST_HWKEYSTORE_CB \ +#define WH_TEST_PORTKEY_CB \ { \ .Init = NULL, \ .Cleanup = NULL, \ - .GetKey = whTest_HwKeystoreGetKeyCb, \ + .GetKey = whTest_PortKeyGetKeyCb, \ } /* clang-format on */ -#endif /* WOLFHSM_CFG_HWKEYSTORE && WOLFHSM_CFG_KEYWRAP */ +#endif /* WOLFHSM_CFG_PORTKEY && WOLFHSM_CFG_KEYWRAP */ #endif /* WH_TEST_KEYWRAP_H_ */ diff --git a/test/wh_test_multiclient.c b/test/wh_test_multiclient.c index 61ffae787..f7ff99cf9 100644 --- a/test/wh_test_multiclient.c +++ b/test/wh_test_multiclient.c @@ -2180,35 +2180,36 @@ static int _testNvmNonAddVerbRejections(whClientContext* client1, } /* - * Verify that WRAPPED and HW client flags cannot access differently typed + * Verify that WRAPPED and PORT client flags cannot access differently typed * objects through the NVM API. */ -static int _testNvmWrappedHwFlagIsolation(whClientContext* client1, - whServerContext* server1, - whClientContext* client2, - whServerContext* server2) +static int _testNvmWrappedPortFlagIsolation(whClientContext* client1, + whServerContext* server1, + whClientContext* client2, + whServerContext* server2) { const whNvmId planted_id = 9; const uint8_t secret[] = "planted-non-nvm-secret"; const whNvmSize secretSz = (whNvmSize)sizeof(secret); const whKeyId clientFlags[2] = {WH_KEYID_CLIENT_WRAPPED_FLAG, - WH_KEYID_CLIENT_HW_FLAG}; + WH_KEYID_CLIENT_PORT_FLAG}; int32_t out_rc = 0; whNvmSize out_len = 0; uint8_t buf[64] = {0}; whNvmMetadata meta = {0}; whKeyId wrappedId; - whKeyId hwId; + whKeyId portKeyId; int i; (void)client2; (void)server2; - WH_TEST_PRINT("Testing NVM WRAPPED/HW flag type isolation...\n"); + WH_TEST_PRINT("Testing NVM WRAPPED/PORT flag type isolation...\n"); wrappedId = WH_MAKE_KEYID(WH_KEYTYPE_WRAPPED, server1->comm->client_id, planted_id); - hwId = WH_MAKE_KEYID(WH_KEYTYPE_HW, server1->comm->client_id, planted_id); + portKeyId = + WH_MAKE_KEYID(WH_KEYTYPE_PORT, server1->comm->client_id, planted_id); meta.access = WH_NVM_ACCESS_ANY; meta.flags = WH_NVM_FLAGS_NONE; @@ -2216,7 +2217,7 @@ static int _testNvmWrappedHwFlagIsolation(whClientContext* client1, meta.id = wrappedId; WH_TEST_ASSERT_RETURN( wh_Nvm_AddObject(server1->nvm, &meta, secretSz, secret) == WH_ERROR_OK); - meta.id = hwId; + meta.id = portKeyId; WH_TEST_ASSERT_RETURN( wh_Nvm_AddObject(server1->nvm, &meta, secretSz, secret) == WH_ERROR_OK); @@ -2242,13 +2243,13 @@ static int _testNvmWrappedHwFlagIsolation(whClientContext* client1, WH_TEST_ASSERT_RETURN(wh_Nvm_GetMetadata(server1->nvm, wrappedId, &meta) == WH_ERROR_OK); - WH_TEST_ASSERT_RETURN(wh_Nvm_GetMetadata(server1->nvm, hwId, &meta) == + WH_TEST_ASSERT_RETURN(wh_Nvm_GetMetadata(server1->nvm, portKeyId, &meta) == WH_ERROR_OK); (void)wh_Nvm_DestroyObjects(server1->nvm, 1, &wrappedId); - (void)wh_Nvm_DestroyObjects(server1->nvm, 1, &hwId); + (void)wh_Nvm_DestroyObjects(server1->nvm, 1, &portKeyId); - WH_TEST_PRINT(" NVM WRAPPED/HW flag isolation: PASS\n"); + WH_TEST_PRINT(" NVM WRAPPED/PORT flag isolation: PASS\n"); return WH_ERROR_OK; } @@ -2274,7 +2275,7 @@ static int _runNvmIdTranslationTests(whClientContext* client1, WH_TEST_RETURN_ON_FAIL( _testNvmNonAddVerbRejections(client1, server1, client2, server2)); WH_TEST_RETURN_ON_FAIL( - _testNvmWrappedHwFlagIsolation(client1, server1, client2, server2)); + _testNvmWrappedPortFlagIsolation(client1, server1, client2, server2)); WH_TEST_PRINT("All NVM Id Translation Tests PASSED ===\n"); return WH_ERROR_OK; } diff --git a/tools/whnvmtool/README.md b/tools/whnvmtool/README.md index ae70cc7b0..4d47e6c7d 100644 --- a/tools/whnvmtool/README.md +++ b/tools/whnvmtool/README.md @@ -82,9 +82,9 @@ SHE entries take no `