diff --git a/test-refactor/README.md b/test-refactor/README.md index 6de6046f9..f69c6338a 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -83,6 +83,7 @@ Translated tests: | `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto_{aes,cmac,curve25519,ecc,ed25519,kdf,keypolicy,mldsa,rng,rsa,sha}.c::whTest_Crypto_*` | Client | Split into per-algorithm suites; key revocation is gated by `WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS`. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_crypto.c::whTest_CryptoKeyUsagePolicies` (AES CTR/ECB/GCM subset) | `client-server/wh_test_crypto_aes.c::whTest_CryptoAesKeyUsagePolicies` | Client | AES-CTR/ECB/GCM key usage enforcement (non-DMA and DMA variants) | +| `wh_test_crypto.c::{whTest_KeyCache, whTest_NonExportableKeystore}` | `client-server/wh_test_crypto_keystore.c::whTest_Crypto_Keystore` | Client | Key-cache lifecycle (cache/export, evict, commit/erase, cross-cache eviction/replacement, NVM-backed eviction) and non-exportable-flag enforcement; std and DMA export paths. The `WOLFHSM_CFG_IS_TEST_SERVER` multi-client user-exclusion path is dropped (needs two client contexts) | | `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 | | `wh_test_wolfcrypt_test.c::whTest_WolfCryptTest` | `client-server/wh_test_wolfcrypt.c::whTest_WolfCryptTest` | Client | | | `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 | @@ -99,7 +100,6 @@ Not yet migrated (still live in `wolfHSM/test/`): | `wh_test_comm.c::whTest_Comm` | Pthread mem/tcp/shmem variants only; sequential mem variant has been ported | | `wh_test_clientserver.c::whTest_ClientServer` | Pthread variant: remaining client-side coverage (NVM ops, etc.) still needs to be split out as new tests. The sequential test is dropped | | `wh_test_crypto.c::whTest_Crypto` | Remaining crypto coverage not yet split out: the AES async family (comm-buffer `whTest_CryptoAesAsync`/`AesAsyncKat` + DMA `whTest_CryptoAesDmaAsync`/`AesDmaAsyncKat`, round-trip & KAT). ECC DMA export-public and the ML-DSA wolfCrypt-API path are now migrated. | -| `wh_test_crypto.c::whTest_KeyCache`, `whTest_NonExportableKeystore` | Keystore tests (key-cache lifecycle and non-exportable-flag enforcement) dispatched from the legacy `whTest_Crypto`. The per-algorithm suites use `wh_Client_KeyCache`, but these dedicated keystore tests are not yet split out. | | `wh_test_crypto_affinity.c::whTest_CryptoAffinity` | | | `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | | | `wh_test_multiclient.c::whTest_MultiClient` | | diff --git a/test-refactor/client-server/wh_test_crypto_keystore.c b/test-refactor/client-server/wh_test_crypto_keystore.c new file mode 100644 index 000000000..604026e3a --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_keystore.c @@ -0,0 +1,852 @@ +/* + * 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_crypto_keystore.c + * + * Key cache lifecycle and non-exportable-flag enforcement: + * _whTest_KeyCache - cache/export round-trip, evict, + * commit/erase, cross-cache eviction and + * replacement, and eviction with the cache + * full of NVM-backed keys (plus DMA variants + * when WOLFHSM_CFG_DMA) + * _whTest_NonExportableKeystore - confirm WH_NVM_FLAGS_NONEXPORTABLE keys + * cannot be exported while ordinary keys can + * (std and DMA export paths) + */ + +#include "wolfhsm/wh_settings.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/random.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define WH_TEST_KEYCACHE_KEYSIZE (16) +#define WH_TEST_KEYSTORE_TEST_SZ (32) + +/* Cache a key, export it back, and verify the label and key round-trip. */ +static int _whTest_CacheExportKey(whClientContext* ctx, whKeyId* inout_key_id, + uint8_t* label_in, uint8_t* label_out, + uint16_t label_len, uint8_t* key_in, + uint8_t* key_out, uint16_t key_len) +{ + int ret = 0; + uint16_t label_len_out = label_len; + uint16_t key_len_out = key_len; + whKeyId key_id_out = *inout_key_id; + + ret = wh_Client_KeyCache(ctx, 0, label_in, label_len, key_in, key_len, + &key_id_out); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyCache %d\n", ret); + } + else { + ret = wh_Client_KeyExport(ctx, key_id_out, label_out, label_len_out, + key_out, &key_len_out); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyExport %d\n", ret); + } + else { + if ((key_len_out != key_len) || + (memcmp(key_in, key_out, key_len_out) != 0) || + (memcmp(label_in, label_out, label_len) != 0)) { + ret = -1; + } + } + } + *inout_key_id = key_id_out; + return ret; +} + +#ifdef WOLFHSM_CFG_DMA +/* DMA variant of the cache/export round-trip helper. */ +static int _whTest_CacheExportKeyDma(whClientContext* ctx, + whKeyId* inout_key_id, uint8_t* label_in, + uint8_t* label_out, uint16_t label_len, + uint8_t* key_in, uint8_t* key_out, + uint16_t key_len) +{ + int ret = 0; + uint16_t label_len_out = label_len; + uint16_t key_len_out = key_len; + whKeyId key_id_out = *inout_key_id; + + ret = wh_Client_KeyCacheDma(ctx, 0, label_in, label_len, key_in, key_len, + &key_id_out); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyCacheDma %d\n", ret); + } + else { + ret = wh_Client_KeyExportDma(ctx, key_id_out, key_out, key_len_out, + label_out, label_len_out, &key_len_out); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyExportDma %d\n", ret); + } + else { + if ((key_len_out != key_len) || + (memcmp(key_in, key_out, key_len_out) != 0) || + (memcmp(label_in, label_out, label_len) != 0)) { + ret = -1; + } + } + } + *inout_key_id = key_id_out; + return ret; +} +#endif /* WOLFHSM_CFG_DMA */ + +static int _whTest_KeyCache(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + int i; + uint16_t outLen; + uint16_t keyId; + WC_RNG rng[1]; + uint8_t key[WH_TEST_KEYCACHE_KEYSIZE]; + uint8_t keyOut[WH_TEST_KEYCACHE_KEYSIZE] = {0}; + uint8_t labelIn[WH_NVM_LABEL_LEN] = "KeyCache Test Label"; + uint8_t labelOut[WH_NVM_LABEL_LEN] = {0}; + + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + /* Randomize inputs */ + ret = wc_RNG_GenerateBlock(rng, key, sizeof(key)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret); + } + + /* test regular cache/export */ + keyId = WH_KEYID_ERASED; + if (ret == 0) { + ret = _whTest_CacheExportKey(ctx, &keyId, labelIn, labelOut, + sizeof(labelIn), key, keyOut, sizeof(key)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to Test CacheExportKey %d\n", ret); + } + else { + WH_TEST_PRINT("KEY CACHE/EXPORT SUCCESS\n"); + } + } + + if (ret == 0) { + /* test evict for original client */ + ret = wh_Client_KeyEvict(ctx, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyEvict %d\n", ret); + } + else { + outLen = sizeof(keyOut); + ret = wh_Client_KeyExport(ctx, keyId, labelOut, sizeof(labelOut), + keyOut, &outLen); + if (ret != WH_ERROR_NOTFOUND) { + WH_ERROR_PRINT("Failed to not find evicted key %d\n", ret); + } + else { + WH_TEST_PRINT("KEY CACHE EVICT SUCCESS\n"); + ret = 0; + } + } + } + + if (ret == 0) { + /* test commit/erase */ + keyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCache(ctx, 0, labelIn, sizeof(labelIn), key, + sizeof(key), &keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyCache %d\n", ret); + } + else { + ret = wh_Client_KeyCommit(ctx, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyCommit %d\n", ret); + } + else { + ret = wh_Client_KeyEvict(ctx, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyEvict %d\n", ret); + } + else { + outLen = sizeof(keyOut); + ret = wh_Client_KeyExport(ctx, keyId, labelOut, + sizeof(labelOut), keyOut, &outLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyExport %d\n", + ret); + } + else { + if ((outLen != sizeof(key) || + (memcmp(key, keyOut, outLen) != 0) || + (memcmp(labelIn, labelOut, sizeof(labelIn))) != + 0)) { + WH_ERROR_PRINT("Failed to match committed key\n"); + ret = -1; + } + else { + /* verify commit isn't using new nvm objects */ + for (i = 0; i < WOLFHSM_CFG_NVM_OBJECT_COUNT; i++) { + ret = wh_Client_KeyCommit(ctx, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to over commit %d\n", + ret); + } + } + if (ret == 0) { + ret = wh_Client_KeyErase(ctx, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to erase key %d\n", + ret); + } + else { + outLen = sizeof(keyOut); + ret = wh_Client_KeyExport(ctx, keyId, + labelOut, sizeof(labelOut), keyOut, + &outLen); + if (ret != WH_ERROR_NOTFOUND) { + WH_ERROR_PRINT("Failed to not find " + "erased key\n"); + ret = -1; + } + else { + ret = 0; + } + } + } + } + } + } + } + } + if (ret == 0) { + WH_TEST_PRINT("KEY COMMIT/ERASE SUCCESS\n"); + } + } + + /* Test cross-cache key eviction and replacement */ + if (ret == 0) { + uint16_t crossKeyId; + /* Key for regular cache (<= WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE) */ + const uint16_t smallKeySize = WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE / 2; + uint8_t smallKey[WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE / 2]; + /* Key for big cache (> WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE) */ + const uint16_t bigKeySize = WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE + 100; + uint8_t bigKey[WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE + 100]; + + uint8_t labelSmall[WH_NVM_LABEL_LEN] = "Small Key Label"; + uint8_t labelBig[WH_NVM_LABEL_LEN] = "Big Key Label"; + + /* Buffer for exported key and metadata */ + uint8_t exportedKey[WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE + 100]; + uint8_t exportedLabel[WH_NVM_LABEL_LEN]; + uint16_t exportedKeySize; + + /* Initialize test keys with different data */ + memset(smallKey, 0xAA, sizeof(smallKey)); + memset(bigKey, 0xBB, sizeof(bigKey)); + + /* Test 1: Cache small key first, then cache same keyId with big key */ + crossKeyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCache(ctx, 0, labelSmall, sizeof(labelSmall), + smallKey, sizeof(smallKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache small key: %d\n", ret); + } + else { + /* Now cache big key with same keyId - should succeed and evict the + * small key */ + ret = wh_Client_KeyCache(ctx, 0, labelBig, sizeof(labelBig), bigKey, + sizeof(bigKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to cache big key (expected success): %d\n", ret); + } + else { + /* Verify the cached key is the big key by exporting it */ + exportedKeySize = sizeof(exportedKey); + ret = wh_Client_KeyExport(ctx, crossKeyId, exportedLabel, + sizeof(exportedLabel), exportedKey, + &exportedKeySize); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export key after cache: %d\n", + ret); + } + else { + /* Verify exported key matches the big key */ + if (exportedKeySize != bigKeySize || + memcmp(exportedKey, bigKey, bigKeySize) != 0) { + WH_ERROR_PRINT( + "Exported key data doesn't match big key\n"); + ret = -1; + } + /* Verify exported label matches the big key label */ + else if (memcmp(exportedLabel, labelBig, + sizeof(labelBig)) != 0) { + WH_ERROR_PRINT( + "Exported label doesn't match big key label\n"); + ret = -1; + } + else { + ret = 0; + } + } + /* Clean up */ + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to evict key: %d\n", ret); + } + } + else { + /* On error, try our best to clean up */ + (void)wh_Client_KeyEvict(ctx, crossKeyId); + } + + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + /* double evict should fail */ + ret = 0; + } + else { + WH_ERROR_PRINT("Double evict shouldn't succeed, " + "cross-cache duplication test failed\n"); + ret = -1; + } + } + } + } + + /* Test 2: Cache big key first, then cache same keyId with small key */ + if (ret == 0) { + crossKeyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCache(ctx, 0, labelBig, sizeof(labelBig), bigKey, + sizeof(bigKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache big key: %d\n", ret); + } + else { + /* Now cache small key with same keyId - should succeed and + * evict the big key */ + ret = wh_Client_KeyCache(ctx, 0, labelSmall, sizeof(labelSmall), + smallKey, sizeof(smallKey), + &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to cache small key (expected success): %d\n", + ret); + } + else { + /* Verify the cached key is the small key by exporting it */ + exportedKeySize = sizeof(exportedKey); + ret = wh_Client_KeyExport(ctx, crossKeyId, exportedLabel, + sizeof(exportedLabel), + exportedKey, &exportedKeySize); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to export key after cache: %d\n", ret); + } + else { + /* Verify exported key matches the small key */ + if (exportedKeySize != smallKeySize || + memcmp(exportedKey, smallKey, smallKeySize) != 0) { + WH_ERROR_PRINT( + "Exported key data doesn't match small key\n"); + ret = -1; + } + /* Verify exported label matches the small key label */ + else if (memcmp(exportedLabel, labelSmall, + sizeof(labelSmall)) != 0) { + WH_ERROR_PRINT("Exported label doesn't match small " + "key label\n"); + ret = -1; + } + else { + ret = 0; + } + } + /* Clean up */ + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to evict key: %d\n", ret); + } + } + else { + /* On error, try our best to clean up */ + (void)wh_Client_KeyEvict(ctx, crossKeyId); + } + + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + /* double evict should fail */ + ret = 0; + } + else { + WH_ERROR_PRINT( + "Double evict shouldn't succeed, " + "cross-cache duplication test failed\n"); + ret = -1; + } + } + } + } + } + + if (ret == 0) { + WH_TEST_PRINT("KEY CROSS-CACHE EVICTION AND REPLACEMENT SUCCESS\n"); + } + } + +#ifdef WOLFHSM_CFG_DMA + /* test cache/export using DMA */ + if (ret == 0) { + keyId = WH_KEYID_ERASED; + ret = _whTest_CacheExportKeyDma(ctx, &keyId, labelIn, labelOut, + sizeof(labelIn), key, keyOut, + sizeof(key)); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to Test CacheExportKeyDma %d\n", ret); + } + else { + ret = wh_Client_KeyEvict(ctx, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyEvict %d\n", ret); + } + else { + WH_TEST_PRINT("KEY CACHE/EXPORT DMA SUCCESS\n"); + } + } + } + + /* Test cross-cache key eviction and replacement with DMA */ + if (ret == 0) { + uint16_t crossKeyId; + /* Key for regular cache (<= WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE) */ + const uint16_t smallKeySize = WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE / 2; + uint8_t smallKey[WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE / 2]; + /* Key for big cache (> WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE) */ + const uint16_t bigKeySize = WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE + 100; + uint8_t bigKey[WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE + 100]; + + uint8_t labelSmall[WH_NVM_LABEL_LEN] = "Small DMA Key Label"; + uint8_t labelBig[WH_NVM_LABEL_LEN] = "Big DMA Key Label"; + + /* Buffer for exported key and metadata */ + uint8_t exportedKey[WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE + 100]; + uint8_t exportedLabel[WH_NVM_LABEL_LEN]; + uint16_t exportedKeySize; + + /* Initialize test keys with different data */ + memset(smallKey, 0xCC, sizeof(smallKey)); + memset(bigKey, 0xDD, sizeof(bigKey)); + + /* Test 1: Cache small key with DMA first, then cache same keyId + * with big key using DMA */ + crossKeyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCacheDma(ctx, 0, labelSmall, sizeof(labelSmall), + smallKey, sizeof(smallKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache small key with DMA: %d\n", ret); + } + else { + /* Now cache big key with same keyId using DMA - should succeed and + * evict the small key */ + ret = wh_Client_KeyCacheDma(ctx, 0, labelBig, sizeof(labelBig), + bigKey, sizeof(bigKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to cache big key with DMA (expected success): %d\n", + ret); + } + else { + /* Verify the cached key is the big key by exporting it */ + exportedKeySize = bigKeySize; + ret = wh_Client_KeyExportDma( + ctx, crossKeyId, exportedKey, exportedKeySize, exportedLabel, + sizeof(exportedLabel), &exportedKeySize); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export key after cache: %d\n", + ret); + } + else { + /* Verify exported key matches the big key */ + if (exportedKeySize != bigKeySize || + memcmp(exportedKey, bigKey, bigKeySize) != 0) { + WH_ERROR_PRINT( + "Exported key data doesn't match big key\n"); + ret = -1; + } + /* Verify exported label matches the big key label */ + else if (memcmp(exportedLabel, labelBig, + sizeof(labelBig)) != 0) { + WH_ERROR_PRINT( + "Exported label doesn't match big key label\n"); + ret = -1; + } + else { + ret = 0; + } + } + /* Clean up */ + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to evict key: %d\n", ret); + } + } + else { + /* On error, try our best to clean up */ + (void)wh_Client_KeyEvict(ctx, crossKeyId); + } + + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + /* double evict should fail */ + ret = 0; + } + else { + WH_ERROR_PRINT("Double evict shouldn't succeed, " + "cross-cache duplication test failed\n"); + ret = -1; + } + } + } + } + + /* Test 2: Cache big key with DMA first, then cache + * same keyId with small key using DMA */ + if (ret == 0) { + crossKeyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCacheDma(ctx, 0, labelBig, sizeof(labelBig), + bigKey, sizeof(bigKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache big key with DMA: %d\n", ret); + } + else { + /* Now cache small key with same keyId using DMA - should + * succeed and evict the big key */ + ret = wh_Client_KeyCacheDma(ctx, 0, labelSmall, + sizeof(labelSmall), smallKey, + sizeof(smallKey), &crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache small key with DMA " + "(expected success): %d\n", + ret); + } + else { + /* Verify the cached key is the small key by exporting it */ + exportedKeySize = smallKeySize; + ret = wh_Client_KeyExportDma( + ctx, crossKeyId, exportedKey, exportedKeySize, + exportedLabel, sizeof(exportedLabel), &exportedKeySize); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to export key after cache: %d\n", ret); + } + else { + /* Verify exported key matches the small key */ + if (exportedKeySize != smallKeySize || + memcmp(exportedKey, smallKey, smallKeySize) != 0) { + WH_ERROR_PRINT( + "Exported key data doesn't match small key\n"); + ret = -1; + } + /* Verify exported label matches the small key label */ + else if (memcmp(exportedLabel, labelSmall, + sizeof(labelSmall)) != 0) { + WH_ERROR_PRINT("Exported label doesn't match small " + "key label\n"); + ret = -1; + } + else { + ret = 0; + } + } + /* Clean up */ + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to evict key: %d\n", ret); + } + } + else { + /* On error, try our best to clean up */ + (void)wh_Client_KeyEvict(ctx, crossKeyId); + } + + if (ret == 0) { + ret = wh_Client_KeyEvict(ctx, crossKeyId); + if (ret != 0) { + /* double evict should fail */ + ret = 0; + } + else { + WH_ERROR_PRINT( + "Double evict shouldn't succeed, " + "cross-cache duplication test failed\n"); + ret = -1; + } + } + } + } + } + + if (ret == 0) { + WH_TEST_PRINT( + "KEY CROSS-CACHE EVICTION AND REPLACEMENT DMA SUCCESS\n"); + } + } +#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"); + } + } + + (void)wc_FreeRng(rng); + return ret; +} + +static int _whTest_NonExportableKeystore(whClientContext* ctx) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + uint8_t key[WH_TEST_KEYSTORE_TEST_SZ] = { + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00}; + uint8_t exportedKey[WH_TEST_KEYSTORE_TEST_SZ] = {0}; + uint8_t label[WH_NVM_LABEL_LEN] = "NonExportableTestKey"; + uint8_t exportedLabel[WH_NVM_LABEL_LEN] = {0}; + uint16_t exportedKeySize; + + WH_TEST_PRINT("Testing non-exportable keystore enforcement...\n"); + + /* Test 1: Cache a key with non-exportable flag and try to export it */ + ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONEXPORTABLE, label, + sizeof(label), key, sizeof(key), &keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache non-exportable key: %d\n", ret); + return ret; + } + + /* Try to export the non-exportable key - should fail */ + exportedKeySize = sizeof(exportedKey); + ret = wh_Client_KeyExport(ctx, keyId, exportedLabel, sizeof(exportedLabel), + exportedKey, &exportedKeySize); + if (ret != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("Non-exportable key was exported unexpectedly: %d\n", + ret); + return -1; + } + + WH_TEST_DEBUG_PRINT("Non-exportable key export correctly denied\n"); + + /* Clean up the key */ + wh_Client_KeyEvict(ctx, keyId); + + /* Test 2: Cache a key without non-exportable flag and verify it can be + * exported */ + keyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONE, label, sizeof(label), key, + sizeof(key), &keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache exportable key: %d\n", ret); + return ret; + } + + /* Try to export the exportable key - should succeed */ + exportedKeySize = sizeof(exportedKey); + ret = wh_Client_KeyExport(ctx, keyId, exportedLabel, sizeof(exportedLabel), + exportedKey, &exportedKeySize); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export exportable key: %d\n", ret); + return ret; + } + + /* Verify exported data matches original */ + if (exportedKeySize != sizeof(key) || + memcmp(key, exportedKey, exportedKeySize) != 0 || + memcmp(label, exportedLabel, sizeof(label)) != 0) { + WH_ERROR_PRINT("Exported key data doesn't match original\n"); + return -1; + } + + WH_TEST_DEBUG_PRINT("Exportable key export succeeded\n"); + + /* Clean up */ + wh_Client_KeyEvict(ctx, keyId); + +#ifdef WOLFHSM_CFG_DMA + /* Test 3: Test DMA export with non-exportable key */ + WH_TEST_PRINT("Testing DMA key export protection...\n"); + + /* Cache a key with non-exportable flag */ + keyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONEXPORTABLE, label, + sizeof(label), key, sizeof(key), &keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache non-exportable key for DMA test: %d\n", + ret); + return ret; + } + + /* Try to export the non-exportable key via DMA - should fail */ + exportedKeySize = sizeof(exportedKey); + ret = wh_Client_KeyExportDma(ctx, keyId, exportedKey, sizeof(exportedKey), + exportedLabel, sizeof(exportedLabel), + &exportedKeySize); + if (ret != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "Non-exportable key was exported via DMA unexpectedly: %d\n", ret); + return -1; + } + + WH_TEST_DEBUG_PRINT("Non-exportable key DMA export correctly denied\n"); + + /* Clean up the key */ + wh_Client_KeyEvict(ctx, keyId); + + /* Test 4: Test DMA export with exportable key */ + keyId = WH_KEYID_ERASED; + ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONE, label, sizeof(label), key, + sizeof(key), &keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache exportable key for DMA test: %d\n", + ret); + return ret; + } + + /* Try to export the exportable key via DMA - should succeed */ + memset(exportedKey, 0, sizeof(exportedKey)); + memset(exportedLabel, 0, sizeof(exportedLabel)); + exportedKeySize = sizeof(exportedKey); + ret = wh_Client_KeyExportDma(ctx, keyId, exportedKey, sizeof(exportedKey), + exportedLabel, sizeof(exportedLabel), + &exportedKeySize); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export exportable key via DMA: %d\n", ret); + return ret; + } + + /* Verify exported data matches original */ + if (exportedKeySize != sizeof(key) || + memcmp(key, exportedKey, exportedKeySize) != 0 || + memcmp(label, exportedLabel, sizeof(label)) != 0) { + WH_ERROR_PRINT("DMA exported key data doesn't match original\n"); + return -1; + } + + WH_TEST_DEBUG_PRINT("Exportable key DMA export succeeded\n"); + + /* Clean up */ + wh_Client_KeyEvict(ctx, keyId); +#endif /* WOLFHSM_CFG_DMA */ + + WH_TEST_PRINT("NON-EXPORTABLE KEYSTORE TEST SUCCESS\n"); + return 0; +} + +int whTest_Crypto_Keystore(whClientContext* ctx) +{ + /* A preceding suite may leave the DMA-preferred dispatch mode set; reset + * to the std path so this suite runs the same way in every config. */ + (void)wh_Client_SetDmaMode(ctx, 0); + WH_TEST_RETURN_ON_FAIL(_whTest_KeyCache(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_NonExportableKeystore(ctx)); + return 0; +} + +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index a7ff07128..e7a850128 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -51,6 +51,7 @@ WH_TEST_DECL(whTest_Crypto_Ecc); WH_TEST_DECL(whTest_Crypto_Ed25519); WH_TEST_DECL(whTest_Crypto_Kdf); WH_TEST_DECL(whTest_Crypto_KeyPolicy); +WH_TEST_DECL(whTest_Crypto_Keystore); WH_TEST_DECL(whTest_Crypto_MlDsa); WH_TEST_DECL(whTest_Crypto_Rng); WH_TEST_DECL(whTest_Crypto_Rsa); @@ -91,6 +92,7 @@ const whTestCase whTestsClient[] = { { "whTest_Crypto_Ed25519", whTest_Crypto_Ed25519 }, { "whTest_Crypto_Kdf", whTest_Crypto_Kdf }, { "whTest_Crypto_KeyPolicy", whTest_Crypto_KeyPolicy }, + { "whTest_Crypto_Keystore", whTest_Crypto_Keystore }, { "whTest_Crypto_MlDsa", whTest_Crypto_MlDsa }, { "whTest_Crypto_Rng", whTest_Crypto_Rng }, { "whTest_Crypto_Rsa", whTest_Crypto_Rsa },