diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c
index 277db543f..bd4775dcf 100644
--- a/src/wh_server_keystore.c
+++ b/src/wh_server_keystore.c
@@ -886,7 +886,11 @@ int wh_Server_KeystoreReadKey(whServerContext* server, whKeyId keyId,
}
/* cache key if free slot, will only kick out other committed keys */
if (ret == 0 && out != NULL) {
- (void)wh_Server_KeystoreCacheKey(server, meta, out);
+ if (wh_Server_KeystoreCacheKey(server, meta, out) == WH_ERROR_OK) {
+ /* Cached key found in NVM. Mark it committed so it can be
+ evicted later. */
+ _MarkKeyCommitted(_GetCacheContext(server, keyId), keyId, 1);
+ }
}
#ifdef WOLFHSM_CFG_SHE_EXTENSION
/* use empty key of zeros if we couldn't find the master ecu key */
diff --git a/test-refactor/README.md b/test-refactor/README.md
index cd37861f9..5c37719c2 100644
--- a/test-refactor/README.md
+++ b/test-refactor/README.md
@@ -87,6 +87,8 @@ Translated tests:
| `wh_test_flash_ramsim.c::whTest_Flash_RamSim` | `posix/wh_test_flash_ramsim.c::{whTest_FlashWriteLock, whTest_FlashEraseProgramVerify, whTest_FlashUnitOps}` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group |
| `wh_test_nvm_flash.c::whTest_NvmFlash` | `posix/wh_test_nvm_flash.c::whTest_NvmAddOverwriteDestroy` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group |
| `wh_test_posix_threadsafe_stress.c::whTest_ThreadSafeStress` | called directly from `posix/wh_test_posix_main.c` | POSIX port-specific (direct call) | |
+| `wh_test_she.c` (`whTest_SheMasterEcuKeyFallback`, `whTest_SheReqSizeChecking`) | `server/wh_test_she_server.c::{whTest_SheMasterEcuKeyFallback, whTest_SheReqSizeChecking}` | Server | server-internal checks reworked to use the shared server context; the POSIX server config gains a `whServerSheContext` under `WOLFHSM_CFG_SHE_EXTENSION` |
+| `wh_test_she.c::whTest_She` (client flows) | `client-server/wh_test_she.c::whTest_She` | Client | SHE UID/secure-boot state is one-shot per server lifetime, so the three legacy client flows are folded into one test that does `SetUid` plus a single comm-boundary-sized secure boot, then the load-key vectors, UID handling, RND, ECB/CBC/MAC, and write-protect rejection -- all of which only need UID set and secure boot complete. Build with `make SHE=1` |
Not yet migrated (still live in `wolfHSM/test/`):
@@ -100,7 +102,6 @@ Not yet migrated (still live in `wolfHSM/test/`):
| `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` | |
| `wh_test_timeout.c::whTest_TimeoutPosix` | |
| `wh_test_auth.c::whTest_AuthMEM`, `whTest_AuthTCP` | |
| `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | |
diff --git a/test-refactor/client-server/wh_test_she.c b/test-refactor/client-server/wh_test_she.c
new file mode 100644
index 000000000..f5883e403
--- /dev/null
+++ b/test-refactor/client-server/wh_test_she.c
@@ -0,0 +1,552 @@
+/*
+ * 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/client-server/wh_test_she.c
+ *
+ * Client-side SHE flow. SHE UID and secure-boot state is one-shot
+ * per server lifetime, so the whole sequence runs as a single test
+ * against one connected client: one SetUid and one secure boot
+ * (sized to the comm-buffer boundary), then the load-key vectors,
+ * UID handling, RND, ECB/CBC/MAC round-trips, and a write-protect
+ * rejection, which only require that UID is set and secure boot has
+ * completed.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#if defined(WOLFHSM_CFG_SHE_EXTENSION) && !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_ENABLE_CLIENT)
+
+#include
+#include
+
+#include "wolfssl/wolfcrypt/settings.h"
+#include "wolfssl/wolfcrypt/types.h"
+#include "wolfssl/wolfcrypt/cmac.h"
+
+#include "wolfhsm/wh_common.h"
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_client_she.h"
+#include "wolfhsm/wh_she_common.h"
+#include "wolfhsm/wh_she_crypto.h"
+#include "wolfhsm/wh_message_she.h"
+
+#include "wh_test_common.h"
+
+#ifndef TEST_ADMIN_USERNAME
+#define TEST_ADMIN_USERNAME "admin"
+#endif
+#ifndef TEST_ADMIN_PIN
+#define TEST_ADMIN_PIN "1234"
+#endif
+
+
+/* Destroy a SHE key so the unit tests don't leak NVM objects across
+ * invocations. Necessary, as SHE doesn't expose a destroy key API since
+ * SHE keys are supposed to be fixed hardware keys. */
+static int _destroySheKey(whClientContext* client, whNvmId clientSheKeyId)
+{
+ int rc = 0;
+ int32_t serverRc = 0;
+ whNvmId id = WH_MAKE_KEYID(WH_KEYTYPE_SHE, client->comm->client_id,
+ clientSheKeyId);
+
+ rc = wh_Client_NvmDestroyObjects(client, 1, &id, &serverRc);
+ if (rc == WH_ERROR_OK) {
+ rc = serverRc;
+ }
+
+ return rc;
+}
+
+
+/*
+ * Full SHE flow against a freshly connected client. SetUid and secure
+ * boot are one-shot per server lifetime, so they run once up front; the
+ * remaining checks (load-key vectors, UID handling, RND, ECB/CBC/MAC,
+ * write protect) only need UID set and secure boot complete.
+ */
+int whTest_She(whClientContext* client)
+{
+ int ret = 0;
+ WC_RNG rng[1];
+ Cmac cmac[1];
+ /* key doubles as the boot MAC key (secure boot) and later the RND
+ * output that gets loaded as the RAM plain key. */
+ uint8_t key[16] = {0};
+ uint32_t keySz = sizeof(key);
+ uint8_t iv[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+ uint8_t plainText[64];
+ uint8_t cipherText[64];
+ uint8_t finalText[64];
+ /* secretKey and prngSeed are taken from SHE test vectors */
+ uint8_t sheUid[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+ uint8_t secretKey[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
+ uint8_t prngSeed[] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+ 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a};
+ uint8_t zeros[WH_SHE_BOOT_MAC_PREFIX_LEN] = {0};
+ /* Bootloader sized to the comm-buffer boundary so the single secure
+ * boot exercises the secure-boot update path at the maximum chunk. */
+ uint8_t bootloader[WOLFHSM_CFG_COMM_DATA_LEN -
+ sizeof(whMessageShe_SecureBootUpdateRequest)];
+ uint8_t bootMacDigest[16] = {0};
+ uint8_t vectorMasterEcuKey[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f};
+ uint32_t digestSz = sizeof(bootMacDigest);
+ uint32_t bootloaderSz;
+ uint32_t serverCommDataLen = WOLFHSM_CFG_COMM_DATA_LEN;
+ uint32_t maxBoundaryUpdateChunk =
+ WOLFHSM_CFG_COMM_DATA_LEN -
+ sizeof(whMessageShe_SecureBootUpdateRequest);
+ uint8_t vectorMessageOne[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x41};
+ uint8_t vectorMessageTwo[] = {0x2b, 0x11, 0x1e, 0x2d, 0x93, 0xf4, 0x86,
+ 0x56, 0x6b, 0xcb, 0xba, 0x1d, 0x7f, 0x7a, 0x97, 0x97, 0xc9, 0x46, 0x43,
+ 0xb0, 0x50, 0xfc, 0x5d, 0x4d, 0x7d, 0xe1, 0x4c, 0xff, 0x68, 0x22, 0x03,
+ 0xc3};
+ uint8_t vectorMessageThree[] = {0xb9, 0xd7, 0x45, 0xe5, 0xac, 0xe7, 0xd4,
+ 0x18, 0x60, 0xbc, 0x63, 0xc2, 0xb9, 0xf5, 0xbb, 0x46};
+ uint8_t vectorMessageFour[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0xb4, 0x72, 0xe8,
+ 0xd8, 0x72, 0x7d, 0x70, 0xd5, 0x72, 0x95, 0xe7, 0x48, 0x49, 0xa2, 0x79,
+ 0x17};
+ uint8_t vectorMessageFive[] = {0x82, 0x0d, 0x8d, 0x95, 0xdc, 0x11, 0xb4,
+ 0x66, 0x88, 0x78, 0x16, 0x0c, 0xb2, 0xa4, 0xe2, 0x3e};
+ uint8_t vectorRawKey[] = {0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
+ 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00};
+ uint8_t outMessageFour[sizeof(vectorMessageFour)];
+ uint8_t outMessageFive[sizeof(vectorMessageFive)];
+ uint8_t entropy[] = {0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e,
+ 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51};
+ uint8_t sreg;
+ uint8_t messageOne[WH_SHE_M1_SZ];
+ uint8_t messageTwo[WH_SHE_M2_SZ];
+ uint8_t messageThree[WH_SHE_M3_SZ];
+ uint8_t messageFour[WH_SHE_M4_SZ];
+ uint8_t messageFive[WH_SHE_M5_SZ];
+ const uint32_t SHE_TEST_VECTOR_KEY_ID = 4;
+ const uint32_t SHE_WP_KEY_ID = 6;
+
+ if (client == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
+ /* Log in as an admin user for the rest of the test */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_AuthLogin(
+ client, WH_AUTH_METHOD_PIN, TEST_ADMIN_USERNAME, TEST_ADMIN_PIN,
+ strlen(TEST_ADMIN_PIN), &ret, NULL));
+#endif /* WOLFHSM_CFG_ENABLE_AUTHENTICATION */
+
+ /* === SetUid + boundary-sized secure boot (one-shot per server) === */
+
+ /* Size the bootloader to the server's comm-buffer boundary so the
+ * single secure boot drives a maximum-sized secure-boot update. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CommInfo(
+ client, NULL, NULL, &serverCommDataLen, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL));
+ if (serverCommDataLen <= sizeof(whMessageShe_SecureBootUpdateRequest)) {
+ WH_ERROR_PRINT("Invalid server cfg_comm_data_len %u\n",
+ (unsigned int)serverCommDataLen);
+ return WH_ERROR_ABORTED;
+ }
+ if (serverCommDataLen < WOLFHSM_CFG_COMM_DATA_LEN) {
+ maxBoundaryUpdateChunk =
+ serverCommDataLen - sizeof(whMessageShe_SecureBootUpdateRequest);
+ }
+ bootloaderSz = maxBoundaryUpdateChunk;
+
+ /* generate the boot MAC key and a fake bootloader */
+ if ((ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wc_RNG_GenerateBlock(rng, key, sizeof(key))) != 0) {
+ WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret);
+ wc_FreeRng(rng);
+ goto exit;
+ }
+ if ((ret = wc_RNG_GenerateBlock(rng, bootloader,
+ maxBoundaryUpdateChunk)) != 0) {
+ WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret);
+ wc_FreeRng(rng);
+ goto exit;
+ }
+ /* Done generating test data, free RNG */
+ wc_FreeRng(rng);
+
+ /* boot MAC digest: CMAC(0..0 | size | bootloader) */
+ if ((ret = wc_InitCmac(cmac, key, sizeof(key), WC_CMAC_AES, NULL)) != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitCmac %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wc_CmacUpdate(cmac, zeros, sizeof(zeros))) != 0) {
+ WH_ERROR_PRINT("Failed to wc_CmacUpdate %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wc_CmacUpdate(cmac, (uint8_t*)&bootloaderSz,
+ sizeof(bootloaderSz))) != 0) {
+ WH_ERROR_PRINT("Failed to wc_CmacUpdate %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wc_CmacUpdate(cmac, bootloader, bootloaderSz)) != 0) {
+ WH_ERROR_PRINT("Failed to wc_CmacUpdate %d\n", ret);
+ goto exit;
+ }
+ digestSz = AES_BLOCK_SIZE;
+ if ((ret = wc_CmacFinal(cmac, bootMacDigest, (word32*)&digestSz)) != 0) {
+ WH_ERROR_PRINT("Failed to wc_CmacFinal %d\n", ret);
+ goto exit;
+ }
+ /* store the boot MAC key and digest */
+ if ((ret = wh_Client_ShePreProgramKey(client, WH_SHE_BOOT_MAC_KEY_ID, 0,
+ key, sizeof(key))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_ShePreProgramKey %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_ShePreProgramKey(client, WH_SHE_BOOT_MAC, 0,
+ bootMacDigest,
+ sizeof(bootMacDigest))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_ShePreProgramKey %d\n", ret);
+ goto exit;
+ }
+ /* set the she uid */
+ if ((ret = wh_Client_SheSetUid(client, sheUid, sizeof(sheUid))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheSetUid %d\n", ret);
+ goto exit;
+ }
+ /* verify bootloader at the comm-buffer boundary */
+ if ((ret = wh_Client_SheSecureBoot(client, bootloader, bootloaderSz)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheSecureBoot %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheGetStatus(client, &sreg)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheGetStatus %d\n", ret);
+ goto exit;
+ }
+ /* verify bootOk, bootFinished and secureBoot */
+ if ((sreg & WH_SHE_SREG_BOOT_OK) == 0 ||
+ (sreg & WH_SHE_SREG_BOOT_FINISHED) == 0 ||
+ (sreg & WH_SHE_SREG_SECURE_BOOT) == 0) {
+ ret = WH_ERROR_ABORTED;
+ WH_ERROR_PRINT("Failed to secureBoot with SHE CMAC\n");
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE secure boot SUCCESS\n");
+
+ /* === Loadable keys and test vectors === */
+
+ /* load the secret key and prng seed using pre program */
+ if ((ret = wh_Client_ShePreProgramKey(client, WH_SHE_SECRET_KEY_ID, 0,
+ secretKey, sizeof(secretKey))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_ShePreProgramKey %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_ShePreProgramKey(client, WH_SHE_PRNG_SEED_ID, 0,
+ prngSeed, sizeof(prngSeed))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_ShePreProgramKey %d\n", ret);
+ goto exit;
+ }
+ /* load the vector master ecu key */
+ if ((ret = wh_She_GenerateLoadableKey(WH_SHE_MASTER_ECU_KEY_ID,
+ WH_SHE_SECRET_KEY_ID, 1, 0, sheUid, vectorMasterEcuKey, secretKey,
+ messageOne, messageTwo, messageThree, messageFour,
+ messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_She_GenerateLoadableKey %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheLoadKey(client, messageOne, messageTwo,
+ messageThree, outMessageFour, outMessageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheLoadKey %d\n", ret);
+ goto exit;
+ }
+ /* verify that our helper function output matches the vector */
+ if ((ret = wh_She_GenerateLoadableKey(SHE_TEST_VECTOR_KEY_ID,
+ WH_SHE_MASTER_ECU_KEY_ID, 1, 0, sheUid, vectorRawKey,
+ vectorMasterEcuKey, messageOne, messageTwo, messageThree,
+ messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_She_GenerateLoadableKey %d\n", ret);
+ goto exit;
+ }
+ if (memcmp(messageOne, vectorMessageOne, sizeof(vectorMessageOne)) != 0 ||
+ memcmp(messageTwo, vectorMessageTwo, sizeof(vectorMessageTwo)) != 0 ||
+ memcmp(messageThree, vectorMessageThree,
+ sizeof(vectorMessageThree)) != 0 ||
+ memcmp(messageFour, vectorMessageFour, sizeof(vectorMessageFour)) != 0 ||
+ memcmp(messageFive, vectorMessageFive, sizeof(vectorMessageFive)) != 0) {
+ ret = WH_ERROR_ABORTED;
+ WH_ERROR_PRINT("Failed to generate a loadable key to match the "
+ "vector\n");
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE wh_SheGenerateLoadableKey SUCCESS\n");
+ /* test CMD_LOAD_KEY with test vector */
+ if ((ret = wh_Client_SheLoadKey(client, vectorMessageOne, vectorMessageTwo,
+ vectorMessageThree, outMessageFour, outMessageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheLoadKey %d\n", ret);
+ goto exit;
+ }
+ if (memcmp(outMessageFour, vectorMessageFour, sizeof(vectorMessageFour))
+ != 0 ||
+ memcmp(outMessageFive, vectorMessageFive,
+ sizeof(vectorMessageFive)) != 0) {
+ ret = WH_ERROR_ABORTED;
+ WH_ERROR_PRINT("wh_Client_SheLoadKey FAILED TO MATCH\n");
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE LOAD KEY SUCCESS\n");
+
+ /* === LoadKey UID handling === */
+
+ /* A non-matching UID must be rejected, an all-zero UID must be
+ * rejected unless the stored target key has WH_SHE_FLAG_WILDCARD set.
+ * Use wh_She_GenerateLoadableKey with the authKey bytes so M3 is valid
+ * and the server reaches the UID check instead of failing earlier on
+ * CMAC verification. */
+ {
+ uint8_t badUid[WH_SHE_UID_SZ];
+ uint8_t zeroUid[WH_SHE_UID_SZ] = {0};
+ const uint32_t SHE_WILDCARD_KEY_ID = 5;
+
+ memset(badUid, 0xAA, sizeof(badUid));
+
+ /* Wrong UID targeting an existing key slot. Server must reject
+ * with WH_SHE_ERC_KEY_UPDATE_ERROR. */
+ if ((ret = wh_She_GenerateLoadableKey(SHE_TEST_VECTOR_KEY_ID,
+ WH_SHE_MASTER_ECU_KEY_ID, 2, 0, badUid, vectorRawKey,
+ vectorMasterEcuKey, messageOne, messageTwo, messageThree,
+ messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to generate bad-UID M1/M2/M3 %d\n", ret);
+ goto exit;
+ }
+ ret = wh_Client_SheLoadKey(client, messageOne, messageTwo, messageThree,
+ outMessageFour, outMessageFive);
+ if (ret != WH_SHE_ERC_KEY_UPDATE_ERROR) {
+ WH_ERROR_PRINT("SHE LOAD KEY bad UID: expected KEY_UPDATE_ERROR, "
+ "got %d\n", ret);
+ ret = WH_ERROR_ABORTED;
+ goto exit;
+ }
+
+ /* Zero UID targeting an unused slot (stored flags == 0, so
+ * WH_SHE_FLAG_WILDCARD is clear). Server must reject. */
+ if ((ret = wh_She_GenerateLoadableKey(SHE_WILDCARD_KEY_ID,
+ WH_SHE_MASTER_ECU_KEY_ID, 1, 0, zeroUid, vectorRawKey,
+ vectorMasterEcuKey, messageOne, messageTwo, messageThree,
+ messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to generate zero-UID no-wildcard "
+ "M1/M2/M3 %d\n", ret);
+ goto exit;
+ }
+ ret = wh_Client_SheLoadKey(client, messageOne, messageTwo, messageThree,
+ outMessageFour, outMessageFive);
+ if (ret != WH_SHE_ERC_KEY_UPDATE_ERROR) {
+ WH_ERROR_PRINT("SHE LOAD KEY zero UID without wildcard: expected "
+ "KEY_UPDATE_ERROR, got %d\n", ret);
+ ret = WH_ERROR_ABORTED;
+ goto exit;
+ }
+
+ /* Preload the target slot with WH_SHE_FLAG_WILDCARD and count
+ * 0 via ShePreProgramKey, which writes the meta label directly
+ * (wh_She_GenerateLoadableKey cannot encode flags > 4 bits due
+ * to the M2 layout overlap between flags and count). Then
+ * re-load the slot with an all-zero UID; the server must
+ * accept it because the stored flags contain WILDCARD. */
+ if ((ret = wh_Client_ShePreProgramKey(client, SHE_WILDCARD_KEY_ID,
+ WH_SHE_FLAG_WILDCARD, vectorRawKey, sizeof(vectorRawKey)))
+ != 0) {
+ WH_ERROR_PRINT("Failed to preload wildcard key %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_She_GenerateLoadableKey(SHE_WILDCARD_KEY_ID,
+ WH_SHE_MASTER_ECU_KEY_ID, 1, 0, zeroUid, vectorRawKey,
+ vectorMasterEcuKey, messageOne, messageTwo, messageThree,
+ messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to generate zero-UID wildcard "
+ "M1/M2/M3 %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheLoadKey(client, messageOne, messageTwo,
+ messageThree, outMessageFour, outMessageFive)) != 0) {
+ WH_ERROR_PRINT("SHE LOAD KEY zero UID with wildcard: expected "
+ "success, got %d\n", ret);
+ goto exit;
+ }
+
+ if ((ret = _destroySheKey(client, SHE_WILDCARD_KEY_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey wildcard slot, ret=%d\n",
+ ret);
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE LOAD KEY UID checks SUCCESS\n");
+ }
+
+ /* === RND === */
+
+ if ((ret = wh_Client_SheInitRnd(client)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheInitRnd %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheRnd(client, key, &keySz)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheRnd %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheExtendSeed(client, entropy, sizeof(entropy))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheExtendSeed %d\n", ret);
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE RND SUCCESS\n");
+
+ /* === RAM key ECB/CBC/MAC round-trips === */
+
+ if ((ret = wh_Client_SheLoadPlainKey(client, key, sizeof(key))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheLoadPlainKey %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheEncEcb(client, WH_SHE_RAM_KEY_ID, plainText,
+ cipherText, sizeof(plainText))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheEncEcb %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheExportRamKey(client, messageOne, messageTwo,
+ messageThree, messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheExportRamKey %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheLoadKey(client, messageOne, messageTwo,
+ messageThree, messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheLoadKey %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheDecEcb(client, WH_SHE_RAM_KEY_ID, cipherText,
+ finalText, sizeof(cipherText))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheDecEcb %d\n", ret);
+ goto exit;
+ }
+ if (memcmp(finalText, plainText, sizeof(plainText)) != 0) {
+ ret = WH_ERROR_ABORTED;
+ WH_ERROR_PRINT("SHE ECB FAILED TO MATCH\n");
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE ECB SUCCESS\n");
+ if ((ret = wh_Client_SheEncCbc(client, WH_SHE_RAM_KEY_ID, iv, sizeof(iv),
+ plainText, cipherText, sizeof(plainText))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheEncCbc %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheDecCbc(client, WH_SHE_RAM_KEY_ID, iv, sizeof(iv),
+ cipherText, finalText, sizeof(cipherText))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheDecCbc %d\n", ret);
+ goto exit;
+ }
+ if (memcmp(finalText, plainText, sizeof(plainText)) != 0) {
+ ret = WH_ERROR_ABORTED;
+ WH_ERROR_PRINT("SHE CBC FAILED TO MATCH\n");
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE CBC SUCCESS\n");
+ if ((ret = wh_Client_SheGenerateMac(client, WH_SHE_RAM_KEY_ID, plainText,
+ sizeof(plainText), cipherText, sizeof(cipherText))) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheGenerateMac %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_Client_SheVerifyMac(client, WH_SHE_RAM_KEY_ID, plainText,
+ sizeof(plainText), cipherText, sizeof(cipherText), &sreg)) != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SheVerifyMac %d\n", ret);
+ goto exit;
+ }
+ if (sreg != 0) {
+ ret = WH_ERROR_ABORTED;
+ WH_ERROR_PRINT("SHE CMAC FAILED TO VERIFY\n");
+ goto exit;
+ }
+ WH_TEST_PRINT("SHE CMAC SUCCESS\n");
+
+ /* === Write protect === */
+
+ /* A key pre-programmed with WH_SHE_FLAG_WRITE_PROTECT cannot be
+ * overwritten via SHE LoadKey; the server must return
+ * WH_SHE_ERC_WRITE_PROTECTED. Reuses the secret key (auth) and the
+ * secure boot established above; uses a clean slot of its own. */
+ if ((ret = wh_Client_ShePreProgramKey(client, SHE_WP_KEY_ID,
+ WH_SHE_FLAG_WRITE_PROTECT,
+ vectorRawKey,
+ sizeof(vectorRawKey))) != 0) {
+ WH_ERROR_PRINT("Failed to pre-program write-protected key %d\n", ret);
+ goto exit;
+ }
+ if ((ret = wh_She_GenerateLoadableKey(SHE_WP_KEY_ID, WH_SHE_SECRET_KEY_ID,
+ 1, 0, sheUid, vectorRawKey, secretKey, messageOne, messageTwo,
+ messageThree, messageFour, messageFive)) != 0) {
+ WH_ERROR_PRINT("Failed to generate loadable key %d\n", ret);
+ goto exit;
+ }
+ ret = wh_Client_SheLoadKey(client, messageOne, messageTwo, messageThree,
+ messageFour, messageFive);
+ if (ret != WH_SHE_ERC_WRITE_PROTECTED) {
+ WH_ERROR_PRINT("Expected WH_SHE_ERC_WRITE_PROTECTED, got %d\n", ret);
+ ret = WH_ERROR_ABORTED;
+ goto exit;
+ }
+ ret = 0;
+ WH_TEST_PRINT("SHE write protect SUCCESS\n");
+
+ /* === Cleanup: destroy provisioned keys so we don't leak NVM === */
+
+ if ((ret = _destroySheKey(client, WH_SHE_BOOT_MAC_KEY_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+ if ((ret = _destroySheKey(client, WH_SHE_BOOT_MAC)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+ if ((ret = _destroySheKey(client, WH_SHE_SECRET_KEY_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+ if ((ret = _destroySheKey(client, WH_SHE_PRNG_SEED_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+ if ((ret = _destroySheKey(client, WH_SHE_MASTER_ECU_KEY_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+ if ((ret = _destroySheKey(client, SHE_TEST_VECTOR_KEY_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+ if ((ret = _destroySheKey(client, SHE_WP_KEY_ID)) != 0) {
+ WH_ERROR_PRINT("Failed to _destroySheKey, ret=%d\n", ret);
+ goto exit;
+ }
+
+exit:
+ return ret;
+}
+
+#endif /* WOLFHSM_CFG_SHE_EXTENSION && !WOLFHSM_CFG_NO_CRYPTO && \
+ WOLFHSM_CFG_ENABLE_CLIENT */
diff --git a/test-refactor/posix/wh_test_posix_server.c b/test-refactor/posix/wh_test_posix_server.c
index 8ced55d32..30ea32339 100644
--- a/test-refactor/posix/wh_test_posix_server.c
+++ b/test-refactor/posix/wh_test_posix_server.c
@@ -37,6 +37,10 @@
#include "wolfhsm/wh_comm.h"
#include "wolfhsm/wh_server.h"
+#ifdef WOLFHSM_CFG_SHE_EXTENSION
+#include "wolfhsm/wh_server_she.h"
+#endif
+
#ifndef WOLFHSM_CFG_NO_CRYPTO
#include "wolfssl/wolfcrypt/settings.h"
#include "wolfssl/wolfcrypt/random.h"
@@ -70,6 +74,11 @@ static whNvmContext _nvm;
static whServerCryptoContext _crypto;
#endif
+#ifdef WOLFHSM_CFG_SHE_EXTENSION
+/* SHE keystore/crypto state for the shared server (uidSet, sbState) */
+static whServerSheContext _she;
+#endif
+
/* Mem transport -- buffers and server-side state.
* The client side re-uses these buffers via
* whTestPosix_Server_GetTransportConfig. */
@@ -139,6 +148,11 @@ int whTestPosix_Server_Init(whServerContext* server)
sCfg.devId = INVALID_DEVID;
#endif
+#ifdef WOLFHSM_CFG_SHE_EXTENSION
+ memset(&_she, 0, sizeof(_she));
+ sCfg.she = &_she;
+#endif
+
return wh_Server_Init(server, &sCfg);
}
diff --git a/test-refactor/server/wh_test_she_server.c b/test-refactor/server/wh_test_she_server.c
new file mode 100644
index 000000000..c772f34fb
--- /dev/null
+++ b/test-refactor/server/wh_test_she_server.c
@@ -0,0 +1,482 @@
+/*
+ * 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_she_server.c
+ *
+ * Server-side SHE test suite. Exercises internal server SHE
+ * behavior through direct server API calls against the shared
+ * server context: the master ECU key metadata fallback and the
+ * per-action request-size validation in the SHE handlers.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#if defined(WOLFHSM_CFG_SHE_EXTENSION) && !defined(WOLFHSM_CFG_NO_CRYPTO)
+
+#include
+#include
+
+#include "wolfhsm/wh_common.h"
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_nvm.h"
+#include "wolfhsm/wh_server.h"
+#include "wolfhsm/wh_server_keystore.h"
+#include "wolfhsm/wh_server_she.h"
+#include "wolfhsm/wh_she_common.h"
+#include "wolfhsm/wh_message.h"
+#include "wolfhsm/wh_message_she.h"
+#include "wolfhsm/wh_comm.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+/* Value of WH_SHE_SB_SUCCESS from the wh_server_she.c internal enum.
+ * Mirrored here since the enum is private to that translation unit. */
+#define TEST_SHE_SB_STATE_SUCCESS 3
+
+
+/*
+ * Reading the master ECU key when it has never been provisioned must
+ * succeed and return an all-zero key with correctly populated metadata.
+ */
+int whTest_SheMasterEcuKeyFallback(whServerContext* server)
+{
+ int ret;
+ whNvmMetadata outMeta[1] = {0};
+ uint8_t keyBuf[WH_SHE_KEY_SZ] = {0};
+ uint32_t keySz = sizeof(keyBuf);
+ uint8_t zeros[WH_SHE_KEY_SZ] = {0};
+ whKeyId masterEcuKeyId;
+
+ if (server == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ masterEcuKeyId = WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id,
+ WH_SHE_MASTER_ECU_KEY_ID);
+
+ /* Fill keyBuf with non-zero to ensure it gets overwritten */
+ memset(keyBuf, 0xFF, sizeof(keyBuf));
+
+ ret = wh_Server_KeystoreReadKey(server, masterEcuKeyId, outMeta, keyBuf,
+ &keySz);
+
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(keySz == WH_SHE_KEY_SZ);
+ WH_TEST_ASSERT_RETURN(memcmp(keyBuf, zeros, WH_SHE_KEY_SZ) == 0);
+ WH_TEST_ASSERT_RETURN(outMeta->len == WH_SHE_KEY_SZ);
+ WH_TEST_ASSERT_RETURN(outMeta->id == masterEcuKeyId);
+
+ WH_TEST_PRINT("SHE master ECU key fallback metadata test SUCCESS\n");
+
+ return 0;
+}
+
+
+/*
+ * Test that SHE server handlers reject requests with an invalid
+ * req_size while still producing an action-specific response packet.
+ * Each handler is called directly via wh_Server_HandleSheRequest()
+ * with a realistic but incorrectly sized request packet.
+ */
+int whTest_SheReqSizeChecking(whServerContext* server)
+{
+ int ret = 0;
+ uint16_t req_size = 0;
+ uint16_t resp_size = 0;
+
+ /* Buffers for request and response packets */
+ uint8_t req_packet[WOLFHSM_CFG_COMM_DATA_LEN];
+ uint8_t resp_packet[WOLFHSM_CFG_COMM_DATA_LEN];
+
+ if (server == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /*
+ * Set SHE state so _ReportInvalidSheState allows requests through.
+ * WH_SHE_SET_UID always passes the state gate, but most other handlers
+ * require uidSet=1 and sbState=WH_SHE_SB_SUCCESS.
+ */
+ server->she->uidSet = 1;
+ server->she->sbState = TEST_SHE_SB_STATE_SUCCESS;
+
+ /*
+ * Test 1: WH_SHE_SET_UID with truncated request.
+ * Populate a valid UID in the packet, but pass req_size one byte short.
+ */
+ {
+ whMessageShe_SetUidRequest* req =
+ (whMessageShe_SetUidRequest*)req_packet;
+ whMessageShe_SetUidResponse* setUidResp =
+ (whMessageShe_SetUidResponse*)resp_packet;
+ memset(setUidResp, 0, sizeof(*setUidResp));
+ memset(req->uid, 0xAA, WH_SHE_UID_SZ);
+ req_size = sizeof(whMessageShe_SetUidRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_SET_UID, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*setUidResp));
+ WH_TEST_ASSERT_RETURN(setUidResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 2: WH_SHE_SECURE_BOOT_INIT with truncated request.
+ * Set a valid bootloader size, but pass req_size one byte short.
+ */
+ {
+ whMessageShe_SecureBootInitRequest* req =
+ (whMessageShe_SecureBootInitRequest*)req_packet;
+ whMessageShe_SecureBootInitResponse* secureBootInitResp =
+ (whMessageShe_SecureBootInitResponse*)resp_packet;
+ /* The state gate allows SECURE_BOOT_INIT through regardless of
+ * sbState; we are testing the size check which happens first. */
+ memset(secureBootInitResp, 0, sizeof(*secureBootInitResp));
+ req->sz = 256;
+ req_size = sizeof(whMessageShe_SecureBootInitRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_SECURE_BOOT_INIT, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*secureBootInitResp));
+ WH_TEST_ASSERT_RETURN(secureBootInitResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /* Secure boot failure resets sbState to SB_INIT, restore it */
+ server->she->sbState = TEST_SHE_SB_STATE_SUCCESS;
+
+ /*
+ * Test 3: WH_SHE_SECURE_BOOT_UPDATE with truncated fixed header.
+ * Set a valid chunk size but pass req_size smaller than the header.
+ */
+ {
+ whMessageShe_SecureBootUpdateRequest* req =
+ (whMessageShe_SecureBootUpdateRequest*)req_packet;
+ whMessageShe_SecureBootUpdateResponse* secureBootUpdateResp =
+ (whMessageShe_SecureBootUpdateResponse*)resp_packet;
+ memset(secureBootUpdateResp, 0, sizeof(*secureBootUpdateResp));
+ req->sz = 64;
+ req_size = sizeof(whMessageShe_SecureBootUpdateRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_SECURE_BOOT_UPDATE, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*secureBootUpdateResp));
+ WH_TEST_ASSERT_RETURN(secureBootUpdateResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /* Secure boot failure resets sbState to SB_INIT, restore it */
+ server->she->sbState = TEST_SHE_SB_STATE_SUCCESS;
+
+ /*
+ * Test 4: WH_SHE_SECURE_BOOT_FINISH expects no request body.
+ * Send a nonzero req_size to trigger the check.
+ */
+ {
+ whMessageShe_SecureBootFinishResponse* secureBootFinishResp =
+ (whMessageShe_SecureBootFinishResponse*)resp_packet;
+ memset(secureBootFinishResp, 0, sizeof(*secureBootFinishResp));
+ req_size = 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_SECURE_BOOT_FINISH, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*secureBootFinishResp));
+ WH_TEST_ASSERT_RETURN(secureBootFinishResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /* Secure boot failure resets sbState to SB_INIT, restore it */
+ server->she->sbState = TEST_SHE_SB_STATE_SUCCESS;
+
+ /*
+ * Test 5: WH_SHE_LOAD_KEY with truncated request.
+ * Fill M1/M2/M3 with nonzero data, pass req_size one byte short.
+ * _LoadKey maps the malformed request to an action-specific response;
+ * verify the request still completes with a response packet instead
+ * of failing the transport path.
+ */
+ {
+ whMessageShe_LoadKeyRequest* req =
+ (whMessageShe_LoadKeyRequest*)req_packet;
+ whMessageShe_LoadKeyResponse* loadKeyResp =
+ (whMessageShe_LoadKeyResponse*)resp_packet;
+ memset(loadKeyResp, 0, sizeof(*loadKeyResp));
+ memset(req->messageOne, 0x11, WH_SHE_M1_SZ);
+ memset(req->messageTwo, 0x22, WH_SHE_M2_SZ);
+ memset(req->messageThree, 0x33, WH_SHE_M3_SZ);
+ req_size = sizeof(whMessageShe_LoadKeyRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_LOAD_KEY, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*loadKeyResp));
+ WH_TEST_ASSERT_RETURN(loadKeyResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 6: WH_SHE_LOAD_PLAIN_KEY with truncated request.
+ * Fill a valid key, pass req_size one byte short.
+ */
+ {
+ whMessageShe_LoadPlainKeyRequest* req =
+ (whMessageShe_LoadPlainKeyRequest*)req_packet;
+ whMessageShe_LoadPlainKeyResponse* loadPlainKeyResp =
+ (whMessageShe_LoadPlainKeyResponse*)resp_packet;
+ memset(loadPlainKeyResp, 0, sizeof(*loadPlainKeyResp));
+ memset(req->key, 0xBB, WH_SHE_KEY_SZ);
+ req_size = sizeof(whMessageShe_LoadPlainKeyRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_LOAD_PLAIN_KEY, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*loadPlainKeyResp));
+ WH_TEST_ASSERT_RETURN(loadPlainKeyResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 7: WH_SHE_EXPORT_RAM_KEY expects no request body.
+ * Send a nonzero req_size to trigger the check.
+ */
+ {
+ whMessageShe_ExportRamKeyResponse* exportRamKeyResp =
+ (whMessageShe_ExportRamKeyResponse*)resp_packet;
+ memset(exportRamKeyResp, 0, sizeof(*exportRamKeyResp));
+ req_size = 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_EXPORT_RAM_KEY, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*exportRamKeyResp));
+ WH_TEST_ASSERT_RETURN(exportRamKeyResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 8: WH_SHE_INIT_RND expects no request body.
+ * Send a nonzero req_size to trigger the check.
+ */
+ {
+ whMessageShe_InitRngResponse* initRngResp =
+ (whMessageShe_InitRngResponse*)resp_packet;
+ memset(initRngResp, 0, sizeof(*initRngResp));
+ req_size = 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_INIT_RND, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*initRngResp));
+ WH_TEST_ASSERT_RETURN(initRngResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 9: WH_SHE_RND expects no request body.
+ * Send a nonzero req_size to trigger the check.
+ */
+ {
+ whMessageShe_RndResponse* rndResp =
+ (whMessageShe_RndResponse*)resp_packet;
+ memset(rndResp, 0, sizeof(*rndResp));
+ req_size = 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_RND, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*rndResp));
+ WH_TEST_ASSERT_RETURN(rndResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 10: WH_SHE_EXTEND_SEED with truncated request.
+ * Fill valid entropy data, pass req_size one byte short.
+ */
+ {
+ whMessageShe_ExtendSeedRequest* req =
+ (whMessageShe_ExtendSeedRequest*)req_packet;
+ whMessageShe_ExtendSeedResponse* extendSeedResp =
+ (whMessageShe_ExtendSeedResponse*)resp_packet;
+ memset(extendSeedResp, 0, sizeof(*extendSeedResp));
+ memset(req->entropy, 0xCC, WH_SHE_KEY_SZ);
+ req_size = sizeof(whMessageShe_ExtendSeedRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_EXTEND_SEED, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*extendSeedResp));
+ WH_TEST_ASSERT_RETURN(extendSeedResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 11: WH_SHE_ENC_ECB with valid header but truncated payload.
+ * Set sz to 16 (one AES block) but only include the header, no data.
+ */
+ {
+ whMessageShe_EncEcbRequest* req =
+ (whMessageShe_EncEcbRequest*)req_packet;
+ whMessageShe_EncEcbResponse* encEcbResp =
+ (whMessageShe_EncEcbResponse*)resp_packet;
+ memset(encEcbResp, 0, sizeof(*encEcbResp));
+ req->sz = 16;
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ req_size = sizeof(whMessageShe_EncEcbRequest);
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_ENC_ECB, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*encEcbResp));
+ WH_TEST_ASSERT_RETURN(encEcbResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 12: WH_SHE_ENC_ECB with truncated header.
+ * Pass req_size one byte short of the header struct.
+ */
+ {
+ whMessageShe_EncEcbRequest* req =
+ (whMessageShe_EncEcbRequest*)req_packet;
+ whMessageShe_EncEcbResponse* encEcbResp =
+ (whMessageShe_EncEcbResponse*)resp_packet;
+ memset(encEcbResp, 0, sizeof(*encEcbResp));
+ req->sz = 16;
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ req_size = sizeof(whMessageShe_EncEcbRequest) - 1;
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_ENC_ECB, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*encEcbResp));
+ WH_TEST_ASSERT_RETURN(encEcbResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 13: WH_SHE_ENC_CBC with valid header but truncated payload.
+ * Set sz to 16 (one AES block), fill a valid IV, but only include the
+ * header with no cipher data following it.
+ */
+ {
+ whMessageShe_EncCbcRequest* req =
+ (whMessageShe_EncCbcRequest*)req_packet;
+ whMessageShe_EncCbcResponse* encCbcResp =
+ (whMessageShe_EncCbcResponse*)resp_packet;
+ memset(encCbcResp, 0, sizeof(*encCbcResp));
+ req->sz = 16;
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ memset(req->iv, 0xDD, WH_SHE_KEY_SZ);
+ req_size = sizeof(whMessageShe_EncCbcRequest);
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_ENC_CBC, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*encCbcResp));
+ WH_TEST_ASSERT_RETURN(encCbcResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 14: WH_SHE_DEC_ECB with valid header but truncated payload.
+ */
+ {
+ whMessageShe_DecEcbRequest* req =
+ (whMessageShe_DecEcbRequest*)req_packet;
+ whMessageShe_DecEcbResponse* decEcbResp =
+ (whMessageShe_DecEcbResponse*)resp_packet;
+ memset(decEcbResp, 0, sizeof(*decEcbResp));
+ req->sz = 16;
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ req_size = sizeof(whMessageShe_DecEcbRequest);
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_DEC_ECB, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*decEcbResp));
+ WH_TEST_ASSERT_RETURN(decEcbResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 15: WH_SHE_DEC_CBC with valid header but truncated payload.
+ */
+ {
+ whMessageShe_DecCbcRequest* req =
+ (whMessageShe_DecCbcRequest*)req_packet;
+ whMessageShe_DecCbcResponse* decCbcResp =
+ (whMessageShe_DecCbcResponse*)resp_packet;
+ memset(decCbcResp, 0, sizeof(*decCbcResp));
+ req->sz = 16;
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ memset(req->iv, 0xEE, WH_SHE_KEY_SZ);
+ req_size = sizeof(whMessageShe_DecCbcRequest);
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_DEC_CBC, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*decCbcResp));
+ WH_TEST_ASSERT_RETURN(decCbcResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 16: WH_SHE_GEN_MAC with valid header but truncated payload.
+ * Set sz to 16 bytes of message data, but only pass the header.
+ */
+ {
+ whMessageShe_GenMacRequest* req =
+ (whMessageShe_GenMacRequest*)req_packet;
+ whMessageShe_GenMacResponse* genMacResp =
+ (whMessageShe_GenMacResponse*)resp_packet;
+ memset(genMacResp, 0, sizeof(*genMacResp));
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ req->sz = 16;
+ req_size = sizeof(whMessageShe_GenMacRequest);
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_GEN_MAC, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*genMacResp));
+ WH_TEST_ASSERT_RETURN(genMacResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /*
+ * Test 17: WH_SHE_VERIFY_MAC with valid header but truncated payload.
+ * Set messageLen=16 and macLen=16 but only pass the header.
+ */
+ {
+ whMessageShe_VerifyMacRequest* req =
+ (whMessageShe_VerifyMacRequest*)req_packet;
+ whMessageShe_VerifyMacResponse* verifyMacResp =
+ (whMessageShe_VerifyMacResponse*)resp_packet;
+ memset(verifyMacResp, 0, sizeof(*verifyMacResp));
+ req->keyId = WH_SHE_RAM_KEY_ID;
+ req->messageLen = 16;
+ req->macLen = 16;
+ req_size = sizeof(whMessageShe_VerifyMacRequest);
+ ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE,
+ WH_SHE_VERIFY_MAC, req_size,
+ req_packet, &resp_size, resp_packet);
+ WH_TEST_ASSERT_RETURN(ret == 0);
+ WH_TEST_ASSERT_RETURN(resp_size == sizeof(*verifyMacResp));
+ WH_TEST_ASSERT_RETURN(verifyMacResp->rc != WH_SHE_ERC_NO_ERROR);
+ }
+
+ /* Restore a clean SHE context so the poked uidSet/sbState don't
+ * leak into the live request loop the server enters next. */
+ memset(server->she, 0, sizeof(*server->she));
+
+ WH_TEST_PRINT("SHE req_size checking test SUCCESS\n");
+
+ return 0;
+}
+
+#endif /* WOLFHSM_CFG_SHE_EXTENSION && !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index ac29019a9..9ecc769eb 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -36,6 +36,8 @@ WH_TEST_DECL(whTest_Comm);
WH_TEST_DECL(whTest_Dma);
WH_TEST_DECL(whTest_KeystoreReqSize);
WH_TEST_DECL(whTest_CertVerify);
+WH_TEST_DECL(whTest_SheMasterEcuKeyFallback);
+WH_TEST_DECL(whTest_SheReqSizeChecking);
WH_TEST_DECL(whTest_ClientCerts);
WH_TEST_DECL(whTest_CryptoAes);
WH_TEST_DECL(whTest_CryptoEcc256);
@@ -43,6 +45,7 @@ WH_TEST_DECL(whTest_CryptoEd25519BufferTooSmall);
WH_TEST_DECL(whTest_CryptoMlDsaBufferTooSmall);
WH_TEST_DECL(whTest_CryptoRsaBufferTooSmall);
WH_TEST_DECL(whTest_CryptoSha256);
+WH_TEST_DECL(whTest_She);
WH_TEST_DECL(whTest_Echo);
WH_TEST_DECL(whTest_ServerInfo);
WH_TEST_DECL(whTest_WolfCryptTest);
@@ -56,6 +59,8 @@ const size_t whTestsMiscCount = sizeof(whTestsMisc) / sizeof(whTestsMisc[0]);
const whTestCase whTestsServer[] = {
{ "whTest_CertVerify", whTest_CertVerify },
+ { "whTest_SheMasterEcuKeyFallback", whTest_SheMasterEcuKeyFallback },
+ { "whTest_SheReqSizeChecking", whTest_SheReqSizeChecking },
};
const size_t whTestsServerCount = sizeof(whTestsServer) / sizeof(whTestsServer[0]);
@@ -68,6 +73,7 @@ const whTestCase whTestsClient[] = {
{ "whTest_CryptoMlDsaBufferTooSmall", whTest_CryptoMlDsaBufferTooSmall },
{ "whTest_CryptoRsaBufferTooSmall", whTest_CryptoRsaBufferTooSmall },
{ "whTest_CryptoSha256", whTest_CryptoSha256 },
+ { "whTest_She", whTest_She },
{ "whTest_Echo", whTest_Echo },
{ "whTest_ServerInfo", whTest_ServerInfo },
{ "whTest_WolfCryptTest", whTest_WolfCryptTest },
diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c
index da9434743..5bd5f0dc5 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -7304,6 +7304,68 @@ static int whTest_KeyCache(whClientContext* ctx, int devId, WC_RNG* rng)
}
#endif /* WOLFHSM_CFG_DMA */
+ /* Ensure cache entries read from NVM are evictable.
+ * Max out the cache with NVM-backed keys, then try caching a new key. */
+ if (ret == 0) {
+ const int nvmKeyCount = WOLFHSM_CFG_SERVER_KEYCACHE_COUNT;
+ /* {0} == WH_KEYID_ERASED, so unused entries are skipped on cleanup
+ * and each cache call requests a server-assigned id. */
+ uint16_t nvmKeyIds[WOLFHSM_CFG_SERVER_KEYCACHE_COUNT] = {0};
+ uint16_t extraKeyId = WH_KEYID_ERASED;
+
+ /* Commit each key to NVM then evict it, so the keys live only in
+ * the backing store and not in the cache. */
+ for (i = 0; (i < nvmKeyCount) && (ret == 0); i++) {
+ ret = wh_Client_KeyCache(ctx, 0, labelIn, sizeof(labelIn), key,
+ sizeof(key), &nvmKeyIds[i]);
+ if (ret == 0) {
+ ret = wh_Client_KeyCommit(ctx, nvmKeyIds[i]);
+ }
+ if (ret == 0) {
+ ret = wh_Client_KeyEvict(ctx, nvmKeyIds[i]);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to stage NVM key %d: %d\n", i, ret);
+ }
+ }
+
+ /* Read each key back, which caches it from NVM and fills the
+ * cache with NVM-backed entries. */
+ for (i = 0; (i < nvmKeyCount) && (ret == 0); i++) {
+ outLen = sizeof(keyOut);
+ ret = wh_Client_KeyExport(ctx, nvmKeyIds[i], labelOut,
+ sizeof(labelOut), keyOut, &outLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to read back NVM key %d: %d\n", i, ret);
+ }
+ }
+
+ /* With the cache full of NVM-backed keys, caching a new key must
+ * still succeed by evicting one of them. */
+ if (ret == 0) {
+ ret = wh_Client_KeyCache(ctx, 0, labelIn, sizeof(labelIn), key,
+ sizeof(key), &extraKeyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache key with NVM-backed cache "
+ "full: %d\n", ret);
+ }
+ }
+
+ /* Restore state regardless of the outcome above. */
+ if (extraKeyId != WH_KEYID_ERASED) {
+ (void)wh_Client_KeyEvict(ctx, extraKeyId);
+ }
+ for (i = 0; i < nvmKeyCount; i++) {
+ if (nvmKeyIds[i] != WH_KEYID_ERASED) {
+ (void)wh_Client_KeyErase(ctx, nvmKeyIds[i]);
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("KEY CACHE NVM-BACKED EVICTION SUCCESS\n");
+ }
+ }
+
return ret;
}