diff --git a/test-refactor/README.md b/test-refactor/README.md
index cd37861f9..b8cc32133 100644
--- a/test-refactor/README.md
+++ b/test-refactor/README.md
@@ -80,6 +80,7 @@ Translated tests:
| `wh_test_dma.c::whTest_Dma` | `misc/wh_test_dma.c::whTest_Dma` | Misc | |
| `wh_test_comm.c::whTest_Comm` | `misc/wh_test_comm.c::whTest_Comm` | Misc | Sequential mem variant only; pthread mem/tcp/shmem variants remain in the legacy harness |
| `wh_test_keystore_reqsize.c::whTest_KeystoreReqSize` | `misc/wh_test_keystore_reqsize.c::whTest_KeystoreReqSize` | Misc | |
+| `wh_test_multiclient.c::whTest_MultiClient` | `misc/wh_test_multiclient.c::whTest_MultiClient` | Misc | Sequential variant only (no legacy pthread variant exists); body is a no-op when `WOLFHSM_CFG_GLOBAL_KEYS` is off |
| `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group |
| `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto.c::{whTest_CryptoSha256, whTest_CryptoAes, whTest_CryptoEcc256}` | Client | Subset only; remaining cases listed below |
| `wh_test_clientserver.c` (echo and server-info paths) | `client-server/wh_test_echo.c::whTest_Echo`, `client-server/wh_test_server_info.c::whTest_ServerInfo` | Client | pthread test ported, sequential test dropped |
@@ -97,7 +98,6 @@ Not yet migrated (still live in `wolfHSM/test/`):
| `wh_test_crypto.c::whTest_Crypto` | RNG, key cache, key-cache enforcement, RSA, CMAC, Curve25519, ML-DSA, key usage policies, key revocation |
| `wh_test_crypto_affinity.c::whTest_CryptoAffinity` | |
| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | |
-| `wh_test_multiclient.c::whTest_MultiClient` | |
| `wh_test_lock.c::whTest_LockConfig`, `whTest_LockPosix` | `whTest_LockConfig` to be reworked to fit the Misc group, likely with a context param. |
| `wh_test_log.c::whTest_Log`, `whTest_LogBackend_RunAll` | `whTest_LogBackend_RunAll` to be reworked to fit the Misc group, likely with a context param. |
| `wh_test_she.c::whTest_She` | |
diff --git a/test-refactor/misc/wh_test_multiclient.c b/test-refactor/misc/wh_test_multiclient.c
new file mode 100644
index 000000000..df9e74f45
--- /dev/null
+++ b/test-refactor/misc/wh_test_multiclient.c
@@ -0,0 +1,1658 @@
+/*
+ * Copyright (C) 2025 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_multiclient.c
+ *
+ * Multi-client test framework and test suites
+ *
+ * Provides reusable setup/teardown infrastructure for testing features that
+ * require multiple clients. Each client connects to its own server instance,
+ * but both servers share a common NVM context to enable testing of shared
+ * resources (global keys, shared counters, etc.).
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+/* Legacy test/wh_test.c gates the whTest_MultiClient() call site on
+ * !WOLFHSM_CFG_NO_CRYPTO. In test-refactor that gate has to live at the
+ * file level so the test reports SKIPPED rather than running an empty
+ * fixture under NOCRYPTO. */
+#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) \
+ && !defined(WOLFHSM_CFG_NO_CRYPTO)
+
+#include
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_message.h"
+#include "wolfhsm/wh_transport_mem.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_server.h"
+#include "wolfhsm/wh_server_keystore.h"
+#include "wolfhsm/wh_nvm.h"
+#include "wolfhsm/wh_nvm_flash.h"
+#include "wolfhsm/wh_flash_ramsim.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+/* Test configuration */
+#define FLASH_RAM_SIZE (1024 * 1024) /* 1MB */
+#define FLASH_SECTOR_SIZE (128 * 1024) /* 128KB */
+#define FLASH_PAGE_SIZE (8) /* 8B */
+#define BUFFER_SIZE 4096
+
+#ifdef WOLFHSM_CFG_GLOBAL_KEYS
+/* Test key data */
+static const uint8_t TEST_KEY_DATA_1[] = "TestGlobalKey1Data";
+static const uint8_t TEST_KEY_DATA_2[] = "TestLocalKey2Data";
+static const uint8_t TEST_KEY_DATA_3[] = "TestGlobalKey3DataLonger";
+#endif
+
+/* ============================================================================
+ * DUMMY KEY ID DEFINITIONS
+ *
+ * Generic key ID values for use in tests. Actual keyIds are defined locally
+ * within each test function and macros (WH_CLIENT_KEYID_MAKE_GLOBAL, etc.) are
+ * applied at assignment time.
+ * ========================================================================== */
+
+#define DUMMY_KEYID_1 1
+#define DUMMY_KEYID_2 2
+
+/* ============================================================================
+ * MULTI-CLIENT TEST FRAMEWORK INFRASTRUCTURE
+ * ========================================================================== */
+
+/* Server contexts for connect callbacks */
+static whServerContext* testServer1 = NULL;
+static whServerContext* testServer2 = NULL;
+
+/* Connect callback for client 1 */
+static int _connectCb1(void* context, whCommConnected connected)
+{
+ (void)context;
+ if (testServer1 == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+ return wh_Server_SetConnected(testServer1, connected);
+}
+
+/* Connect callback for client 2 */
+static int _connectCb2(void* context, whCommConnected connected)
+{
+ (void)context;
+ if (testServer2 == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+ return wh_Server_SetConnected(testServer2, connected);
+}
+
+/* ============================================================================
+ * GLOBAL KEYS TEST SUITE
+ * ========================================================================== */
+
+#ifdef WOLFHSM_CFG_GLOBAL_KEYS
+
+/*
+ * Test 1: Basic global key operations
+ * - Client 1 caches a global key
+ * - Client 2 reads the same global key and verifies the data matches
+ */
+static int _testGlobalKeyBasic(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint8_t outBuf[sizeof(TEST_KEY_DATA_1)] = {0};
+ uint16_t outSz = sizeof(outBuf);
+
+ WH_TEST_PRINT("Test: Global key basic operations\n");
+
+ /* Client 1 caches a global key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"GlobalKey5", sizeof("GlobalKey5"),
+ (uint8_t*)TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1), keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &keyId));
+
+ /* Client 2 reads the same global key */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz));
+
+ /* Verify the key data matches */
+ WH_TEST_ASSERT_RETURN(outSz == sizeof(TEST_KEY_DATA_1));
+ WH_TEST_ASSERT_RETURN(0 == memcmp(outBuf, TEST_KEY_DATA_1, outSz));
+
+ WH_TEST_PRINT(" PASS: Basic global key operations\n");
+
+ (void)ret;
+ return 0;
+}
+
+/*
+ * Test 2: Local key isolation
+ * - Both clients cache local keys with the same ID but different data
+ * - Each client verifies they can only read their own local key data, not the
+ * other's
+ */
+static int _testLocalKeyIsolation(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId keyId1 = DUMMY_KEYID_1; /* Local key for client 1 */
+ whKeyId keyId2 =
+ DUMMY_KEYID_1; /* Same ID for client 2 - should be different key */
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint8_t outBuf[32] = {0};
+ uint16_t outSz;
+
+ WH_TEST_PRINT("Test: Local key isolation\n");
+
+ /* Client 1 caches a local key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"LocalKey10_C1", sizeof("LocalKey10_C1"),
+ (uint8_t*)TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1), keyId1));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &keyId1));
+
+ /* Client 2 caches a different local key with same ID */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client2, 0, (uint8_t*)"LocalKey10_C2", sizeof("LocalKey10_C2"),
+ (uint8_t*)TEST_KEY_DATA_2, sizeof(TEST_KEY_DATA_2), keyId2));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client2, &keyId2));
+
+ /* Client 1 reads its own key */
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client1, keyId1));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client1, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1)));
+
+ /* Client 2 reads its own key (different data) */
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, keyId2));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_2, sizeof(TEST_KEY_DATA_2)));
+
+ WH_TEST_PRINT(" PASS: Local key isolation\n");
+
+ (void)ret;
+ return 0;
+}
+
+/*
+ * Test 3: Mixed global and local keys with no cross-cache interference
+ * - Client 1 caches both a global key and a local key with the same ID number
+ * - Client 2 caches a local key with the same ID (different data)
+ * - Client 1 can read both its global and local keys correctly
+ * - Client 2 can access the global key
+ * - Client 2 can read its own local key (different data than client 1's)
+ * - Client 2 correctly fails to access Client 1's local key
+ */
+static int _testMixedGlobalLocal(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId globalKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ whKeyId localKeyId = DUMMY_KEYID_1; /* Same ID number but local */
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint8_t outBuf[32] = {0};
+ uint16_t outSz;
+
+ WH_TEST_DEBUG_PRINT(
+ "Test: Mixed global and local keys with no cross-cache interference\n");
+
+ /* Client 1 caches global key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"Global15", sizeof("Global15"),
+ (uint8_t*)TEST_KEY_DATA_3, sizeof(TEST_KEY_DATA_3), globalKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &globalKeyId));
+
+ /* Client 1 caches local key with same ID number */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"Local15_C1", sizeof("Local15_C1"),
+ (uint8_t*)TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1), localKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &localKeyId));
+
+ /* Client 2 caches local key with same ID number (different data) */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client2, 0, (uint8_t*)"Local15_C2", sizeof("Local15_C2"),
+ (uint8_t*)TEST_KEY_DATA_2, sizeof(TEST_KEY_DATA_2), localKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client2, &localKeyId));
+
+ /* Client 1 reads its global key */
+ globalKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client1, globalKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client1, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_3, sizeof(TEST_KEY_DATA_3)));
+
+ /* Client 1 reads its local key */
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client1, localKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client1, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1)));
+
+ /* Client 2 accesses global key (should work) */
+ globalKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, globalKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_3, sizeof(TEST_KEY_DATA_3)));
+
+ /* Client 2 reads its own local key 15 (different data) */
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, localKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_2, sizeof(TEST_KEY_DATA_2)));
+
+ /* Client 1 tries to access Client 2's local key 15 - should fail */
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client1, localKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ ret = wh_Client_KeyExportResponse(client1, label, labelSz, outBuf, &outSz);
+ /* Should get client 1's own local key, not client 2's */
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1)));
+
+ WH_TEST_PRINT(" PASS: Mixed global and local keys with no cross-cache "
+ "interference\n");
+
+ (void)ret;
+ return 0;
+}
+
+/*
+ * Test 4: NVM persistence of global keys
+ * - Client 1 caches a global key and commits it to NVM, then evicts it from
+ * cache
+ * - Client 2 successfully reloads the global key from NVM
+ */
+static int _testGlobalKeyNvmPersistence(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint8_t outBuf[sizeof(TEST_KEY_DATA_1)] = {0};
+ uint16_t outSz;
+
+ WH_TEST_PRINT("Test: NVM persistence of global keys\n");
+
+ /* Client 1 caches and commits a global key to NVM */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"GlobalNVM20", sizeof("GlobalNVM20"),
+ (uint8_t*)TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1), keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &keyId));
+
+ /* Commit to NVM */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCommitRequest(client1, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCommitResponse(client1));
+
+ /* Evict from cache on server1 */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ /* Client 2 reads from NVM (will reload to cache) */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1)));
+
+ /* Clean up - erase the key */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEraseRequest(client1, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEraseResponse(client1));
+
+ WH_TEST_PRINT(" PASS: NVM persistence of global keys\n");
+
+ (void)ret;
+ return 0;
+}
+
+/*
+ * Test 5: Export protection on global keys
+ * - Client 1 caches a non-exportable global key
+ * - Client 2 correctly fails when attempting to export the protected key
+ */
+static int _testGlobalKeyExportProtection(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint8_t outBuf[sizeof(TEST_KEY_DATA_1)] = {0};
+ uint16_t outSz;
+
+ WH_TEST_PRINT("Test: Export protection on global keys\n");
+
+ /* Client 1 caches a non-exportable global key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_NONEXPORTABLE, (uint8_t*)"NoExport25",
+ sizeof("NoExport25"), (uint8_t*)TEST_KEY_DATA_1,
+ sizeof(TEST_KEY_DATA_1), keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &keyId));
+
+ /* Client 2 tries to export it - should fail */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ ret = wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz);
+ /* Should fail due to non-exportable flag */
+ WH_TEST_ASSERT_RETURN(ret != 0);
+
+ /* Clean up */
+ keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Export protection on global keys\n");
+
+ return 0;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+/*
+ * Test 6: DMA operations with global keys
+ * - Client 1 caches a global key using DMA transfer
+ * - Client 2 reads the DMA-cached global key via regular export
+ * - Client 1 caches another global key via regular method
+ * - Client 2 exports that global key via DMA transfer
+ */
+static int _testGlobalKeyDma(whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId keyId1 = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ whKeyId keyId2 = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_2);
+ uint8_t keyData1[32] = "GlobalDmaCacheTestKey123456!";
+ uint8_t keyData2[32] = "GlobalDmaExportTestKey12345!";
+ uint8_t outBuf[32] = {0};
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint16_t outSz;
+
+ WH_TEST_PRINT("Test: DMA operations with global keys\n");
+
+ /* Part 1: Cache via DMA, export via regular */
+ /* Client 1 caches a global key using DMA */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheDmaRequest(
+ client1, 0, (uint8_t*)"DmaGlobal35", sizeof("DmaGlobal35"), keyData1,
+ sizeof(keyData1), keyId1));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheDmaResponse(client1, &keyId1));
+
+ /* Client 2 reads the global key via regular export */
+ keyId1 = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(client2, keyId1));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportResponse(client2, label, labelSz, outBuf, &outSz));
+
+ /* Verify the key data matches */
+ WH_TEST_ASSERT_RETURN(outSz == sizeof(keyData1));
+ WH_TEST_ASSERT_RETURN(0 == memcmp(outBuf, keyData1, outSz));
+
+ /* Part 2: Cache via regular, export via DMA */
+ /* Client 1 caches a global key using regular method */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"DmaExport40", sizeof("DmaExport40"), keyData2,
+ sizeof(keyData2), keyId2));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &keyId2));
+
+ /* Client 2 exports the global key via DMA */
+ keyId2 = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_2);
+ outSz = sizeof(outBuf);
+ memset(outBuf, 0, sizeof(outBuf));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportDmaRequest(client2, keyId2, outBuf, sizeof(outBuf)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportDmaResponse(client2, label, labelSz, &outSz));
+
+ /* Verify the key data matches */
+ WH_TEST_ASSERT_RETURN(outSz == sizeof(keyData2));
+ WH_TEST_ASSERT_RETURN(0 == memcmp(outBuf, keyData2, outSz));
+
+ /* Clean up */
+ keyId1 = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, keyId1));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ keyId2 = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_2);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, keyId2));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: DMA operations with global keys\n");
+
+ (void)ret;
+ return 0;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+#ifdef WOLFHSM_CFG_KEYWRAP
+/*
+ * Test 7: Key wrap with global server key
+ * - Client 1 caches a global wrapping key
+ * - Client 2 wraps a key using that global server key
+ * - Client 1 unwraps the key using the same global server key
+ */
+static int _testGlobalKeyWrapExport(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "GlobalWrapKey123456789012345!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "PlainKeyToWrap1234567890123!";
+ /* Wrapped key size = IV(12) + TAG(16) + KEYSIZE(32) + metadata */
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_PRINT("Test: Key wrap with global server key\n");
+
+ /* Client 1 caches a global wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey45",
+ sizeof("WrapKey45"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 2 wraps a global key using the global server key */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ meta.id =
+ WH_CLIENT_KEYID_MAKE_WRAPPED_META(WH_KEYUSER_GLOBAL, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client2, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 1 unwraps the key using the same global server key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportResponse(
+ client1, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz));
+
+ /* Verify the unwrapped key matches the original */
+ WH_TEST_ASSERT_RETURN(0 ==
+ memcmp(unwrappedKey, plainKey, sizeof(plainKey)));
+
+ /* Clean up */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Key wrap with global server key\n");
+
+ (void)ret;
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 8: Key unwrap and cache with global server key
+ * - Client 1 caches a global wrapping key and wraps a key (also global)
+ * - Client 2 unwraps and caches the key using the global server key
+ * - Client 2 exports and verifies the cached key matches the original
+ */
+static int _testGlobalKeyUnwrapCache(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ whKeyId cachedKeyId = 0;
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "GlobalUnwrapKey123456789012!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "KeyToCacheViaUnwrap123456!!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t verifyBuf[AES_256_KEY_SIZE] = {0};
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint16_t verifySz = sizeof(verifyBuf);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_PRINT("Test: Key unwrap and cache with global server key\n");
+
+ /* Client 1 caches a global wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"UnwrapKey50",
+ sizeof("UnwrapKey50"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 1 wraps a global key */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ meta.id =
+ WH_CLIENT_KEYID_MAKE_WRAPPED_META(WH_KEYUSER_GLOBAL, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client1, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client1, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 2 unwraps and caches the key using the global server key */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ ret = wh_Client_KeyUnwrapAndCacheRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, wrappedKey,
+ sizeof(wrappedKey));
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_OK);
+
+ ret = wh_Server_HandleRequestMessage(server2);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_OK);
+
+ ret = wh_Client_KeyUnwrapAndCacheResponse(client2, WC_CIPHER_AES_GCM,
+ &cachedKeyId);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_OK);
+
+ /* Verify the cached key by exporting it */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(
+ client2, WH_CLIENT_KEYID_MAKE_WRAPPED_GLOBAL(cachedKeyId)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportResponse(client2, label, labelSz,
+ verifyBuf, &verifySz));
+
+ /* Verify the exported key matches the original */
+ WH_TEST_ASSERT_RETURN(0 == memcmp(verifyBuf, plainKey, sizeof(plainKey)));
+
+ /* Clean up */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(
+ client2, WH_CLIENT_KEYID_MAKE_WRAPPED_GLOBAL(cachedKeyId)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client2));
+
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Key unwrap and cache with global server key\n");
+
+ (void)ret;
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 7a: Global wrapping key + Global wrapped key (Positive)
+ * - Client 1 caches a global wrapping key
+ * - Client 2 wraps a global key using it
+ * - Client 1 unwraps and exports successfully
+ */
+static int _testWrappedKey_GlobalWrap_GlobalKey_Positive(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "GlobalWrapKey2Test7aXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "GlobalPlainKey2Test7aXXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_DEBUG_PRINT("Test 7a: Global wrap key + Global wrapped key (Positive)\n");
+
+ /* Client 1 caches a global wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_7a",
+ sizeof("WrapKey_7a"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 2 wraps a GLOBAL key using the global server key */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ meta.id =
+ WH_CLIENT_KEYID_MAKE_WRAPPED_META(WH_KEYUSER_GLOBAL, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client2, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 1 unwraps and exports the global key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportResponse(
+ client1, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz));
+
+ /* Verify the unwrapped key matches the original */
+ WH_TEST_ASSERT_RETURN(0 ==
+ memcmp(unwrappedKey, plainKey, sizeof(plainKey)));
+
+ /* Clean up */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Global wrap key + Global wrapped key (Positive)\n");
+
+ (void)ret;
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 7b: Global wrapping key + Global wrapped key (Negative - NONEXPORTABLE)
+ * - Client 1 caches a global wrapping key
+ * - Client 2 wraps a global key with NONEXPORTABLE flag
+ * - Client 1 unwrap-and-export fails with WH_ERROR_ACCESS
+ */
+static int _testWrappedKey_GlobalWrap_GlobalKey_NonExportable(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "GlobalWrapKey2Test7bXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "GlobalPlainKey2Test7bXXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_DEBUG_PRINT("Test 7b: Global wrap key + Global wrapped key (Non-exportable)\n");
+
+ /* Client 1 caches a global wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_7b",
+ sizeof("WrapKey_7b"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 2 wraps a GLOBAL key with NONEXPORTABLE flag */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ meta.id =
+ WH_CLIENT_KEYID_MAKE_WRAPPED_META(WH_KEYUSER_GLOBAL, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ meta.flags = WH_NVM_FLAGS_NONEXPORTABLE;
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client2, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 1 tries to unwrap and export - should fail */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ ret = wh_Client_KeyUnwrapAndExportResponse(
+ client1, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz);
+
+ /* Should fail due to non-exportable flag */
+ WH_TEST_ASSERT_RETURN(ret != 0);
+
+ /* Clean up */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Global wrap key + Global wrapped key (Non-exportable)\n");
+
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 8a: Global wrapping key + Local wrapped key (Positive - Owner)
+ * - Client 1 caches a global wrapping key
+ * - Client 2 wraps a LOCAL key (USER=client2_id) using global wrapping key
+ * - Client 2 unwraps and exports successfully (owner)
+ */
+static int _testWrappedKey_GlobalWrap_LocalKey_OwnerExport(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint16_t client2Id = WH_TEST_DEFAULT_CLIENT_ID + 1;
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "GlobalWrapKey2Test8aXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "LocalPlainKey2Test8aXXXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_DEBUG_PRINT("Test 8a: Global wrap key + Local wrapped key (Owner export)\n");
+
+ /* Client 1 caches a global wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_8a",
+ sizeof("WrapKey_8a"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 2 wraps a LOCAL key (USER=client2_id) */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client2Id, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client2, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 2 (owner) unwraps and exports the local key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportRequest(
+ client2, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportResponse(
+ client2, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz));
+
+ /* Verify the unwrapped key matches the original */
+ WH_TEST_ASSERT_RETURN(0 ==
+ memcmp(unwrappedKey, plainKey, sizeof(plainKey)));
+
+ /* Clean up */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Global wrap key + Local wrapped key (Owner export)\n");
+
+ (void)ret;
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 8b: Global wrapping key + Local wrapped key (Negative - Non-owner)
+ * - Client 1 caches a global wrapping key
+ * - Client 2 wraps a LOCAL key (USER=client2_id)
+ * - Client 1 unwrap-and-export fails with WH_ERROR_ACCESS (not owner)
+ * - Client 1 unwrap-and-cache also fails with WH_ERROR_ACCESS
+ */
+static int _testWrappedKey_GlobalWrap_LocalKey_NonOwnerFails(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ uint16_t client2Id = WH_TEST_DEFAULT_CLIENT_ID + 1;
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "GlobalWrapKey2Test8bXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "LocalPlainKey2Test8bXXXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+ whKeyId cachedKeyId = 0;
+
+ WH_TEST_DEBUG_PRINT("Test 8b: Global wrap key + Local wrapped key (Non-owner fails)\n");
+
+ /* Client 1 caches a global wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_8b",
+ sizeof("WrapKey_8b"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 2 wraps a LOCAL key (USER=client2_id) */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client2Id, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client2, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 1 (non-owner) tries to unwrap and export - should fail */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ ret = wh_Client_KeyUnwrapAndExportResponse(
+ client1, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz);
+
+ /* Should fail - Client 1 is not the owner */
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ACCESS);
+
+ /* Client 1 (non-owner) tries to unwrap and cache - should also fail */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndCacheRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ ret = wh_Client_KeyUnwrapAndCacheResponse(client1, WC_CIPHER_AES_GCM,
+ &cachedKeyId);
+
+ /* Should also fail - Client 1 is not the owner */
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ACCESS);
+
+ /* Clean up */
+ serverKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Global wrap key + Local wrapped key (Non-owner fails)\n");
+
+ return WH_ERROR_OK;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 9a: Local wrapping key + Local wrapped key (Positive - Same owner)
+ * - Client 1 caches a local wrapping key
+ * - Client 1 wraps a LOCAL key (USER=client1_id)
+ * - Client 1 unwraps and exports successfully
+ */
+static int _testWrappedKey_LocalWrap_LocalKey_SameOwner(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = DUMMY_KEYID_1; /* Local wrapping key */
+ uint16_t client1Id = WH_TEST_DEFAULT_CLIENT_ID;
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "LocalWrapKey2Test9aXXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "LocalPlainKey2Test9aXXXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_DEBUG_PRINT("Test 9a: Local wrap key + Local wrapped key (Same owner)\n");
+
+ /* Client 1 caches a LOCAL wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_9a",
+ sizeof("WrapKey_9a"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 1 wraps a LOCAL key (USER=client1_id) */
+ serverKeyId = DUMMY_KEYID_1; /* Use local wrapping key */
+ meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client1Id, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client1, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client1, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 1 (owner) unwraps and exports the local key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndExportResponse(
+ client1, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz));
+
+ /* Verify the unwrapped key matches the original */
+ WH_TEST_ASSERT_RETURN(0 ==
+ memcmp(unwrappedKey, plainKey, sizeof(plainKey)));
+
+ /* Clean up */
+ serverKeyId = DUMMY_KEYID_1;
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Local wrap key + Local wrapped key (Same owner)\n");
+
+ (void)ret;
+ (void)client2;
+ (void)server2;
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 9b: Local wrapping key + Local wrapped key (Negative - No access without
+ * wrap key)
+ * - Client 1 caches a local wrapping key
+ * - Client 1 wraps a local key
+ * - Client 2 cannot unwrap (doesn't have wrapping key)
+ */
+static int _testWrappedKey_LocalWrap_LocalKey_NoAccessWithoutWrapKey(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = DUMMY_KEYID_1; /* Local wrapping key */
+ uint16_t client1Id = WH_TEST_DEFAULT_CLIENT_ID;
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "LocalWrapKey2Test9bXXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "LocalPlainKey2Test9bXXXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_DEBUG_PRINT(
+ "Test 9b: Local wrap key + Local wrapped key (No wrap key access)\n");
+
+ /* Client 1 caches a LOCAL wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_9b",
+ sizeof("WrapKey_9b"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 1 wraps a LOCAL key */
+ serverKeyId = DUMMY_KEYID_1; /* Use local wrapping key */
+ meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client1Id, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client1, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client1, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 2 tries to unwrap - should fail (no wrapping key) */
+ ret = wh_Client_KeyUnwrapAndExportRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, wrappedKey,
+ sizeof(wrappedKey));
+ if (ret == 0) {
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ ret = wh_Client_KeyUnwrapAndExportResponse(
+ client2, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz);
+ }
+
+ /* Should fail - Client 2 doesn't have the wrapping key */
+ WH_TEST_ASSERT_RETURN(ret != 0);
+
+ /* Clean up */
+ serverKeyId = DUMMY_KEYID_1;
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Local wrap key + Local wrapped key (No wrap key access)\n");
+
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 10a: Local wrapping key + Global wrapped key (Positive - Any cache
+ * global)
+ * - Client 1 caches a local wrapping key
+ * - Client 1 wraps a GLOBAL key (USER=0)
+ * - Client 1 unwraps and caches to global cache
+ * - Client 2 can read from global cache via KeyExport
+ */
+static int _testWrappedKey_LocalWrap_GlobalKey_AnyCacheGlobal(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = DUMMY_KEYID_1; /* Local wrapping key */
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "LocalWrapKey2Test10aXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "GlobalPlainKey2Test10aXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t exportedKey[AES_256_KEY_SIZE] = {0};
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+ uint16_t exportedSz = sizeof(exportedKey);
+ whNvmMetadata meta = {0};
+ whKeyId cachedKeyId = 0;
+
+ WH_TEST_DEBUG_PRINT("Test 10a: Local wrap key + Global wrapped key (Cache global)\n");
+
+ /* Client 1 caches a LOCAL wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_10a",
+ sizeof("WrapKey_10a"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 1 wraps a GLOBAL key (USER=0) */
+ serverKeyId = DUMMY_KEYID_1; /* Use local wrapping key */
+ meta.id =
+ WH_CLIENT_KEYID_MAKE_WRAPPED_META(WH_KEYUSER_GLOBAL, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client1, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client1, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 1 unwraps and caches to global cache */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndCacheRequest(
+ client1, WC_CIPHER_AES_GCM, serverKeyId, wrappedKey,
+ sizeof(wrappedKey)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyUnwrapAndCacheResponse(
+ client1, WC_CIPHER_AES_GCM, &cachedKeyId));
+
+ /* Client 2 reads from global cache via KeyExport */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportRequest(
+ client2, WH_CLIENT_KEYID_MAKE_WRAPPED_GLOBAL(cachedKeyId)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportResponse(
+ client2, label, labelSz, exportedKey, &exportedSz));
+
+ /* Verify the exported key matches the original */
+ WH_TEST_ASSERT_RETURN(0 == memcmp(exportedKey, plainKey, sizeof(plainKey)));
+
+ /* Clean up */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(
+ client2, WH_CLIENT_KEYID_MAKE_WRAPPED_GLOBAL(cachedKeyId)));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client2));
+
+ serverKeyId = DUMMY_KEYID_1;
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Local wrap key + Global wrapped key (Cache global)\n");
+
+ (void)ret;
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+
+/*
+ * Test 10b: Local wrapping key + Global wrapped key (Negative - No wrap key)
+ * - Client 1 caches a local wrapping key
+ * - Client 1 wraps a global key
+ * - Client 2 cannot unwrap (doesn't have wrapping key)
+ */
+static int _testWrappedKey_LocalWrap_GlobalKey_NonOwnerNoWrapKey(
+ whClientContext* client1, whServerContext* server1,
+ whClientContext* client2, whServerContext* server2)
+{
+ int ret;
+ whKeyId serverKeyId = DUMMY_KEYID_1; /* Local wrapping key */
+ uint8_t wrapKey[AES_256_KEY_SIZE] = "LocalWrapKey2Test10bXXXXXXXXX!";
+ uint8_t plainKey[AES_256_KEY_SIZE] = "GlobalPlainKey2Test10bXXXXXXX!";
+#define WRAPPED_KEY_SIZE (12 + 16 + AES_256_KEY_SIZE + sizeof(whNvmMetadata))
+ uint8_t wrappedKey[WRAPPED_KEY_SIZE] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ uint8_t unwrappedKey[AES_256_KEY_SIZE] = {0};
+ uint16_t unwrappedKeySz = sizeof(unwrappedKey);
+ whNvmMetadata meta = {0};
+
+ WH_TEST_DEBUG_PRINT("Test 10b: Local wrap key + Global wrapped key (No wrap key)\n");
+
+ /* Client 1 caches a LOCAL wrapping key */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, WH_NVM_FLAGS_USAGE_WRAP, (uint8_t*)"WrapKey_10b",
+ sizeof("WrapKey_10b"), wrapKey, sizeof(wrapKey), serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheResponse(client1, &serverKeyId));
+
+ /* Client 1 wraps a GLOBAL key */
+ serverKeyId = DUMMY_KEYID_1; /* Use local wrapping key */
+ meta.id =
+ WH_CLIENT_KEYID_MAKE_WRAPPED_META(WH_KEYUSER_GLOBAL, DUMMY_KEYID_2);
+ meta.len = sizeof(plainKey);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapRequest(client1, WC_CIPHER_AES_GCM,
+ serverKeyId, plainKey,
+ sizeof(plainKey), &meta));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapResponse(
+ client1, WC_CIPHER_AES_GCM, wrappedKey, &wrappedKeySz));
+
+ /* Client 2 tries to unwrap - should fail (no wrapping key) */
+ ret = wh_Client_KeyUnwrapAndExportRequest(client2, WC_CIPHER_AES_GCM,
+ serverKeyId, wrappedKey,
+ sizeof(wrappedKey));
+ if (ret == 0) {
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server2));
+ ret = wh_Client_KeyUnwrapAndExportResponse(
+ client2, WC_CIPHER_AES_GCM, &meta, unwrappedKey, &unwrappedKeySz);
+ }
+
+ /* Should fail - Client 2 doesn't have the wrapping key */
+ WH_TEST_ASSERT_RETURN(ret != 0);
+
+ /* Clean up */
+ serverKeyId = DUMMY_KEYID_1;
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictRequest(client1, serverKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Local wrap key + Global wrapped key (No wrap key)\n");
+
+ return 0;
+#undef WRAPPED_KEY_SIZE
+}
+#endif /* WOLFHSM_CFG_KEYWRAP */
+
+/*
+ * Test: KeyId flag preservation
+ * - Tests that global and wrapped flags are preserved in server responses
+ * - Verifies keyCache operations return correct flags
+ */
+static int _testKeyIdFlagPreservation(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ (void)client2;
+ (void)server2;
+
+ WH_TEST_PRINT("Test: KeyId flag preservation\n");
+
+ /* Test 1: Global key cache preserves global flag */
+ {
+ whKeyId keyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ whKeyId returnedKeyId = 0;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"GlobalKeyFlags", sizeof("GlobalKeyFlags"),
+ (uint8_t*)TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1), keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyCacheResponse(client1, &returnedKeyId));
+
+ /* Verify global flag is preserved */
+ WH_TEST_ASSERT_RETURN((returnedKeyId & WH_KEYID_CLIENT_GLOBAL_FLAG) !=
+ 0);
+ WH_TEST_ASSERT_RETURN((returnedKeyId & WH_KEYID_MASK) == DUMMY_KEYID_1);
+
+ /* Clean up */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyEvictRequest(client1, returnedKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Global key cache preserves global flag\n");
+ }
+
+ /* Test 2: Local key cache does not have global flag */
+ {
+ whKeyId keyId = DUMMY_KEYID_2; /* Local key - no flags */
+ whKeyId returnedKeyId = 0;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"LocalKeyFlags", sizeof("LocalKeyFlags"),
+ (uint8_t*)TEST_KEY_DATA_2, sizeof(TEST_KEY_DATA_2), keyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyCacheResponse(client1, &returnedKeyId));
+
+ /* Verify no global flag */
+ WH_TEST_ASSERT_RETURN((returnedKeyId & WH_KEYID_CLIENT_GLOBAL_FLAG) ==
+ 0);
+ WH_TEST_ASSERT_RETURN((returnedKeyId & WH_KEYID_MASK) == DUMMY_KEYID_2);
+
+ /* Clean up */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyEvictRequest(client1, returnedKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Local key cache has no global flag\n");
+ }
+
+ /* Test 3: Reusing returned keyId works correctly */
+ {
+ whKeyId requestKeyId = WH_CLIENT_KEYID_MAKE_GLOBAL(DUMMY_KEYID_1);
+ whKeyId returnedKeyId = 0;
+ uint8_t outBuf[sizeof(TEST_KEY_DATA_1)] = {0};
+ uint16_t outSz = sizeof(outBuf);
+ uint8_t label[WH_NVM_LABEL_LEN];
+ uint16_t labelSz = sizeof(label);
+
+ /* Cache a global key and get keyId back */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheRequest_ex(
+ client1, 0, (uint8_t*)"ReuseTest", sizeof("ReuseTest"),
+ (uint8_t*)TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1), requestKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyCacheResponse(client1, &returnedKeyId));
+
+ /* Use the returned keyId to export the key (common pattern) */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportRequest(client1, returnedKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportResponse(
+ client1, label, labelSz, outBuf, &outSz));
+
+ /* Verify data matches */
+ WH_TEST_ASSERT_RETURN(outSz == sizeof(TEST_KEY_DATA_1));
+ WH_TEST_ASSERT_RETURN(
+ 0 == memcmp(outBuf, TEST_KEY_DATA_1, sizeof(TEST_KEY_DATA_1)));
+
+ /* Clean up using returned keyId */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyEvictRequest(client1, returnedKeyId));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_HandleRequestMessage(server1));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(client1));
+
+ WH_TEST_PRINT(" PASS: Reusing returned keyId works correctly\n");
+ }
+
+ return 0;
+}
+
+/* Helper function to run all global keys tests */
+static int _runGlobalKeysTests(whClientContext* client1,
+ whServerContext* server1,
+ whClientContext* client2,
+ whServerContext* server2)
+{
+ WH_TEST_RETURN_ON_FAIL(
+ _testKeyIdFlagPreservation(client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testGlobalKeyBasic(client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testLocalKeyIsolation(client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testMixedGlobalLocal(client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testGlobalKeyNvmPersistence(client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testGlobalKeyExportProtection(client1, server1, client2, server2));
+
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(
+ _testGlobalKeyDma(client1, server1, client2, server2));
+#endif
+
+#ifdef WOLFHSM_CFG_KEYWRAP
+ WH_TEST_RETURN_ON_FAIL(
+ _testGlobalKeyWrapExport(client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testGlobalKeyUnwrapCache(client1, server1, client2, server2));
+
+ /* Comprehensive wrapped key access control tests */
+ WH_TEST_RETURN_ON_FAIL(_testWrappedKey_GlobalWrap_GlobalKey_Positive(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(_testWrappedKey_GlobalWrap_GlobalKey_NonExportable(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(_testWrappedKey_GlobalWrap_LocalKey_OwnerExport(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(_testWrappedKey_GlobalWrap_LocalKey_NonOwnerFails(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(_testWrappedKey_LocalWrap_LocalKey_SameOwner(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testWrappedKey_LocalWrap_LocalKey_NoAccessWithoutWrapKey(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(_testWrappedKey_LocalWrap_GlobalKey_AnyCacheGlobal(
+ client1, server1, client2, server2));
+
+ WH_TEST_RETURN_ON_FAIL(
+ _testWrappedKey_LocalWrap_GlobalKey_NonOwnerNoWrapKey(
+ client1, server1, client2, server2));
+#endif
+
+ WH_TEST_PRINT("All Global Keys Tests PASSED ===\n");
+ return 0;
+}
+
+#endif /* WOLFHSM_CFG_GLOBAL_KEYS */
+
+/* ============================================================================
+ * MULTI-CLIENT SEQUENTIAL TEST FRAMEWORK
+ * ========================================================================== */
+
+/* Generic setup/teardown for multi-client sequential tests using shared memory
+ */
+static int _whTest_MultiClient(void)
+{
+ int ret = 0;
+
+ /* Transport memory configurations for both clients */
+ static uint8_t req1[BUFFER_SIZE];
+ static uint8_t resp1[BUFFER_SIZE];
+ whTransportMemConfig tmcf1[1] = {{
+ .req = (whTransportMemCsr*)req1,
+ .req_size = sizeof(req1),
+ .resp = (whTransportMemCsr*)resp1,
+ .resp_size = sizeof(resp1),
+ }};
+
+ static uint8_t req2[BUFFER_SIZE];
+ static uint8_t resp2[BUFFER_SIZE];
+ whTransportMemConfig tmcf2[1] = {{
+ .req = (whTransportMemCsr*)req2,
+ .req_size = sizeof(req2),
+ .resp = (whTransportMemCsr*)resp2,
+ .resp_size = sizeof(resp2),
+ }};
+
+ /* Client 1 configuration */
+ whTransportClientCb tccb1[1] = {WH_TRANSPORT_MEM_CLIENT_CB};
+ whTransportMemClientContext tmcc1[1] = {0};
+ whCommClientConfig cc_conf1[1] = {{
+ .transport_cb = tccb1,
+ .transport_context = (void*)tmcc1,
+ .transport_config = (void*)tmcf1,
+ .client_id = WH_TEST_DEFAULT_CLIENT_ID,
+ .connect_cb = _connectCb1,
+ }};
+ whClientContext client1[1] = {0};
+ whClientConfig c_conf1[1] = {{
+ .comm = cc_conf1,
+ }};
+
+ /* Client 2 configuration */
+ whTransportClientCb tccb2[1] = {WH_TRANSPORT_MEM_CLIENT_CB};
+ whTransportMemClientContext tmcc2[1] = {0};
+ whCommClientConfig cc_conf2[1] = {{
+ .transport_cb = tccb2,
+ .transport_context = (void*)tmcc2,
+ .transport_config = (void*)tmcf2,
+ .client_id = WH_TEST_DEFAULT_CLIENT_ID + 1,
+ .connect_cb = _connectCb2,
+ }};
+ whClientContext client2[1] = {0};
+ whClientConfig c_conf2[1] = {{
+ .comm = cc_conf2,
+ }};
+
+ /* Shared NVM configuration using RamSim Flash */
+ static uint8_t memory[FLASH_RAM_SIZE] = {0};
+ whFlashRamsimCtx fc[1] = {0};
+ whFlashRamsimCfg fc_conf[1] = {{
+ .size = FLASH_RAM_SIZE,
+ .sectorSize = FLASH_SECTOR_SIZE,
+ .pageSize = FLASH_PAGE_SIZE,
+ .erasedByte = ~(uint8_t)0,
+ .memory = memory,
+ }};
+ const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
+
+ whNvmFlashConfig nf_conf[1] = {{
+ .cb = fcb,
+ .context = fc,
+ .config = fc_conf,
+ }};
+ whNvmFlashContext nfc[1] = {0};
+ whNvmCb nfcb[1] = {WH_NVM_FLASH_CB};
+
+ whNvmConfig n_conf[1] = {{
+ .cb = nfcb,
+ .context = nfc,
+ .config = nf_conf,
+ }};
+ whNvmContext nvm[1] = {0}; /* Shared NVM */
+
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+ /* Crypto contexts for both servers */
+ whServerCryptoContext crypto1[1] = {0};
+ whServerCryptoContext crypto2[1] = {0};
+#endif
+
+ /* Server 1 configuration */
+ whTransportServerCb tscb1[1] = {WH_TRANSPORT_MEM_SERVER_CB};
+ whTransportMemServerContext tmsc1[1] = {0};
+ whCommServerConfig cs_conf1[1] = {{
+ .transport_cb = tscb1,
+ .transport_context = (void*)tmsc1,
+ .transport_config = (void*)tmcf1,
+ .server_id = 101,
+ }};
+ whServerConfig s_conf1[1] = {{
+ .comm_config = cs_conf1,
+ .nvm = nvm, /* Shared NVM */
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+ .crypto = crypto1,
+#endif
+ }};
+ whServerContext server1[1] = {0};
+
+ /* Server 2 configuration */
+ whTransportServerCb tscb2[1] = {WH_TRANSPORT_MEM_SERVER_CB};
+ whTransportMemServerContext tmsc2[1] = {0};
+ whCommServerConfig cs_conf2[1] = {{
+ .transport_cb = tscb2,
+ .transport_context = (void*)tmsc2,
+ .transport_config = (void*)tmcf2,
+ .server_id = 102,
+ }};
+ whServerConfig s_conf2[1] = {{
+ .comm_config = cs_conf2,
+ .nvm = nvm, /* Shared NVM */
+
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+ .crypto = crypto2,
+#endif
+ }};
+ whServerContext server2[1] = {0};
+
+ /* Expose server contexts to connect callbacks */
+ testServer1 = server1;
+ testServer2 = server2;
+
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+ /* Initialize wolfCrypt */
+ ret = wolfCrypt_Init();
+ if (ret != 0)
+ return ret;
+#endif
+
+ /* Initialize NVM (shared) */
+ ret = wh_Nvm_Init(nvm, n_conf);
+ if (ret != 0)
+ return ret;
+
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+ /* Initialize RNGs */
+ ret = wc_InitRng_ex(crypto1->rng, NULL, INVALID_DEVID);
+ if (ret != 0)
+ return ret;
+
+ ret = wc_InitRng_ex(crypto2->rng, NULL, INVALID_DEVID);
+ if (ret != 0)
+ return ret;
+#endif
+
+ /* Initialize servers */
+ ret = wh_Server_Init(server1, s_conf1);
+ if (ret != 0)
+ return ret;
+
+ ret = wh_Server_Init(server2, s_conf2);
+ if (ret != 0)
+ return ret;
+
+ /* Initialize clients */
+ ret = wh_Client_Init(client1, c_conf1);
+ if (ret != 0)
+ return ret;
+
+ ret = wh_Client_Init(client2, c_conf2);
+ if (ret != 0)
+ return ret;
+
+ /* Initialize communication for both clients */
+ uint32_t client_id = 0;
+ uint32_t server_id = 0;
+
+ ret = wh_Client_CommInitRequest(client1);
+ if (ret != 0)
+ return ret;
+ ret = wh_Server_HandleRequestMessage(server1);
+ if (ret != 0)
+ return ret;
+ ret = wh_Client_CommInitResponse(client1, &client_id, &server_id);
+ if (ret != 0)
+ return ret;
+
+ ret = wh_Client_CommInitRequest(client2);
+ if (ret != 0)
+ return ret;
+ ret = wh_Server_HandleRequestMessage(server2);
+ if (ret != 0)
+ return ret;
+ ret = wh_Client_CommInitResponse(client2, &client_id, &server_id);
+ if (ret != 0)
+ return ret;
+
+ WH_TEST_PRINT("=== Multi-Client Sequential Tests Begin ===\n");
+ /* Run test suites that require multiple clients */
+#ifdef WOLFHSM_CFG_GLOBAL_KEYS
+ WH_TEST_RETURN_ON_FAIL(
+ _runGlobalKeysTests(client1, server1, client2, server2));
+#endif
+
+ /* Future test suites here */
+
+ /* Cleanup */
+ wh_Client_Cleanup(client1);
+ wh_Client_Cleanup(client2);
+ wh_Server_Cleanup(server1);
+ wh_Server_Cleanup(server2);
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+ wc_FreeRng(crypto1->rng);
+ wc_FreeRng(crypto2->rng);
+ wolfCrypt_Cleanup();
+#endif
+ wh_Nvm_Cleanup(nvm);
+
+ WH_TEST_PRINT("=== Multi-Client Sequential Tests Complete ===\n");
+
+ return 0;
+}
+
+/* ============================================================================
+ * PUBLIC API
+ * ========================================================================== */
+
+/* Main entry point for multi-client tests */
+int whTest_MultiClient(void* ctx)
+{
+ (void)ctx;
+ return _whTest_MultiClient();
+}
+
+#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index ac29019a9..cb140b151 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -35,6 +35,7 @@
WH_TEST_DECL(whTest_Comm);
WH_TEST_DECL(whTest_Dma);
WH_TEST_DECL(whTest_KeystoreReqSize);
+WH_TEST_DECL(whTest_MultiClient);
WH_TEST_DECL(whTest_CertVerify);
WH_TEST_DECL(whTest_ClientCerts);
WH_TEST_DECL(whTest_CryptoAes);
@@ -49,8 +50,9 @@ WH_TEST_DECL(whTest_WolfCryptTest);
const whTestCase whTestsMisc[] = {
{ "whTest_Comm", whTest_Comm },
- { "whTest_Dma", whTest_Dma },
{ "whTest_KeystoreReqSize", whTest_KeystoreReqSize },
+ { "whTest_Dma", whTest_Dma },
+ { "whTest_MultiClient", whTest_MultiClient },
};
const size_t whTestsMiscCount = sizeof(whTestsMisc) / sizeof(whTestsMisc[0]);
@@ -64,7 +66,7 @@ const whTestCase whTestsClient[] = {
{ "whTest_CryptoAes", whTest_CryptoAes },
{ "whTest_CryptoEcc256", whTest_CryptoEcc256 },
{ "whTest_CryptoEd25519BufferTooSmall",
- whTest_CryptoEd25519BufferTooSmall },
+ whTest_CryptoEd25519BufferTooSmall },
{ "whTest_CryptoMlDsaBufferTooSmall", whTest_CryptoMlDsaBufferTooSmall },
{ "whTest_CryptoRsaBufferTooSmall", whTest_CryptoRsaBufferTooSmall },
{ "whTest_CryptoSha256", whTest_CryptoSha256 },