diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 6e06d1d1c..c2b25b809 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -16,6 +16,7 @@ This chapter provides a detailed overview of the high level features that wolfHS - [Object Metadata and Access Attributes](#object-metadata-and-access-attributes) - [NVM Backends](#nvm-backends) - [Flash Abstraction](#flash-abstraction) + - [Optional NVM Backing](#optional-nvm-backing) - [Keystore](#keystore) - [Key Cache, Key IDs, and NVM Backing Store](#key-cache-key-ids-and-nvm-backing-store) - [Global Keys](#global-keys) @@ -236,6 +237,31 @@ wolfHSM ships with two reference flash drivers usable on host platforms and in t Vendor-supplied flash drivers ship with the platform ports under `port//`. New platforms are integrated into wolfHSM by implementing the `whFlashCb` callback set against the device's flash controller; nothing in the NVM library above this layer needs to change. +### Optional NVM Backing + +The NVM subsystem described above is **optional**. A server can be initialized with `whServerConfig.nvm == NULL`, in which case it runs with no persistent object store at all. This suits clients and cores that only need cached-key cryptography and have no flash available for an NVM partition — at the cost of a reduced feature set, since everything that depends on persistent storage becomes unavailable. + +With no NVM, the [keystore](#keystore) is effectively cache-only. A key is served from the RAM [key cache](#key-cache-key-ids-and-nvm-backing-store) when present; a cache miss would normally fall back to NVM, but with no NVM configured it simply reports `WH_ERROR_NOTFOUND` — the same result as if the key were absent from the store. Keys are made available by *priming* the cache out of band: either by caching key material directly on the server, or by having the client supply [wrapped keys](#wrapped-keys) that are unwrapped directly into the cache. + +What works with no NVM: + +- Cryptographic operations against keys that are primed in the cache. +- Key caching, eviction, and (cache-only) erase. +- SHE encrypt/decrypt/CMAC and secure boot against keys primed in the cache. +- Key wrap/unwrap and unwrap-and-cache, provided the wrapping key (KEK) is primed in the cache. + +What requires NVM, and so fails gracefully at runtime when it is absent (returning an error rather than crashing): + +- The NVM object request API (list/read/add/destroy). +- [Certificate-chain verification](#certificate-management) against trusted roots stored in NVM. +- [Monotonic counters](#non-volatile-monotonic-counters). +- Committing a cached key to persistent storage (`wh_Server_KeystoreCommitKey`). +- [SHE](#autosar-she-subsystem) key persistence and the SHE PRNG seed (`LOAD_KEY` of non-RAM keys, `INIT_RND`, `EXTEND_SEED`), and image-signature loading. + +> **Note**: When [global keys](#global-keys) (`WOLFHSM_CFG_GLOBAL_KEYS`) are enabled, the shared global key cache normally lives inside the NVM context. With no NVM there is no shared store, so global keys (USER `0`) are served from the per-context local cache instead. They remain usable when primed, but are not shared across server contexts as they would be with NVM present. + +When NVM **is** configured, all of the above behavior is unchanged. + ## Keystore The keystore manages the lifecycle of cryptographic key material on the server. It sits on top of [NVM](#non-volatile-memory-nvm) and is the layer every crypto operation goes through to reference a key. Clients refer to keys by a stable 16-bit identifier, not by the key bytes; the material stays server-side and is only returned through explicit, policy-checked operations. This is what lets the server enforce per-key usage policy, isolate keys between clients, and offload bulk crypto to hardware without exposing key bytes outside the trust boundary. diff --git a/src/wh_server.c b/src/wh_server.c index 2618d29b7..580657757 100644 --- a/src/wh_server.c +++ b/src/wh_server.c @@ -712,17 +712,25 @@ int wh_Server_HandleRequestMessage(whServerContext* server) #ifdef WOLFHSM_CFG_THREADSAFE int wh_Server_NvmLock(whServerContext* server) { - if (server == NULL || server->nvm == NULL) { + if (server == NULL) { return WH_ERROR_BADARGS; } + /* NVM is optional. With no NVM there's no shared state to protect, so the + * lock is a no-op and cache-only operations can still run. */ + if (server->nvm == NULL) { + return WH_ERROR_OK; + } return wh_Lock_Acquire(&server->nvm->lock); } int wh_Server_NvmUnlock(whServerContext* server) { - if (server == NULL || server->nvm == NULL) { + if (server == NULL) { return WH_ERROR_BADARGS; } + if (server->nvm == NULL) { + return WH_ERROR_OK; + } return wh_Lock_Release(&server->nvm->lock); } #endif /* WOLFHSM_CFG_THREADSAFE */ diff --git a/src/wh_server_counter.c b/src/wh_server_counter.c index 7bd1d1af2..a681c894a 100644 --- a/src/wh_server_counter.c +++ b/src/wh_server_counter.c @@ -47,8 +47,7 @@ int wh_Server_HandleCounter(whServerContext* server, uint16_t magic, whNvmMetadata meta[1] = {{0}}; uint32_t* counter = (uint32_t*)(&meta->label); - if (server == NULL || server->nvm == NULL || req_packet == NULL || - out_resp_size == NULL) { + if (server == NULL || req_packet == NULL || out_resp_size == NULL) { return WH_ERROR_BADARGS; } diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index 277db543f..3d8fa829d 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -91,12 +91,15 @@ static int _IsGlobalKey(whKeyId keyId) * When WOLFHSM_CFG_GLOBAL_KEYS is enabled, routes to global cache if keyId * has USER == 0, otherwise routes to local cache. When disabled, always * routes to local cache. + * + * The global cache lives in the NVM context, which is optional. With no NVM + * there is no global cache, so global keys fall back to the local cache. */ static whKeyCacheContext* _GetCacheContext(whServerContext* server, whKeyId keyId) { #ifdef WOLFHSM_CFG_GLOBAL_KEYS - if (_IsGlobalKey(keyId)) { + if (_IsGlobalKey(keyId) && (server->nvm != NULL)) { return &server->nvm->globalCache; } #else @@ -160,8 +163,8 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, return ret; } - /* Check NVM if not in cache */ - if (!foundInCache) { + /* Check NVM if not in cache. No NVM means the key can't be there. */ + if (!foundInCache && (server->nvm != NULL)) { ret = wh_Nvm_GetMetadata(server->nvm, keyId, &nvmMeta); if (ret == WH_ERROR_OK) { foundInNvm = 1; @@ -619,7 +622,12 @@ int wh_Server_KeystoreGetUniqueId(whServerContext* server, whNvmId* inout_id) return ret; } - /* Check if keyId exists in NVM */ + /* Check if keyId exists in NVM. With no NVM, not being in the cache + * is enough to make this ID unique. */ + if (server->nvm == NULL) { + found = 1; + break; + } ret = wh_Nvm_GetMetadata(server->nvm, buildId, NULL); if (ret == WH_ERROR_NOTFOUND) { /* key doesn't exist in NVM, we found a candidate ID */ @@ -806,6 +814,11 @@ int wh_Server_KeystoreFreshenKey(whServerContext* server, whKeyId keyId, return WH_ERROR_NOTFOUND; } + /* No NVM to check, so a cache miss means not found. */ + if (server->nvm == NULL) { + return WH_ERROR_NOTFOUND; + } + /* Not in cache. Check if it is in NVM */ ret = wh_Nvm_GetMetadata(server->nvm, keyId, tmpMeta); if (ret == WH_ERROR_OK) { @@ -870,8 +883,14 @@ int wh_Server_KeystoreReadKey(whServerContext* server, whKeyId keyId, return WH_ERROR_NOTFOUND; } - /* Not in cache, try to read the metadata from NVM */ - ret = wh_Nvm_GetMetadata(server->nvm, keyId, meta); + /* Not in cache, try to read the metadata from NVM. With no NVM the key is + * not found, but the SHE master-ecu fallback below still applies. */ + if (server->nvm != NULL) { + ret = wh_Nvm_GetMetadata(server->nvm, keyId, meta); + } + else { + ret = WH_ERROR_NOTFOUND; + } if (ret == 0) { if (meta->len > *outSz) return WH_ERROR_NOSPACE; @@ -978,6 +997,8 @@ int wh_Server_KeystoreCommitKey(whServerContext* server, whNvmId keyId) ret = _FindInKeyCache(ctx, keyId, NULL, NULL, &slotBuf, &slotMeta); if (ret == WH_ERROR_OK) { size = slotMeta->len; + /* Committing writes the cached key to NVM. With no NVM there is + * nowhere to persist it, so wh_Nvm_* returns an error. */ ret = wh_Nvm_AddObjectWithReclaim(server->nvm, slotMeta, size, slotBuf); if (ret == 0) { /* Mark key as committed using unified function */ @@ -1011,12 +1032,20 @@ int wh_Server_KeystoreEraseKey(whServerContext* server, whNvmId keyId) /* remove the key from the cache if present */ (void)wh_Server_KeystoreEvictKey(server, 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; + } + /* destroy the object */ return wh_Nvm_DestroyObjects(server->nvm, 1, &keyId); } int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) { + int ret; + if ((server == NULL) || (WH_KEYID_ISERASED(keyId))) { return WH_ERROR_BADARGS; } @@ -1025,8 +1054,14 @@ int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) return WH_ERROR_ABORTED; } - /* remove the key from the cache if present */ - (void)wh_Server_KeystoreEvictKeyChecked(server, keyId); + /* remove the key from the cache if present, enforcing policy */ + ret = wh_Server_KeystoreEvictKeyChecked(server, keyId); + + /* With no NVM, the cache eviction above is the whole erase; return its + * result so policy and not-found errors still propagate. */ + if (server->nvm == NULL) { + return ret; + } /* destroy the object */ return wh_Nvm_DestroyObjectsChecked(server->nvm, 1, &keyId); @@ -1066,12 +1101,15 @@ int wh_Server_KeystoreRevokeKey(whServerContext* server, whNvmId keyId) return ret; } - ret = wh_Nvm_GetMetadata(server->nvm, keyId, NULL); - if (ret == WH_ERROR_OK) { - isInNvm = 1; - } - else if (ret != WH_ERROR_NOTFOUND) { - return ret; + /* No NVM means the key can't be in NVM. */ + if (server->nvm != NULL) { + ret = wh_Nvm_GetMetadata(server->nvm, keyId, NULL); + if (ret == WH_ERROR_OK) { + isInNvm = 1; + } + else if (ret != WH_ERROR_NOTFOUND) { + return ret; + } } /* be sure to have the key in the cache */ diff --git a/test-refactor/server/wh_test_nvm_optional.c b/test-refactor/server/wh_test_nvm_optional.c new file mode 100644 index 000000000..306d725f1 --- /dev/null +++ b/test-refactor/server/wh_test_nvm_optional.c @@ -0,0 +1,340 @@ +/* + * 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_nvm_optional.c + * + * Server-side test that the keystore treats NVM as optional. The shared + * server context arrives fully initialized (with a real NVM); this test + * temporarily detaches the NVM (server->nvm = NULL) to exercise the no-NVM + * paths, then restores it. Mirrors test/wh_test_nvm_optional.c. + */ + +#include "wolfhsm/wh_settings.h" + +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_keystore.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_message.h" +#include "wolfhsm/wh_message_counter.h" +#include "wolfhsm/wh_server_counter.h" +#include "wolfhsm/wh_message_nvm.h" +#include "wolfhsm/wh_server_nvm.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/aes.h" +#endif + +#ifdef WOLFHSM_CFG_SHE_EXTENSION +#include "wolfhsm/wh_server_she.h" +#endif + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#if defined(WOLFHSM_CFG_ENABLE_SERVER) && !defined(WOLFHSM_CFG_NO_CRYPTO) + +#define WH_TEST_NVMOPT_AES_BLOCK (16) +#define WH_TEST_NVMOPT_KEYLEN (32) + +/* Use a key (as retrieved from the cache) for an AES-CBC round trip, proving a + * primed key is usable for crypto without any NVM. */ +static int _AesCbcRoundTrip(const uint8_t* key, uint32_t keyLen) +{ + Aes aes[1]; + uint8_t iv[WH_TEST_NVMOPT_AES_BLOCK]; + uint8_t pt[WH_TEST_NVMOPT_AES_BLOCK * 2]; + uint8_t ct[sizeof(pt)]; + uint8_t dec[sizeof(pt)]; + int ret; + + if ((keyLen != 16) && (keyLen != 24) && (keyLen != 32)) { + WH_ERROR_PRINT("unexpected cached key length: %u\n", (unsigned)keyLen); + return WH_TEST_FAIL; + } + + memset(iv, 0x24, sizeof(iv)); + memset(pt, 0xA5, sizeof(pt)); + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesSetKey(aes, key, keyLen, iv, AES_ENCRYPTION); + if (ret == 0) { + ret = wc_AesCbcEncrypt(aes, ct, pt, sizeof(pt)); + } + wc_AesFree(aes); + } + if (ret != 0) { + WH_ERROR_PRINT("AES-CBC encrypt failed: %d\n", ret); + return ret; + } + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesSetKey(aes, key, keyLen, iv, AES_DECRYPTION); + if (ret == 0) { + ret = wc_AesCbcDecrypt(aes, dec, ct, sizeof(ct)); + } + wc_AesFree(aes); + } + if (ret != 0) { + WH_ERROR_PRINT("AES-CBC decrypt failed: %d\n", ret); + return ret; + } + + if (memcmp(pt, dec, sizeof(pt)) != 0) { + WH_ERROR_PRINT("NVM-optional AES-CBC round-trip mismatch\n"); + return WH_TEST_FAIL; + } + return 0; +} + +#ifdef WOLFHSM_CFG_SHE_EXTENSION +static int _SheKeystoreChecks(whServerContext* server) +{ + whNvmMetadata meta[1]; + whNvmMetadata outMeta[1]; + uint8_t sheKey[WH_SHE_KEY_SZ]; + uint8_t outKey[WH_SHE_KEY_SZ]; + uint32_t outSz; + whKeyId sheId; + int i; + int allZero; + + /* Init key to arbitrary value */ + for (i = 0; i < (int)sizeof(sheKey); i++) { + sheKey[i] = (uint8_t)(0xF0 ^ i); + } + + sheId = WH_MAKE_KEYID(WH_KEYTYPE_SHE, WH_TEST_DEFAULT_CLIENT_ID, 0x05); + memset(meta, 0, sizeof(meta)); + meta->id = sheId; + meta->len = (whNvmSize)WH_SHE_KEY_SZ; + meta->flags = WH_NVM_FLAGS_USAGE_ANY; + meta->access = WH_NVM_ACCESS_ANY; + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreCacheKey(server, meta, sheKey)); + + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_OK == + wh_Server_KeystoreReadKey(server, sheId, outMeta, outKey, &outSz)); + WH_TEST_ASSERT_RETURN(outSz == (uint32_t)WH_SHE_KEY_SZ); + WH_TEST_ASSERT_RETURN(0 == memcmp(outKey, sheKey, WH_SHE_KEY_SZ)); + + /* SHE master-ecu key falls back to an all-zero key when not present. */ + memset(outKey, 0xFF, sizeof(outKey)); + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_OK == + wh_Server_KeystoreReadKey(server, + WH_MAKE_KEYID(WH_KEYTYPE_SHE, + WH_TEST_DEFAULT_CLIENT_ID, + WH_SHE_MASTER_ECU_KEY_ID), + outMeta, outKey, &outSz)); + WH_TEST_ASSERT_RETURN(outSz == (uint32_t)WH_SHE_KEY_SZ); + allZero = 1; + for (i = 0; i < (int)WH_SHE_KEY_SZ; i++) { + if (outKey[i] != 0) { + allZero = 0; + } + } + WH_TEST_ASSERT_RETURN(allZero == 1); + + (void)wh_Server_KeystoreEvictKey(server, sheId); + return 0; +} +#endif /* WOLFHSM_CFG_SHE_EXTENSION */ + +/* Runs with server->nvm already detached (NULL) by the caller. */ +static int _RunNvmOptionalChecks(whServerContext* server) +{ + whNvmMetadata meta[1]; + whNvmMetadata outMeta[1]; + uint8_t keyData[WH_TEST_NVMOPT_KEYLEN]; + uint8_t outKey[WH_TEST_NVMOPT_KEYLEN]; + uint32_t outSz; + uint8_t* cacheBuf = NULL; + whNvmMetadata* cacheMeta = NULL; + whKeyId localId; + whKeyId globalId; + whKeyId missingId; + whKeyId eraseCheckedId; + whKeyId revokeId; + int i; + + for (i = 0; i < (int)sizeof(keyData); i++) { + keyData[i] = (uint8_t)i; + } + + localId = WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_DEFAULT_CLIENT_ID, 0x10); + globalId = WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_KEYUSER_GLOBAL, 0x11); + missingId = + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_DEFAULT_CLIENT_ID, 0x77); + eraseCheckedId = + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_DEFAULT_CLIENT_ID, 0x20); + revokeId = + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_DEFAULT_CLIENT_ID, 0x21); + + /* A cache miss with no NVM reports NOTFOUND (not BADARGS). */ + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_Server_KeystoreReadKey(server, missingId, outMeta, outKey, &outSz)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_Server_KeystoreFreshenKey(server, missingId, &cacheBuf, &cacheMeta)); + +#ifdef WOLFHSM_CFG_THREADSAFE + /* The NVM lock is a successful no-op when there is no NVM to serialize. */ + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_Server_NvmLock(server)); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_Server_NvmUnlock(server)); +#endif + + /* Prime a local key and read it back. */ + memset(meta, 0, sizeof(meta)); + meta->id = localId; + meta->len = (whNvmSize)sizeof(keyData); + meta->flags = WH_NVM_FLAGS_USAGE_ANY; + meta->access = WH_NVM_ACCESS_ANY; + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreCacheKey(server, meta, keyData)); + + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_OK == + wh_Server_KeystoreReadKey(server, localId, outMeta, outKey, &outSz)); + WH_TEST_ASSERT_RETURN(outSz == (uint32_t)sizeof(keyData)); + WH_TEST_ASSERT_RETURN(0 == memcmp(outKey, keyData, sizeof(keyData))); + + /* Prime a GLOBAL key (USER==0): regression for the _GetCacheContext crash + * (dereferenced server->nvm->globalCache when nvm==NULL). */ + memset(meta, 0, sizeof(meta)); + meta->id = globalId; + meta->len = (whNvmSize)sizeof(keyData); + meta->flags = WH_NVM_FLAGS_USAGE_ANY; + meta->access = WH_NVM_ACCESS_ANY; + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreCacheKey(server, meta, keyData)); + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_OK == + wh_Server_KeystoreReadKey(server, globalId, outMeta, outKey, &outSz)); + WH_TEST_ASSERT_RETURN(0 == memcmp(outKey, keyData, sizeof(keyData))); + + /* The primed key is retrievable via the crypto path (FreshenKey) and + * usable for an AES-CBC round trip -- crypto works with no NVM. */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_OK == + wh_Server_KeystoreFreshenKey(server, localId, &cacheBuf, &cacheMeta)); + WH_TEST_ASSERT_RETURN(cacheBuf != NULL); + WH_TEST_ASSERT_RETURN(cacheMeta != NULL); + WH_TEST_RETURN_ON_FAIL(_AesCbcRoundTrip(cacheBuf, cacheMeta->len)); + + /* Commit requires NVM to persist; with none it fails gracefully. */ + WH_TEST_ASSERT_RETURN(WH_ERROR_OK != + wh_Server_KeystoreCommitKey(server, localId)); + + /* Erase is cache-only without NVM and succeeds; the key is then gone. */ + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreEraseKey(server, localId)); + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOTFOUND == + wh_Server_KeystoreReadKey(server, localId, outMeta, outKey, &outSz)); + + /* EraseKeyChecked enforces policy and, with no NVM, returns the cache + * eviction's status. A missing key reports NOTFOUND -- unlike the + * non-checked EraseKey above, which treats "nothing to destroy" as OK. */ + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Server_KeystoreEraseKeyChecked(server, missingId)); + + /* On a primed, policy-permissive key, EraseKeyChecked succeeds cache-only + * and the key is then gone. */ + memset(meta, 0, sizeof(meta)); + meta->id = eraseCheckedId; + meta->len = (whNvmSize)sizeof(keyData); + meta->flags = WH_NVM_FLAGS_USAGE_ANY; + meta->access = WH_NVM_ACCESS_ANY; + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreCacheKey(server, meta, keyData)); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == wh_Server_KeystoreEraseKeyChecked( + server, eraseCheckedId)); + outSz = sizeof(outKey); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Server_KeystoreReadKey(server, eraseCheckedId, + outMeta, outKey, &outSz)); + + /* RevokeKey with no NVM revokes the cached copy (the NVM probe/commit is + * skipped). The key becomes NONMODIFIABLE, so a subsequent policy-checked + * erase is denied with ACCESS -- proving the revoke took effect and that + * EraseKeyChecked surfaces policy errors with no NVM. */ + memset(meta, 0, sizeof(meta)); + meta->id = revokeId; + meta->len = (whNvmSize)sizeof(keyData); + meta->flags = WH_NVM_FLAGS_USAGE_ANY; + meta->access = WH_NVM_ACCESS_ANY; + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreCacheKey(server, meta, keyData)); + WH_TEST_ASSERT_RETURN(WH_ERROR_OK == + wh_Server_KeystoreRevokeKey(server, revokeId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Server_KeystoreEraseKeyChecked(server, revokeId)); + /* Force-remove the revoked key (EraseKeyChecked could not). */ + (void)wh_Server_KeystoreEvictKey(server, revokeId); + + /* Drop the global key we cached so the shared context is clean. */ + (void)wh_Server_KeystoreEraseKey(server, globalId); + +#ifdef WOLFHSM_CFG_SHE_EXTENSION + WH_TEST_RETURN_ON_FAIL(_SheKeystoreChecks(server)); +#endif + + return 0; +} + +int whTest_NvmOptional(whServerContext* ctx) +{ + whServerContext* server = (whServerContext*)ctx; + whNvmContext* savedNvm; + int ret; + + if (server == NULL) { + return WH_ERROR_BADARGS; + } + + /* Detach NVM to exercise the optional-NVM paths, then always restore so + * later tests in the shared context still see their NVM backing. */ + savedNvm = server->nvm; + server->nvm = NULL; + + ret = _RunNvmOptionalChecks(server); + + server->nvm = savedNvm; + return ret; +} + +#endif /* WOLFHSM_CFG_ENABLE_SERVER && !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index ac29019a9..c01f571c0 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -36,6 +36,7 @@ WH_TEST_DECL(whTest_Comm); WH_TEST_DECL(whTest_Dma); WH_TEST_DECL(whTest_KeystoreReqSize); WH_TEST_DECL(whTest_CertVerify); +WH_TEST_DECL(whTest_NvmOptional); WH_TEST_DECL(whTest_ClientCerts); WH_TEST_DECL(whTest_CryptoAes); WH_TEST_DECL(whTest_CryptoEcc256); @@ -55,7 +56,8 @@ const whTestCase whTestsMisc[] = { const size_t whTestsMiscCount = sizeof(whTestsMisc) / sizeof(whTestsMisc[0]); const whTestCase whTestsServer[] = { - { "whTest_CertVerify", whTest_CertVerify }, + {"whTest_CertVerify", whTest_CertVerify}, + {"whTest_NvmOptional", whTest_NvmOptional}, }; const size_t whTestsServerCount = sizeof(whTestsServer) / sizeof(whTestsServer[0]); diff --git a/wolfhsm/wh_server.h b/wolfhsm/wh_server.h index e332434a2..30eb56f09 100644 --- a/wolfhsm/wh_server.h +++ b/wolfhsm/wh_server.h @@ -141,7 +141,7 @@ typedef struct { typedef struct whServerConfig_t { whCommServerConfig* comm_config; - whNvmContext* nvm; + whNvmContext* nvm; /* optional; NULL = no NVM backing */ #ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION whAuthContext* auth; #endif /* WOLFHSM_CFG_ENABLE_AUTHENTICATION */ @@ -199,15 +199,25 @@ struct whServerContext_t { /** Public server context functions */ /* Initialize the comms and crypto cache components. - * Note: NVM and Crypto components must be initialized prior to Server Init + * Note: Crypto components must be initialized prior to Server Init. NVM, if + * provided, must also be initialized first; NVM is optional (see below). */ /** * @brief Initializes the server context with the provided configuration. * * This function must be called before any other server functions are used on - * the supplied context. Note that the NVM and Crypto components of the config - * structure MUST be initialized before calling this function. + * the supplied context. If a Crypto component is configured it MUST be + * initialized before calling this function. + * + * The NVM component is OPTIONAL: config->nvm may be NULL. With no NVM backing + * the server still runs and crypto works through the key cache when keys are + * primed (cached directly or via wrapped keys). Keystore lookups that miss the + * cache return WH_ERROR_NOTFOUND (as if the key were absent from NVM), and + * operations that inherently require persistence (the NVM request API, + * certificate-chain verification against stored roots, counters, key commit, + * SHE key/seed persistence, image-signature loading) fail at runtime rather + * than crashing. If config->nvm is provided, behavior is unchanged. * * @param[in] server Pointer to the server context. * @param[in] config Pointer to the server configuration.