From 5ee1a258e9ee3e93561c021ad92b79741b66ccc1 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:57:53 -0600 Subject: [PATCH] Modify cert layer to support wrapped/cached certs --- docs/draft/wrapped-certs.md | 290 ++++++++++++++++++++++++++++++++++++ src/wh_client_cert.c | 164 ++++++++++++++++++++ src/wh_server_cert.c | 160 ++++++++++++++------ test/wh_test_cert.c | 238 +++++++++++++++++++++++++++++ wolfhsm/wh_client.h | 195 ++++++++++++++++++++++++ wolfhsm/wh_server_cert.h | 22 ++- 6 files changed, 1017 insertions(+), 52 deletions(-) create mode 100644 docs/draft/wrapped-certs.md diff --git a/docs/draft/wrapped-certs.md b/docs/draft/wrapped-certs.md new file mode 100644 index 000000000..b9abf6794 --- /dev/null +++ b/docs/draft/wrapped-certs.md @@ -0,0 +1,290 @@ +# Wrapped Cached Certificates + +## Overview + +This documents the "wrapped certificate" use case, showing how to leverage the +certificate manager to use trusted root certificates that live in the server's +**keystore cache** (RAM) after being unwrapped via keywrap funcitonality, +rather than exclusively in **NVM** (flash). A root certificate is wrapped +(AES-GCM encrypted) by the server, handed back to the client as an opaque blob, +and later unwrapped into the server's key cache on demand. Once cached, it can +be used in all certificate verification paths — standard, DMA, and ACERT — +exactly like an NVM-resident root certificate. + +This is useful when a client needs to use a trusted root for verification but +does not want to (or cannot) commit it to NVM. The wrapped blob can be stored +cheaply on the client side, while the server only holds the unwrapped plaintext +in its volatile cache for as long as it is needed. + +## High-Level Usage + +The lifecycle has three stages: **wrap**, **unwrap-and-cache**, and **use**. + +### 1. Provision a wrapping key (KEK) + +Before wrapping anything the server needs an AES-256 key to use as the +key-encryption key. Cache it on the server with the `WH_NVM_FLAGS_USAGE_WRAP` +flag: + +```c +whKeyId kekId = 10; +uint8_t kek[32] = { /* 256-bit AES key */ }; + +wh_Client_KeyCache(client, + WH_NVM_FLAGS_USAGE_WRAP, NULL, 0, + kek, sizeof(kek), &kekId); +``` + +The KEK is now sitting in the server's `localCache` (or `globalCache` if marked +global), indexed by `kekId`. + +### 2. Wrap the certificate + +Call `wh_Client_CertWrap` with the raw certificate DER and the KEK's ID. The +server encrypts the certificate using AES-GCM and returns the wrapped blob: + +```c +uint8_t wrappedCert[2048]; +uint16_t wrappedCertSz = sizeof(wrappedCert); + +/* Build metadata: id embeds TYPE=WRAPPED and the client's USER id; + * caller controls flags, access, and optionally label */ +whNvmMetadata certMeta = {0}; +certMeta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META( + client->comm->client_id, 5); +certMeta.flags = WH_NVM_FLAGS_USAGE_ANY; +certMeta.access = WH_NVM_ACCESS_ANY; + +wh_Client_CertWrap(client, WC_CIPHER_AES_GCM, kekId, + rootCaCert, rootCaCertLen, + &certMeta, + wrappedCert, &wrappedCertSz); +``` + +After this call: + +| Data | Location | +|---|---| +| KEK | Server key cache (`localCache[kekId]`) | +| Wrapped cert blob (ciphertext + GCM tag + IV + metadata) | Client memory (`wrappedCert` buffer) | +| Raw certificate | Nowhere on the server — only the client supplied it transiently | + +The client can now persist `wrappedCert` to its own storage (file, flash, +external memory, etc.). + +### 3. Unwrap and cache the certificate on the server + +When the client needs the root for verification, it pushes the wrapped blob back +to the server: + +```c +whKeyId cachedCertId = WH_KEYID_ERASED; + +wh_Client_CertUnwrapAndCache(client, WC_CIPHER_AES_GCM, kekId, + wrappedCert, wrappedCertSz, + &cachedCertId); +``` + +The server decrypts the blob using the KEK, verifies the GCM authentication +tag, and places the plaintext certificate into its key cache. The returned +`cachedCertId` is the server-internal key ID (with `TYPE=WH_KEYTYPE_WRAPPED` +already encoded). + +After this call: + +| Data | Location | +|---|---| +| KEK | Server key cache | +| Plaintext certificate | Server key cache (`localCache[cachedCertId]`) | +| Wrapped cert blob | Still in client memory (unchanged) | + +### 4. Use the cached cert for verification + +Pass the cached cert's ID — decorated with the wrapped flag — as the trusted +root to any verify API: + +```c +int32_t verifyResult; + +wh_Client_CertVerify(client, + intermediateCert, intermediateCertLen, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), + &verifyResult); +``` + +`WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId)` sets bit 9 +(`WH_KEYID_CLIENT_WRAPPED_FLAG = 0x0200`) on the ID the client sends to the +server. This is the signal that tells the server "this root cert is in the +cache, not in NVM." + +The same pattern works for: +- `wh_Client_CertVerifyDma` (DMA path) +- `wh_Client_CertReadTrusted` / `wh_Client_CertReadTrustedDma` (read-back) +- `wh_Client_CertVerifyAcert` / `wh_Client_CertVerifyAcertDma` (attribute certs) + +### 5. Cleanup + +Evict the cached cert and KEK when done: + +```c +wh_Client_KeyEvict(client, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId)); +wh_Client_KeyEvict(client, kekId); +``` + +## Low-Level Implementation Details + +### Client-side functions + +Nine thin wrappers in `src/wh_client_cert.c` (guarded by +`WOLFHSM_CFG_KEYWRAP`), mirroring the Key wrap/unwrap API: + +- **`wh_Client_CertWrap`** / **`wh_Client_CertWrapRequest`** / + **`wh_Client_CertWrapResponse`** — Wrap a certificate. Accepts a + caller-provided `whNvmMetadata*` (with `id`, `flags`, `access`, and + optionally `label` set by the caller), sets `meta->len = certSz`, then + delegates to the corresponding `wh_Client_KeyWrap*` function. The metadata's + `id` field must have `TYPE=WH_KEYTYPE_WRAPPED` encoded via + `WH_CLIENT_KEYID_MAKE_WRAPPED_META`. + +- **`wh_Client_CertUnwrapAndExport`** / **`wh_Client_CertUnwrapAndExportRequest`** / + **`wh_Client_CertUnwrapAndExportResponse`** — Unwrap a wrapped certificate + and export both the plaintext certificate and its metadata back to the client. + Delegates to the corresponding `wh_Client_KeyUnwrapAndExport*` function. + +- **`wh_Client_CertUnwrapAndCache`** / **`wh_Client_CertUnwrapAndCacheRequest`** / + **`wh_Client_CertUnwrapAndCacheResponse`** — Unwrap and cache on the server. + Delegates to the corresponding `wh_Client_KeyUnwrapAndCache*` function. + Returns the server-assigned cache slot ID in `*out_certId`. + +All functions accept an `enum wc_CipherType cipherType` parameter (e.g. +`WC_CIPHER_AES_GCM`) to specify the wrapping cipher. The blocking variants +call their respective Request/Response functions in a do-while-NOTREADY loop. + +These are pure convenience; a caller could use `wh_Client_KeyWrap*` / +`wh_Client_KeyUnwrapAndExport*` / `wh_Client_KeyUnwrapAndCache*` directly if +it needed custom metadata. + +### Server-side routing (the key change) + +#### `wh_Server_CertReadTrusted` (`src/wh_server_cert.c`) + +Previously accepted only `whNvmId` and always read from NVM. Now accepts +`whKeyId` and branches on the TYPE field: + +``` +if WH_KEYID_TYPE(id) == WH_KEYTYPE_WRAPPED + → wh_Server_KeystoreReadKey(server, id, &meta, cert, &sz) // cache path +else + → wh_Nvm_GetMetadata / wh_Nvm_Read // NVM path (unchanged) +``` + +`wh_Server_KeystoreReadKey` looks up the key in the server's `localCache` (or +`globalCache` if global keys are enabled and the USER field is 0). It copies +both the metadata and the raw data into the caller's buffers. + +#### `wh_Server_CertVerify` / `wh_Server_CertVerifyAcert` + +Signature changed from `whNvmId trustedRootNvmId` to `whKeyId trustedRootId`. +Internally they just call `wh_Server_CertReadTrusted`, which now handles the +routing. + +#### Request handlers in `wh_Server_HandleCertRequest` + +Every handler that accepts a trusted root ID (`READTRUSTED`, `VERIFY`, +`READTRUSTED_DMA`, `VERIFY_DMA`, `VERIFY_ACERT`, `VERIFY_ACERT_DMA`) was +updated with the same pattern: + +1. **Translate the client ID**: If the incoming `req.id` (or + `req.trustedRootNvmId`) has `WH_KEYID_CLIENT_WRAPPED_FLAG` set, call + `wh_KeyId_TranslateFromClient(WH_KEYTYPE_NVM, server->comm->client_id, req.id)` + to produce a full server-internal key ID with `TYPE=WH_KEYTYPE_WRAPPED`, + `USER=client_id`, and the bare key `ID` in the low byte. + +2. **Branch on key type** for the read/verify: + - **Cache path** (`WH_KEYID_TYPE(certId) == WH_KEYTYPE_WRAPPED`): Calls + `wh_Server_KeystoreReadKey` to fetch the cert from the cache. Checks + `WH_NVM_FLAGS_NONEXPORTABLE` on the metadata for read-back requests. + - **NVM path** (original, `WH_KEYID_TYPE != WH_KEYTYPE_WRAPPED`): Unchanged + behavior — reads from flash via `wh_Nvm_GetMetadata` / `wh_Nvm_Read`. + +### Key ID encoding walkthrough + +Consider a client with `client_id = 1` wrapping a cert with bare ID `5`: + +| Stage | Value | Encoding | +|---|---|---| +| `WH_CLIENT_KEYID_MAKE_WRAPPED_META(1, 5)` | `0x4105` | TYPE=4 (WRAPPED), USER=1, ID=5 — stored *inside* the wrapped blob metadata | +| Server returns `cachedCertId` after unwrap | `0x4105` | Same — the server preserved the metadata ID | +| Client sends `WH_CLIENT_KEYID_MAKE_WRAPPED(0x4105)` | `0x4305` | Bit 9 (0x0200) set as client flag | +| Server calls `wh_KeyId_TranslateFromClient(...)` | `0x4105` | Flag stripped, TYPE=WRAPPED confirmed, USER=1, ID=5 | +| `WH_KEYID_TYPE(0x4105)` | `4` | Equals `WH_KEYTYPE_WRAPPED` (4) → routes to cache | + +### Data stored at each point + +| Point in flow | Server key cache | Server NVM | Client memory | +|---|---|---|---| +| After `KeyCache` (KEK) | KEK at `kekId` | — | — | +| After `CertWrap` | KEK at `kekId` | — | Wrapped blob (ciphertext + tag + IV + metadata) | +| After `CertUnwrapAndCache` | KEK at `kekId`, plaintext cert at `cachedCertId` | — | Wrapped blob (unchanged) | +| During `CertVerify` | KEK, plaintext cert (read into stack buffer `root_cert[WOLFHSM_CFG_MAX_CERT_SIZE]` by `CertReadTrusted`) | — | — | +| After `KeyEvict` (cert) | KEK at `kekId` | — | Wrapped blob | +| After `KeyEvict` (KEK) | — | — | Wrapped blob | + +## Interaction with Locking and Thread Safety + +### The NVM lock (`WH_SERVER_NVM_LOCK` / `WH_SERVER_NVM_UNLOCK`) + +When `WOLFHSM_CFG_THREADSAFE` is defined, `WH_SERVER_NVM_LOCK(server)` calls +`wh_Server_NvmLock(server)`, which acquires a mutex protecting NVM state. When +not threadsafe, the macros expand to `(WH_ERROR_OK)` (no-ops). + +The existing (pre-branch) code unconditionally called `WH_SERVER_NVM_LOCK` +around every cert read/verify handler, because the cert always came from NVM. + +### What changes for cached certs + +Cached certs do not touch NVM at all — they are read from the in-memory key +cache via `wh_Server_KeystoreReadKey`. However, the NVM lock is still +unconditionally acquired around both cache and NVM paths. This is conservative +but correct: the key cache (`localCache` / `globalCache`) does not have its own +lock, so the NVM lock serves as the coarse serialization mechanism for all +server-side storage operations (both NVM and cache) when +`WOLFHSM_CFG_THREADSAFE` is enabled. + +The pattern used in every updated handler is: + +```c +rc = WH_SERVER_NVM_LOCK(server); +if (rc == WH_ERROR_OK) { + if (req.id & WH_KEYID_CLIENT_WRAPPED_FLAG) { + /* Cache path: translate and read from keystore cache */ + whKeyId certId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, req.id); + rc = wh_Server_KeystoreReadKey(server, certId, &meta, cert_data, &cert_len); + /* ... exportability check for read-back requests ... */ + } else { + /* NVM path (unchanged) */ + rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta); + /* ... NVM reads ... */ + } + (void)WH_SERVER_NVM_UNLOCK(server); +} +``` + +Key points: + +- **Both paths hold the NVM lock**: The lock is always acquired before + branching. While the cache read itself doesn't strictly need NVM protection, + holding the lock ensures serialization with any concurrent operations that + access the `localCache` array on other threads. + +- **NVM path**: Unchanged — same behavior as before this branch. + +### Backward compatibility + +- All existing NVM-based certificate operations continue to work identically. + The routing branch only activates when the key type is `WH_KEYTYPE_WRAPPED`. +- The `wh_Server_CertReadTrusted` and `wh_Server_CertVerify` function + signatures changed from `whNvmId` to `whKeyId`. Since `whNvmId` and `whKeyId` + are both `uint16_t`, this is ABI-compatible. Any existing callers passing a + plain NVM ID (with TYPE=0) will hit the NVM path as before. diff --git a/src/wh_client_cert.c b/src/wh_client_cert.c index d233da5e8..d4168c0a7 100644 --- a/src/wh_client_cert.c +++ b/src/wh_client_cert.c @@ -958,6 +958,170 @@ int wh_Client_CertVerifyAcertDma(whClientContext* c, const void* cert, #endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT */ +#ifdef WOLFHSM_CFG_KEYWRAP + +int wh_Client_CertWrapRequest(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, const uint8_t* cert, + uint32_t certSz, whNvmMetadata* meta) +{ + if ((ctx == NULL) || (cert == NULL) || (certSz == 0) || (meta == NULL) || + (certSz > UINT16_MAX)) { + return WH_ERROR_BADARGS; + } + + meta->len = certSz; + + return wh_Client_KeyWrapRequest(ctx, cipherType, serverKeyId, (void*)cert, + (uint16_t)certSz, meta); +} + +int wh_Client_CertWrapResponse(whClientContext* ctx, + enum wc_CipherType cipherType, + uint8_t* wrappedOut, uint16_t* inout_wrappedSz) +{ + if ((ctx == NULL) || (wrappedOut == NULL) || (inout_wrappedSz == NULL)) { + return WH_ERROR_BADARGS; + } + + return wh_Client_KeyWrapResponse(ctx, cipherType, wrappedOut, + inout_wrappedSz); +} + +int wh_Client_CertWrap(whClientContext* ctx, enum wc_CipherType cipherType, + whKeyId serverKeyId, const uint8_t* cert, + uint32_t certSz, whNvmMetadata* meta, + uint8_t* wrappedOut, uint16_t* inout_wrappedSz) +{ + int rc; + + if ((ctx == NULL) || (cert == NULL) || (certSz == 0) || (meta == NULL) || + (wrappedOut == NULL) || (inout_wrappedSz == NULL) || + (certSz > UINT16_MAX)) { + return WH_ERROR_BADARGS; + } + + rc = wh_Client_CertWrapRequest(ctx, cipherType, serverKeyId, cert, certSz, + meta); + if (rc != WH_ERROR_OK) { + return rc; + } + + do { + rc = wh_Client_CertWrapResponse(ctx, cipherType, wrappedOut, + inout_wrappedSz); + } while (rc == WH_ERROR_NOTREADY); + + return rc; +} + +int wh_Client_CertUnwrapAndExportRequest(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, + const uint8_t* wrappedCert, + uint16_t wrappedCertSz) +{ + if ((ctx == NULL) || (wrappedCert == NULL) || (wrappedCertSz == 0)) { + return WH_ERROR_BADARGS; + } + + return wh_Client_KeyUnwrapAndExportRequest( + ctx, cipherType, serverKeyId, (void*)wrappedCert, wrappedCertSz); +} + +int wh_Client_CertUnwrapAndExportResponse(whClientContext* ctx, + enum wc_CipherType cipherType, + whNvmMetadata* metadataOut, + uint8_t* certOut, + uint16_t* inout_certSz) +{ + if ((ctx == NULL) || (certOut == NULL) || (inout_certSz == NULL)) { + return WH_ERROR_BADARGS; + } + + return wh_Client_KeyUnwrapAndExportResponse(ctx, cipherType, metadataOut, + certOut, inout_certSz); +} + +int wh_Client_CertUnwrapAndExport( + whClientContext* ctx, enum wc_CipherType cipherType, whKeyId serverKeyId, + const uint8_t* wrappedCert, uint16_t wrappedCertSz, + whNvmMetadata* metadataOut, uint8_t* certOut, uint16_t* inout_certSz) +{ + int rc; + + if ((ctx == NULL) || (wrappedCert == NULL) || (wrappedCertSz == 0) || + (certOut == NULL) || (inout_certSz == NULL)) { + return WH_ERROR_BADARGS; + } + + rc = wh_Client_CertUnwrapAndExportRequest(ctx, cipherType, serverKeyId, + wrappedCert, wrappedCertSz); + if (rc != WH_ERROR_OK) { + return rc; + } + + do { + rc = wh_Client_CertUnwrapAndExportResponse(ctx, cipherType, metadataOut, + certOut, inout_certSz); + } while (rc == WH_ERROR_NOTREADY); + + return rc; +} + +int wh_Client_CertUnwrapAndCacheRequest(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, + const uint8_t* wrappedCert, + uint16_t wrappedCertSz) +{ + if ((ctx == NULL) || (wrappedCert == NULL) || (wrappedCertSz == 0)) { + return WH_ERROR_BADARGS; + } + + return wh_Client_KeyUnwrapAndCacheRequest( + ctx, cipherType, serverKeyId, (void*)wrappedCert, wrappedCertSz); +} + +int wh_Client_CertUnwrapAndCacheResponse(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId* out_certId) +{ + if ((ctx == NULL) || (out_certId == NULL)) { + return WH_ERROR_BADARGS; + } + + return wh_Client_KeyUnwrapAndCacheResponse(ctx, cipherType, out_certId); +} + +int wh_Client_CertUnwrapAndCache(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, + const uint8_t* wrappedCert, + uint16_t wrappedCertSz, whKeyId* out_certId) +{ + int rc; + + if ((ctx == NULL) || (wrappedCert == NULL) || (wrappedCertSz == 0) || + (out_certId == NULL)) { + return WH_ERROR_BADARGS; + } + + rc = wh_Client_CertUnwrapAndCacheRequest(ctx, cipherType, serverKeyId, + wrappedCert, wrappedCertSz); + if (rc != WH_ERROR_OK) { + return rc; + } + + do { + rc = wh_Client_CertUnwrapAndCacheResponse(ctx, cipherType, out_certId); + } while (rc == WH_ERROR_NOTREADY); + + return rc; +} + +#endif /* WOLFHSM_CFG_KEYWRAP */ + #endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */ #endif /* WOLFHSM_CFG_ENABLE_CLIENT */ diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c index 5cc9b77e3..830338aab 100644 --- a/src/wh_server_cert.c +++ b/src/wh_server_cert.c @@ -226,8 +226,8 @@ int wh_Server_CertEraseTrusted(whServerContext* server, whNvmId id) return rc; } -/* Get a trusted certificate from NVM storage */ -int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, +/* Get a trusted certificate from NVM storage or keystore cache */ +int wh_Server_CertReadTrusted(whServerContext* server, whKeyId id, uint8_t* cert, uint32_t* inout_cert_len) { int rc; @@ -238,6 +238,15 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, return WH_ERROR_BADARGS; } + /* Cache path: wrapped certs are read from keystore cache, not NVM */ + if (WH_KEYID_TYPE(id) == WH_KEYTYPE_WRAPPED) { + uint32_t sz = *inout_cert_len; + rc = wh_Server_KeystoreReadKey(server, id, &meta, cert, &sz); + if (rc == WH_ERROR_OK) { + *inout_cert_len = sz; + } + return rc; + } /* Get metadata to check the certificate size */ rc = wh_Nvm_GetMetadata(server->nvm, id, &meta); @@ -259,7 +268,7 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, /* Verify a certificate against trusted certificates */ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, - uint32_t cert_len, whNvmId trustedRootNvmId, + uint32_t cert_len, whKeyId trustedRootId, whCertFlags flags, whNvmFlags cachedKeyFlags, whKeyId* inout_keyId) { @@ -286,8 +295,8 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, return WH_ERROR_ABORTED; } - /* Get the trusted root certificate */ - rc = wh_Server_CertReadTrusted(server, trustedRootNvmId, root_cert, + /* Get the trusted root certificate (from NVM or cache based on id type) */ + rc = wh_Server_CertReadTrusted(server, trustedRootId, root_cert, &root_cert_len); if (rc == WH_ERROR_OK) { /* Load the trusted root certificate */ @@ -314,7 +323,7 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT) int wh_Server_CertVerifyAcert(whServerContext* server, const uint8_t* cert, - uint32_t cert_len, whNvmId trustedRootNvmId) + uint32_t cert_len, whKeyId trustedRootId) { int rc; @@ -322,8 +331,8 @@ int wh_Server_CertVerifyAcert(whServerContext* server, const uint8_t* cert, uint8_t root_cert[WOLFHSM_CFG_MAX_CERT_SIZE]; uint32_t root_cert_len = sizeof(root_cert); - /* Load the trusted root certificate into the buffer */ - rc = wh_Server_CertReadTrusted(server, trustedRootNvmId, root_cert, + /* Load the trusted root certificate into the buffer (NVM or cache) */ + rc = wh_Server_CertReadTrusted(server, trustedRootId, root_cert, &root_cert_len); if (rc != WH_ERROR_OK) { return rc; @@ -473,21 +482,35 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, ? max_transport_cert_len : WOLFHSM_CFG_MAX_CERT_SIZE; - /* Check metadata to check if the certificate is non-exportable. - * This is unfortunately redundant since metadata is checked in - * wh_Server_CertReadTrusted(). */ rc = WH_SERVER_NVM_LOCK(server); if (rc == WH_ERROR_OK) { - rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta); - if (rc == WH_ERROR_OK) { - /* Check if the certificate is non-exportable */ - if (meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) { - rc = WH_ERROR_ACCESS; + if (req.id & WH_KEYID_CLIENT_WRAPPED_FLAG) { + /* Cache path: translate and read cert + metadata */ + whKeyId certId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, req.id); + rc = wh_Server_KeystoreReadKey(server, certId, &meta, + cert_data, &cert_len); + if (rc == WH_ERROR_OK) { + if (meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) { + rc = WH_ERROR_ACCESS; + } + else { + resp.cert_len = cert_len; + } } - else { - rc = wh_Server_CertReadTrusted(server, req.id, - cert_data, &cert_len); - resp.cert_len = cert_len; + } + else { + /* NVM path */ + rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta); + if (rc == WH_ERROR_OK) { + if (meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) { + rc = WH_ERROR_ACCESS; + } + else { + rc = wh_Server_CertReadTrusted( + server, req.id, cert_data, &cert_len); + resp.cert_len = cert_len; + } } } @@ -522,12 +545,19 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, whKeyId keyId = wh_KeyId_TranslateFromClient( WH_KEYTYPE_CRYPTO, server->comm->client_id, req.keyId); + /* Translate trustedRootNvmId if client set wrapped flag */ + whKeyId trustedRootId = req.trustedRootNvmId; + if (req.trustedRootNvmId & WH_KEYID_CLIENT_WRAPPED_FLAG) { + trustedRootId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, + req.trustedRootNvmId); + } rc = WH_SERVER_NVM_LOCK(server); if (rc == WH_ERROR_OK) { /* Process the verify action */ rc = wh_Server_CertVerify(server, cert_data, req.cert_len, - req.trustedRootNvmId, req.flags, + trustedRootId, req.flags, req.cachedKeyFlags, &keyId); /* Propagate the keyId back to the client with flags * preserved */ @@ -612,19 +642,37 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); } if (resp.rc == WH_ERROR_OK) { - /* Check metadata to see if the certificate is non-exportable */ resp.rc = WH_SERVER_NVM_LOCK(server); if (resp.rc == WH_ERROR_OK) { - resp.rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta); - if (resp.rc == WH_ERROR_OK) { - if ((meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) != 0) { - resp.rc = WH_ERROR_ACCESS; + if (req.id & WH_KEYID_CLIENT_WRAPPED_FLAG) { + /* Cache path: translate and read cert + metadata */ + whKeyId certId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, + req.id); + cert_len = req.cert_len; + resp.rc = wh_Server_KeystoreReadKey( + server, certId, &meta, cert_data, &cert_len); + if (resp.rc == WH_ERROR_OK) { + if ((meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) != + 0) { + resp.rc = WH_ERROR_ACCESS; + } } - else { - /* Clamp cert_len to actual stored length */ - cert_len = req.cert_len; - resp.rc = wh_Server_CertReadTrusted( - server, req.id, cert_data, &cert_len); + } + else { + /* NVM path */ + resp.rc = + wh_Nvm_GetMetadata(server->nvm, req.id, &meta); + if (resp.rc == WH_ERROR_OK) { + if ((meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) != + 0) { + resp.rc = WH_ERROR_ACCESS; + } + else { + cert_len = req.cert_len; + resp.rc = wh_Server_CertReadTrusted( + server, req.id, cert_data, &cert_len); + } } } @@ -645,10 +693,11 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, }; break; case WH_MESSAGE_CERT_ACTION_VERIFY_DMA: { - whMessageCert_VerifyDmaRequest req = {0}; - whMessageCert_VerifyDmaResponse resp = {0}; - void* cert_data = NULL; - whKeyId keyId = WH_KEYID_ERASED; + whMessageCert_VerifyDmaRequest req = {0}; + whMessageCert_VerifyDmaResponse resp = {0}; + void* cert_data = NULL; + whKeyId keyId = WH_KEYID_ERASED; + whKeyId trustedRootId = 0; if (req_size != sizeof(req)) { /* Request is malformed */ @@ -663,6 +712,14 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, keyId = wh_KeyId_TranslateFromClient( WH_KEYTYPE_CRYPTO, server->comm->client_id, req.keyId); + /* Translate trustedRootNvmId if client set wrapped flag */ + trustedRootId = req.trustedRootNvmId; + if (req.trustedRootNvmId & WH_KEYID_CLIENT_WRAPPED_FLAG) { + trustedRootId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, + req.trustedRootNvmId); + } + /* Process client address */ resp.rc = wh_Server_DmaProcessClientAddress( server, req.cert_addr, &cert_data, req.cert_len, @@ -673,7 +730,7 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, if (resp.rc == WH_ERROR_OK) { /* Process the verify action */ resp.rc = wh_Server_CertVerify( - server, cert_data, req.cert_len, req.trustedRootNvmId, + server, cert_data, req.cert_len, trustedRootId, req.flags, req.cachedKeyFlags, &keyId); /* Propagate the keyId back to the client with flags @@ -709,11 +766,18 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, cert_data = (const uint8_t*)req_packet + sizeof(req); - /* Process the verify action */ + /* Translate trustedRootNvmId if client set wrapped flag */ + whKeyId trustedRootId = req.trustedRootNvmId; + if (req.trustedRootNvmId & WH_KEYID_CLIENT_WRAPPED_FLAG) { + trustedRootId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, + req.trustedRootNvmId); + } + rc = WH_SERVER_NVM_LOCK(server); if (rc == WH_ERROR_OK) { rc = wh_Server_CertVerifyAcert(server, cert_data, req.cert_len, - req.trustedRootNvmId); + trustedRootId); (void)WH_SERVER_NVM_UNLOCK(server); } /* WH_SERVER_NVM_LOCK() */ @@ -738,9 +802,10 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, #if defined(WOLFHSM_CFG_DMA) case WH_MESSAGE_CERT_ACTION_VERIFY_ACERT_DMA: { /* Acert verify request uses standard cert verify request struct */ - whMessageCert_VerifyDmaRequest req = {0}; - whMessageCert_SimpleResponse resp = {0}; - void* cert_data = NULL; + whMessageCert_VerifyDmaRequest req = {0}; + whMessageCert_SimpleResponse resp = {0}; + void* cert_data = NULL; + whKeyId trustedRootId = 0; if (req_size != sizeof(req)) { /* Request is malformed */ @@ -751,17 +816,24 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, wh_MessageCert_TranslateVerifyDmaRequest( magic, (whMessageCert_VerifyDmaRequest*)req_packet, &req); + /* Translate trustedRootNvmId if client set wrapped flag */ + trustedRootId = req.trustedRootNvmId; + if (req.trustedRootNvmId & WH_KEYID_CLIENT_WRAPPED_FLAG) { + trustedRootId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_WRAPPED, server->comm->client_id, + req.trustedRootNvmId); + } + /* Process client address */ rc = wh_Server_DmaProcessClientAddress( server, req.cert_addr, &cert_data, req.cert_len, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); } if (rc == WH_ERROR_OK) { - /* Process the verify action */ rc = WH_SERVER_NVM_LOCK(server); if (rc == WH_ERROR_OK) { - rc = wh_Server_CertVerifyAcert( - server, cert_data, req.cert_len, req.trustedRootNvmId); + rc = wh_Server_CertVerifyAcert(server, cert_data, + req.cert_len, trustedRootId); (void)WH_SERVER_NVM_UNLOCK(server); } /* WH_SERVER_NVM_LOCK() */ diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c index ede3a0b4a..32b728f6f 100644 --- a/test/wh_test_cert.c +++ b/test/wh_test_cert.c @@ -51,6 +51,13 @@ #ifdef WOLFHSM_CFG_ENABLE_CLIENT static int whTest_CertNonExportable(whClientContext* client); +#ifdef WOLFHSM_CFG_KEYWRAP +static int whTest_CertCacheVerify(whClientContext* client); +static int whTest_CertCacheReadTrusted(whClientContext* client); +#if defined(WOLFHSM_CFG_DMA) +static int whTest_CertCacheVerifyDma(whClientContext* client); +#endif +#endif #endif #define FLASH_RAM_SIZE (1024 * 1024) /* 1MB */ @@ -259,6 +266,13 @@ int whTest_CertClient(whClientContext* client) /* Test non-exportable flag enforcement */ WH_TEST_RETURN_ON_FAIL(whTest_CertNonExportable(client)); +#ifdef WOLFHSM_CFG_KEYWRAP + /* Test cache-based cert verification (wrapped cert in cache instead of + * NVM) */ + WH_TEST_RETURN_ON_FAIL(whTest_CertCacheVerify(client)); + WH_TEST_RETURN_ON_FAIL(whTest_CertCacheReadTrusted(client)); +#endif + WH_TEST_PRINT("Certificate client test completed successfully\n"); return rc; @@ -442,6 +456,11 @@ int whTest_CertClientDma_ClientServerTestInternal(whClientContext* client) wh_Client_CertEraseTrusted(client, rootCertB_id, &out_rc)); WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); +#ifdef WOLFHSM_CFG_KEYWRAP + /* Test DMA verify and ReadTrusted with wrapped/cached root cert */ + WH_TEST_RETURN_ON_FAIL(whTest_CertCacheVerifyDma(client)); +#endif + WH_TEST_PRINT("Certificate client DMA test completed successfully\n"); return rc; @@ -513,6 +532,225 @@ int whTest_CertClientAcertDma_ClientServerTestInternal(whClientContext* client) #endif /* WOLFHSM_CFG_DMA */ +#if defined(WOLFHSM_CFG_DMA) && defined(WOLFHSM_CFG_KEYWRAP) +/* Test DMA verify and DMA ReadTrusted with a wrapped/cached root cert. This + * exercises the conditional-locking logic for wrapped certs in the DMA + * handlers. */ +static int whTest_CertCacheVerifyDma(whClientContext* client) +{ + int rc = WH_ERROR_OK; + int32_t out_rc = 0; + whKeyId kekId = 12; + uint8_t wrappedCert[2048]; + uint16_t wrappedCertSz = sizeof(wrappedCert); + whKeyId cachedCertId = WH_KEYID_ERASED; + uint8_t readBackCert[2048]; + + /* AES-256 key for wrapping */ + uint8_t kek[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + + WH_TEST_PRINT("Starting DMA cache-based cert verification test...\n"); + + /* Provision the wrapping key (KEK) on the server (non-DMA, no DMA wrap + * variant exists) */ + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCache( + client, WH_NVM_FLAGS_USAGE_WRAP, NULL, 0, kek, sizeof(kek), &kekId)); + + /* Wrap the root CA cert */ + whNvmMetadata certMeta = {0}; + certMeta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, 7); + certMeta.flags = WH_NVM_FLAGS_USAGE_ANY; + certMeta.access = WH_NVM_ACCESS_ANY; + WH_TEST_RETURN_ON_FAIL(wh_Client_CertWrap( + client, WC_CIPHER_AES_GCM, kekId, ROOT_A_CERT, ROOT_A_CERT_len, + &certMeta, wrappedCert, &wrappedCertSz)); + + /* Unwrap and cache the root cert on the server */ + WH_TEST_RETURN_ON_FAIL(wh_Client_CertUnwrapAndCache( + client, WC_CIPHER_AES_GCM, kekId, wrappedCert, wrappedCertSz, + &cachedCertId)); + + /* DMA Verify intermediate cert against the cached root cert */ + WH_TEST_PRINT("DMA verifying intermediate cert against cached root...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma( + client, INTERMEDIATE_A_CERT, INTERMEDIATE_A_CERT_len, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* DMA Verify a full chain against the cached root cert */ + WH_TEST_PRINT("DMA verifying certificate chain against cached root...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* DMA Verify that a mismatched chain fails against the cached root */ + WH_TEST_PRINT("DMA verifying mismatched chain against cached root (should " + "fail)...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* DMA ReadTrusted of cached cert */ + WH_TEST_PRINT("DMA reading back cached cert via CertReadTrustedDma...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertReadTrustedDma( + client, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), readBackCert, + sizeof(readBackCert), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(0 == + memcmp(readBackCert, ROOT_A_CERT, ROOT_A_CERT_len)); + + /* Cleanup: evict cached cert and KEK */ + WH_TEST_RETURN_ON_FAIL( + wh_Client_KeyEvict(client, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId))); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, kekId)); + + WH_TEST_PRINT( + "DMA cache-based cert verification test completed successfully\n"); + return rc; +} +#endif /* WOLFHSM_CFG_DMA && WOLFHSM_CFG_KEYWRAP */ + +#ifdef WOLFHSM_CFG_KEYWRAP +/* Test certificate verification using a wrapped/cached root cert instead of + * NVM. This validates the cache-aware path in wh_Server_CertReadTrusted and + * the conditional NVM locking in the handlers. */ +static int whTest_CertCacheVerify(whClientContext* client) +{ + int rc = WH_ERROR_OK; + int32_t out_rc = 0; + whKeyId kekId = 10; + uint8_t wrappedCert[2048]; + uint16_t wrappedCertSz = sizeof(wrappedCert); + whKeyId cachedCertId = WH_KEYID_ERASED; + + /* AES-256 key for wrapping */ + uint8_t kek[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + + WH_TEST_PRINT("Starting cache-based cert verification test...\n"); + + /* Provision the wrapping key (KEK) on the server */ + WH_TEST_PRINT("Provisioning wrapping key on server...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCache( + client, WH_NVM_FLAGS_USAGE_WRAP, NULL, 0, kek, sizeof(kek), &kekId)); + + /* Wrap the root CA cert for client-side storage. + * certMeta.id must have WH_KEYTYPE_WRAPPED set in metadata. */ + whNvmMetadata certMeta = {0}; + certMeta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, 5); + certMeta.flags = WH_NVM_FLAGS_USAGE_ANY; + certMeta.access = WH_NVM_ACCESS_ANY; + WH_TEST_PRINT("Wrapping root certificate...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertWrap( + client, WC_CIPHER_AES_GCM, kekId, ROOT_A_CERT, ROOT_A_CERT_len, + &certMeta, wrappedCert, &wrappedCertSz)); + + /* Unwrap and cache the root cert on the server */ + WH_TEST_PRINT("Unwrapping and caching root certificate on server...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertUnwrapAndCache( + client, WC_CIPHER_AES_GCM, kekId, wrappedCert, wrappedCertSz, + &cachedCertId)); + + /* Verify intermediate cert against the cached root cert. + * Must mark the cachedCertId as wrapped so the server routes to cache */ + WH_TEST_PRINT("Verifying intermediate cert against cached root...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify( + client, INTERMEDIATE_A_CERT, INTERMEDIATE_A_CERT_len, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* Verify a full chain against the cached root cert */ + WH_TEST_PRINT("Verifying certificate chain against cached root...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* Verify that a mismatched chain fails against the cached root */ + WH_TEST_PRINT( + "Verifying mismatched chain against cached root (should fail)...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, + WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* Cleanup: evict cached cert and KEK */ + WH_TEST_PRINT("Cleaning up cached cert and KEK...\n"); + WH_TEST_RETURN_ON_FAIL( + wh_Client_KeyEvict(client, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId))); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, kekId)); + + WH_TEST_PRINT( + "Cache-based cert verification test completed successfully\n"); + return rc; +} + +/* Test reading back a wrapped/cached cert via CertReadTrusted. This exercises + * the cache path in the READTRUSTED handler. */ +static int whTest_CertCacheReadTrusted(whClientContext* client) +{ + int rc = WH_ERROR_OK; + int32_t out_rc = 0; + whKeyId kekId = 11; + uint8_t wrappedCert[2048]; + uint16_t wrappedCertSz = sizeof(wrappedCert); + whKeyId cachedCertId = WH_KEYID_ERASED; + uint8_t readBackCert[2048]; + uint32_t readBackCertLen = sizeof(readBackCert); + + /* AES-256 key for wrapping */ + uint8_t kek[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + + WH_TEST_PRINT("Starting cache-based cert ReadTrusted test...\n"); + + /* Provision the wrapping key (KEK) on the server */ + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCache( + client, WH_NVM_FLAGS_USAGE_WRAP, NULL, 0, kek, sizeof(kek), &kekId)); + + /* Wrap the root CA cert */ + whNvmMetadata certMeta = {0}; + certMeta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, 6); + certMeta.flags = WH_NVM_FLAGS_USAGE_ANY; + certMeta.access = WH_NVM_ACCESS_ANY; + WH_TEST_RETURN_ON_FAIL(wh_Client_CertWrap( + client, WC_CIPHER_AES_GCM, kekId, ROOT_A_CERT, ROOT_A_CERT_len, + &certMeta, wrappedCert, &wrappedCertSz)); + + /* Unwrap and cache the root cert on the server */ + WH_TEST_RETURN_ON_FAIL(wh_Client_CertUnwrapAndCache( + client, WC_CIPHER_AES_GCM, kekId, wrappedCert, wrappedCertSz, + &cachedCertId)); + + /* Read back the cached cert via CertReadTrusted */ + WH_TEST_PRINT("Reading back cached cert via CertReadTrusted...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertReadTrusted( + client, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId), readBackCert, + &readBackCertLen, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(readBackCertLen == ROOT_A_CERT_len); + WH_TEST_ASSERT_RETURN(0 == + memcmp(readBackCert, ROOT_A_CERT, ROOT_A_CERT_len)); + + /* Cleanup: evict cached cert and KEK */ + WH_TEST_RETURN_ON_FAIL( + wh_Client_KeyEvict(client, WH_CLIENT_KEYID_MAKE_WRAPPED(cachedCertId))); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, kekId)); + + WH_TEST_PRINT("Cache-based cert ReadTrusted test completed successfully\n"); + return rc; +} +#endif /* WOLFHSM_CFG_KEYWRAP */ + static int whTest_CertNonExportable(whClientContext* client) { int rc = WH_ERROR_OK; diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index 5fcac92fd..4663a2d5c 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -2557,6 +2557,201 @@ int wh_Client_CertVerifyAcertDma(whClientContext* c, const void* cert, #endif /* WOLFHSM_CFG_DMA */ +#ifdef WOLFHSM_CFG_KEYWRAP + +/** + * @brief Wrap a certificate for client-side storage + * + * Convenience wrapper around wh_Client_KeyWrap that sets up the appropriate + * metadata for wrapping a certificate. The wrapped blob can be stored + * client-side and later unwrapped/cached on the server for verification. + * This function will block until the entire operation completes or an error + * occurs. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used to wrap the certificate. + * @param[in] serverKeyId Key ID of the wrapping key (KEK) on the server. + * @param[in] cert Pointer to the certificate data to wrap. + * @param[in] certSz Size of the certificate data in bytes. + * @param[in] meta Pointer to caller-populated NVM metadata. Caller must set + * id (use WH_CLIENT_KEYID_MAKE_WRAPPED_META), flags, access, and optionally + * label. The len field is set internally from certSz. + * @param[out] wrappedOut Buffer to store the wrapped certificate blob. + * @param[in,out] inout_wrappedSz IN: size of wrappedOut buffer. + * OUT: actual size of the wrapped blob. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertWrap(whClientContext* ctx, enum wc_CipherType cipherType, + whKeyId serverKeyId, const uint8_t* cert, + uint32_t certSz, whNvmMetadata* meta, + uint8_t* wrappedOut, uint16_t* inout_wrappedSz); + +/** + * @brief Sends a certificate wrap request to the server + * + * This function prepares and sends a certificate wrap request to the server. + * This function does not block; it returns immediately after sending the + * request. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used to wrap the certificate. + * @param[in] serverKeyId Key ID of the wrapping key (KEK) on the server. + * @param[in] cert Pointer to the certificate data to wrap. + * @param[in] certSz Size of the certificate data in bytes. + * @param[in] meta Pointer to caller-populated NVM metadata. Caller must set + * id (use WH_CLIENT_KEYID_MAKE_WRAPPED_META), flags, access, and optionally + * label. The len field is set internally from certSz. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertWrapRequest(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, const uint8_t* cert, + uint32_t certSz, whNvmMetadata* meta); + +/** + * @brief Receives a certificate wrap response from the server + * + * This function attempts to process a certificate wrap response message from + * the server. This function does not block; it returns WH_ERROR_NOTREADY if a + * response has not been received. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used to wrap the certificate. + * @param[out] wrappedOut Buffer to store the wrapped certificate blob. + * @param[in,out] inout_wrappedSz IN: size of wrappedOut buffer. + * OUT: actual size of the wrapped blob. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertWrapResponse(whClientContext* ctx, + enum wc_CipherType cipherType, + uint8_t* wrappedOut, uint16_t* inout_wrappedSz); + +/** + * @brief Unwrap a wrapped certificate and export it to the client + * + * Convenience wrapper around wh_Client_KeyUnwrapAndExport. This function will + * block until the entire operation completes or an error occurs. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used when unwrapping the certificate. + * @param[in] serverKeyId Key ID of the wrapping key (KEK) on the server. + * @param[in] wrappedCert Pointer to the wrapped certificate blob. + * @param[in] wrappedCertSz Size of the wrapped certificate blob in bytes. + * @param[out] metadataOut Pointer to store the unwrapped certificate metadata. + * @param[out] certOut Pointer to store the unwrapped certificate. + * @param[in,out] inout_certSz IN: size of certOut buffer. + * OUT: actual size of the unwrapped certificate. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertUnwrapAndExport( + whClientContext* ctx, enum wc_CipherType cipherType, whKeyId serverKeyId, + const uint8_t* wrappedCert, uint16_t wrappedCertSz, + whNvmMetadata* metadataOut, uint8_t* certOut, uint16_t* inout_certSz); + +/** + * @brief Sends a certificate unwrap-and-export request to the server + * + * This function prepares and sends a certificate unwrap-and-export request to + * the server. This function does not block; it returns immediately after + * sending the request. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used when unwrapping the certificate. + * @param[in] serverKeyId Key ID of the wrapping key (KEK) on the server. + * @param[in] wrappedCert Pointer to the wrapped certificate blob. + * @param[in] wrappedCertSz Size of the wrapped certificate blob in bytes. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertUnwrapAndExportRequest(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, + const uint8_t* wrappedCert, + uint16_t wrappedCertSz); + +/** + * @brief Receives a certificate unwrap-and-export response from the server + * + * This function attempts to process a certificate unwrap-and-export response + * message from the server. This function does not block; it returns + * WH_ERROR_NOTREADY if a response has not been received. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used when unwrapping the certificate. + * @param[out] metadataOut Pointer to store the unwrapped certificate metadata. + * @param[out] certOut Pointer to store the unwrapped certificate. + * @param[in,out] inout_certSz IN: size of certOut buffer. + * OUT: actual size of the unwrapped certificate. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertUnwrapAndExportResponse(whClientContext* ctx, + enum wc_CipherType cipherType, + whNvmMetadata* metadataOut, + uint8_t* certOut, + uint16_t* inout_certSz); + +/** + * @brief Unwrap a wrapped certificate and cache it on the server + * + * Convenience wrapper around wh_Client_KeyUnwrapAndCache. After this call, + * the certificate is cached on the server and can be used as the trusted root + * in wh_Client_CertVerify by passing WH_CLIENT_KEYID_MAKE_WRAPPED(*out_certId) + * as the trustedRootNvmId parameter. This function will block until the entire + * operation completes or an error occurs. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used when unwrapping the certificate. + * @param[in] serverKeyId Key ID of the wrapping key (KEK) on the server. + * @param[in] wrappedCert Pointer to the wrapped certificate blob. + * @param[in] wrappedCertSz Size of the wrapped certificate blob in bytes. + * @param[out] out_certId Pointer to store the cached cert's key ID. Use + * WH_CLIENT_KEYID_MAKE_WRAPPED(*out_certId) when passing to cert verify. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertUnwrapAndCache(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, + const uint8_t* wrappedCert, + uint16_t wrappedCertSz, whKeyId* out_certId); + +/** + * @brief Sends a certificate unwrap-and-cache request to the server + * + * This function prepares and sends a certificate unwrap-and-cache request to + * the server. This function does not block; it returns immediately after + * sending the request. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used when unwrapping the certificate. + * @param[in] serverKeyId Key ID of the wrapping key (KEK) on the server. + * @param[in] wrappedCert Pointer to the wrapped certificate blob. + * @param[in] wrappedCertSz Size of the wrapped certificate blob in bytes. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertUnwrapAndCacheRequest(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId serverKeyId, + const uint8_t* wrappedCert, + uint16_t wrappedCertSz); + +/** + * @brief Receives a certificate unwrap-and-cache response from the server + * + * This function attempts to process a certificate unwrap-and-cache response + * message from the server. This function does not block; it returns + * WH_ERROR_NOTREADY if a response has not been received. + * + * @param[in] ctx Pointer to the client context. + * @param[in] cipherType Cipher used when unwrapping the certificate. + * @param[out] out_certId Pointer to store the cached cert's key ID. Use + * WH_CLIENT_KEYID_MAKE_WRAPPED(*out_certId) when passing to cert verify. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertUnwrapAndCacheResponse(whClientContext* ctx, + enum wc_CipherType cipherType, + whKeyId* out_certId); + +#endif /* WOLFHSM_CFG_KEYWRAP */ + /** * @brief Mark a key ID as global (shared across all clients) diff --git a/wolfhsm/wh_server_cert.h b/wolfhsm/wh_server_cert.h index 0d0be42b7..f456321f6 100644 --- a/wolfhsm/wh_server_cert.h +++ b/wolfhsm/wh_server_cert.h @@ -30,6 +30,7 @@ #include "wolfhsm/wh_server.h" #include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_keyid.h" /** * @brief Initialize the certificate manager @@ -61,9 +62,12 @@ int wh_Server_CertAddTrusted(whServerContext* server, whNvmId id, int wh_Server_CertEraseTrusted(whServerContext* server, whNvmId id); /** - * @brief Get a trusted certificate from NVM storage + * @brief Get a trusted certificate from NVM storage or keystore cache * @param server The server context - * @param id The NVM ID of the certificate to read + * @param id The key ID of the certificate to read. If the key type is + * WH_KEYTYPE_NVM, the certificate is read from NVM. Otherwise, the certificate + * is read from the keystore cache (e.g. for wrapped certs cached via + * wh_Client_KeyUnwrapAndCache). * @param cert Buffer to store the certificate data * @param inout_cert_len On input, size of cert buffer. On output, actual cert * size @@ -71,7 +75,7 @@ int wh_Server_CertEraseTrusted(whServerContext* server, whNvmId id); * large for the buffer, WH_ERROR_BUFFER_SIZE will be returned and * inout_cert_len will be updated to the actual certificate size. */ -int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, +int wh_Server_CertReadTrusted(whServerContext* server, whKeyId id, uint8_t* cert, uint32_t* inout_cert_len); /** @@ -79,7 +83,9 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, * @param server The server context * @param cert The certificate data to verify * @param cert_len Length of the certificate data - * @param trustedRootNvmId NVM ID of the trusted root certificate + * @param trustedRootId Key ID of the trusted root certificate. Can be an NVM ID + * (WH_KEYTYPE_NVM) or a cached key ID (e.g. WH_KEYTYPE_WRAPPED from + * wh_Client_KeyUnwrapAndCache). * @param flags Flags for the certificate verification (see WH_CERT_FLAGS_* in * wh_common.h) * @param cachedKeyFlags NVM usage flags to apply when caching the leaf public @@ -91,7 +97,7 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, * @return WH_ERROR_OK on success, error code on failure */ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, - uint32_t cert_len, whNvmId trustedRootNvmId, + uint32_t cert_len, whKeyId trustedRootId, whCertFlags flags, whNvmFlags cachedKeyFlags, whKeyId* inout_keyId); @@ -105,12 +111,12 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, * @param[in] server Pointer to the server context * @param[in] cert Pointer to the attribute certificate data to verify * @param[in] cert_len Length of the certificate data in bytes - * @param[in] trustedRootNvmId NVM ID of the trusted root certificate to verify - * against + * @param[in] trustedRootId Key ID of the trusted root certificate to verify + * against. Can be an NVM ID or a cached key ID. * @return int Returns 0 on success, or a negative error code on failure. */ int wh_Server_CertVerifyAcert(whServerContext* server, const uint8_t* cert, - uint32_t cert_len, whNvmId trustedRootNvmId); + uint32_t cert_len, whKeyId trustedRootId); #endif /**