From 69e54762c95496dd77602cad66a05c94fab31bc4 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Wed, 15 Jul 2026 15:53:46 -0700 Subject: [PATCH 1/2] Add refactored tests for keywrap, coverage matches --- test-refactor/README.md | 2 +- test-refactor/client-server/wh_test_keywrap.c | 412 ++++++++++++++++++ test-refactor/wh_test_list.c | 2 + 3 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 test-refactor/client-server/wh_test_keywrap.c diff --git a/test-refactor/README.md b/test-refactor/README.md index 12ed3ce17..d8af26daf 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -100,6 +100,7 @@ Translated tests: | `wh_test_auth.c` (`whTest_AuthMEM` / `whTest_AuthTest` sub-tests) | `client-server/wh_test_auth.c::{whTest_AuthBadArgs, whTest_AuthLogin, whTest_AuthLogout, whTest_AuthAddUser, whTest_AuthDeleteUser, whTest_AuthSetPermissions, whTest_AuthSetCredentials, whTest_AuthRequestAuthorization}` | Client | Under `WOLFHSM_CFG_ENABLE_AUTHENTICATION` the POSIX server installs an auth context + admin user and the client logs in as admin at connect, so the ordinary client tests run authorized; each auth test brackets its own session (logout to start clean, restore admin on exit). Uses the blocking client API; the legacy own-server setup and single-thread manual-pump are dropped. Build with `make AUTH=1`. The TCP/client-only variant (`whTest_AuthTCP`) is not ported | | `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` | +| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | `client-server/wh_test_keywrap.c::whTest_KeyWrap` | Client | Caches a test KEK (`WH_NVM_FLAGS_USAGE_WRAP`) then runs the AES-GCM wrap path: wrap a random key, unwrap-and-cache it, AES-GCM round trip with the cached key, unwrap-and-export and match key + metadata, plus the data-wrap round trip and the key/data unwrap underflow bad-args checks. Gated on `WOLFHSM_CFG_KEYWRAP`; the legacy own-server/auth setup is dropped since the harness supplies a connected, authorized client. Covers the software-KEK path only; the legacy hardware-KEK keywrap suite (`whTest_Client_HwKeystore`) is not part of this test -- it lives in `misc/wh_test_hwkeystore.c` (wrap/data round trips and keystore rejections) and `server/wh_test_hwkeystore_server.c` (crypto-use rejection) | Not yet migrated (still live in `wolfHSM/test/`): @@ -110,7 +111,6 @@ Not yet migrated (still live in `wolfHSM/test/`): | `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_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_timeout.c::whTest_TimeoutPosix` | | | `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | | diff --git a/test-refactor/client-server/wh_test_keywrap.c b/test-refactor/client-server/wh_test_keywrap.c new file mode 100644 index 000000000..c0a215749 --- /dev/null +++ b/test-refactor/client-server/wh_test_keywrap.c @@ -0,0 +1,412 @@ +/* + * 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_keywrap.c + * + * Key-wrap and data-wrap lifecycle against the shared client group: + * _AesGcm_TestKeyWrap - wrap a random key under the server + * KEK, unwrap-and-cache it, use the + * cached key for an AES-GCM round trip, + * then unwrap-and-export and confirm the + * key and metadata match the originals + * _AesGcm_TestKeyUnwrapUnderflow - tiny wrapped-key sizes must return + * WH_ERROR_BADARGS, not underflow + * _AesGcm_TestDataWrap - wrap and unwrap opaque data, confirm + * the round trip preserves the bytes + * _AesGcm_TestDataUnwrapUnderflow- tiny wrapped-data sizes must return + * WH_ERROR_BADARGS, not underflow + * + * The shared port server provisions no KEK, so each entry point caches a + * test KEK first and evicts it on exit. The legacy own-server/auth setup + * is dropped; the harness supplies a connected, authorized client. + */ + +#include "wolfhsm/wh_settings.h" + +#include +#include /* For memset, memcpy */ + +#if defined(WOLFHSM_CFG_KEYWRAP) && defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ + !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/aes.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" + +/* Common defines */ +#define WH_TEST_KEKID 10 + +/* AES GCM Specific defines */ +#ifdef HAVE_AESGCM + +#define WH_TEST_AESGCM_KEYID 20 +#define WH_TEST_AES_KEYSIZE 32 +#define WH_TEST_AES_WRAPPED_KEYSIZE \ + (WH_KEYWRAP_AES_GCM_HEADER_SIZE + WH_TEST_AES_KEYSIZE + \ + sizeof(whNvmMetadata)) + +#endif /* HAVE_AESGCM */ + +static int _InitServerKek(whClientContext* client) +{ + /* IMPORTANT NOTE: Server KEK is typically intrinsic or set during + * provisioning. Uploading the KEK via the client is for testing purposes + * only and not intended as a recommendation */ + whKeyId serverKeyId = WH_TEST_KEKID; + whNvmFlags flags = WH_NVM_FLAGS_NONEXPORTABLE | WH_NVM_FLAGS_USAGE_WRAP; + uint8_t label[WH_NVM_LABEL_LEN] = "Server KEK key"; + uint8_t kek[] = {0x03, 0x03, 0x0d, 0xd9, 0xeb, 0x18, 0x17, 0x2e, + 0x06, 0x6e, 0x19, 0xce, 0x98, 0x44, 0x54, 0x0d, + 0x78, 0xa0, 0xbe, 0xe7, 0x35, 0x43, 0x40, 0xa4, + 0x22, 0x8a, 0xd1, 0x0e, 0xa3, 0x63, 0x1c, 0x0b}; + + return wh_Client_KeyCache(client, flags, label, sizeof(label), kek, + sizeof(kek), &serverKeyId); +} + +static int _CleanupServerKek(whClientContext* client) +{ + return wh_Client_KeyEvict(client, WH_TEST_KEKID); +} + +#ifdef HAVE_AESGCM + +static int _AesGcm_TestKeyWrap(whClientContext* client, WC_RNG* rng) +{ + + int ret = 0; + uint8_t plainKey[WH_TEST_AES_KEYSIZE]; + uint8_t tmpPlainKey[WH_TEST_AES_KEYSIZE]; + uint16_t tmpPlainKeySz = sizeof(tmpPlainKey); + uint8_t wrappedKey[WH_TEST_AES_WRAPPED_KEYSIZE]; + uint16_t wrappedKeySz = sizeof(wrappedKey); + whKeyId wrappedKeyId = WH_KEYID_ERASED; + whNvmMetadata metadata = { + .id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, + WH_TEST_AESGCM_KEYID), + .label = "AES Key Label", + .len = WH_TEST_AES_KEYSIZE, + .flags = WH_NVM_FLAGS_USAGE_ANY, + }; + whNvmMetadata tmpMetadata = {0}; + + Aes aes[1]; + const uint8_t plaintext[] = "hello, wolfSSL AES-GCM!"; + uint8_t ciphertext[sizeof(plaintext)]; + uint8_t decrypted[sizeof(plaintext)]; + + uint8_t tag[WH_KEYWRAP_AES_GCM_TAG_SIZE]; + uint8_t iv[WH_KEYWRAP_AES_GCM_IV_SIZE]; + const uint8_t aad[] = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2}; + + + ret = wc_RNG_GenerateBlock(rng, plainKey, sizeof(plainKey)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock for key data %d\n", ret); + return ret; + } + + ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, plainKey, + sizeof(plainKey), &metadata, wrappedKey, + &wrappedKeySz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_AesGcmKeyWrap %d\n", ret); + return ret; + } + + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + wrappedKey, wrappedKeySz, &wrappedKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_AesGcmKeyWrapCache %d\n", ret); + return ret; + } + + /* Initialize AES context */ + ret = wc_AesInit(aes, NULL, WH_CLIENT_DEVID(client)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesInit %d\n", ret); + return ret; + } + + ret = + wh_Client_AesSetKeyId(aes, WH_CLIENT_KEYID_MAKE_WRAPPED(wrappedKeyId)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_AesSetKeyId %d\n", ret); + return ret; + } + + /* Generate a random IV */ + ret = wc_RNG_GenerateBlock(rng, iv, sizeof(iv)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock for AES-GCM key %d\n", + ret); + return ret; + } + + /* Request the server to encrypt some data using the + * unwrapped and cached key via the key ID */ + ret = wc_AesGcmEncrypt(aes, ciphertext, plaintext, sizeof(plaintext), iv, + sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesGcmEncrypt %d\n", ret); + return ret; + } + + /* Request the server to decrypt the encrypted data using the + * unwrapped and cached key via the key ID */ + ret = wc_AesGcmDecrypt(aes, decrypted, /* out */ + ciphertext, sizeof(ciphertext), /* in, inLen */ + iv, sizeof(iv), /* iv, ivLen */ + tag, sizeof(tag), /* authTag, authTagSz */ + aad, sizeof(aad)); /* authIn (AAD), authInSz */ + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesGcmDecrypt %d\n", ret); + return ret; + } + + /* Check if the decrypted data matches an expected value */ + if (memcmp(decrypted, plaintext, sizeof(decrypted)) != 0) { + WH_ERROR_PRINT("Decrypted value does not match expected value\n"); + return -1; + } + + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + wrappedKey, wrappedKeySz, &tmpMetadata, + tmpPlainKey, &tmpPlainKeySz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyUnwrapAndExport %d\n", ret); + return ret; + } + + if (memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { + WH_ERROR_PRINT("AES GCM wrap/unwrap key failed to match\n"); + return -1; + } + + if (memcmp(&metadata, &tmpMetadata, sizeof(metadata)) != 0) { + WH_ERROR_PRINT("AES GCM wrap/unwrap metadata failed to match\n"); + return -1; + } + + /* Cache a local key using the same numeric ID to confirm coexistence */ + { + whKeyId localKeyId = WH_TEST_AESGCM_KEYID; + uint8_t localLabel[WH_NVM_LABEL_LEN] = "LocalKeySameId"; + const uint8_t localKey[WH_TEST_AES_KEYSIZE] = {0}; + + ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE, localLabel, + (uint16_t)sizeof("LocalKeySameId"), + (uint8_t*)localKey, sizeof(localKey), + &localKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache local key with shared ID %d\n", ret); + return ret; + } + if (localKeyId != WH_TEST_AESGCM_KEYID) { + WH_ERROR_PRINT("Local key ID mismatch (expected %u, got %u)\n", + WH_TEST_AESGCM_KEYID, localKeyId); + return WH_ERROR_ABORTED; + } + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, localKeyId)); + } + + wh_Client_KeyEvict(client, wrappedKeyId); + wc_AesFree(aes); + + return ret; +} + +static int _AesGcm_TestDataWrap(whClientContext* client) +{ + int ret = 0; + uint8_t data[] = "Example data!"; + uint8_t unwrappedData[sizeof(data)] = {0}; + uint32_t unwrappedDataSz = sizeof(unwrappedData); + uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE] = {0}; + uint32_t wrappedDataSz = sizeof(wrappedData); + + ret = wh_Client_DataWrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, data, + sizeof(data), wrappedData, &wrappedDataSz); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to wh_Client_DataWrap %d\n", ret); + return ret; + } + + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + wrappedData, sizeof(wrappedData), unwrappedData, + &unwrappedDataSz); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to wh_Client_DataUnwrap %d\n", ret); + return ret; + } + + if (memcmp(data, unwrappedData, sizeof(data)) != 0) { + WH_ERROR_PRINT("Unwrapped data failed to match input data\n"); + return -1; + } + + return ret; +} + +static int _AesGcm_TestKeyUnwrapUnderflow(whClientContext* client) +{ + int ret; + uint8_t dummyBuf[1] = {0}; + whNvmMetadata tmpMetadata = {0}; + uint8_t tmpKey[WH_TEST_AES_KEYSIZE] = {0}; + uint16_t tmpKeySz = sizeof(tmpKey); + whKeyId wrappedKeyId = WH_KEYID_ERASED; + + /* wrappedKeySz=0: must return WH_ERROR_BADARGS, not underflow */ + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + dummyBuf, 0, &tmpMetadata, tmpKey, + &tmpKeySz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndExport(sz=0) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + /* wrappedKeySz=1: must return WH_ERROR_BADARGS, not underflow */ + tmpKeySz = sizeof(tmpKey); + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + dummyBuf, 1, &tmpMetadata, tmpKey, + &tmpKeySz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndExport(sz=1) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + /* wrappedKeySz=0: test KeyUnwrapAndCache path */ + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + dummyBuf, 0, &wrappedKeyId); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndCache(sz=0) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + /* wrappedKeySz=1: test KeyUnwrapAndCache path */ + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + dummyBuf, 1, &wrappedKeyId); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndCache(sz=1) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + return WH_ERROR_OK; +} + +static int _AesGcm_TestDataUnwrapUnderflow(whClientContext* client) +{ + int ret; + uint8_t dummyBuf[1] = {0}; + uint8_t outBuf[32] = {0}; + uint32_t outSz = sizeof(outBuf); + + /* wrappedDataSz=0: must return WH_ERROR_BADARGS, not underflow */ + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + dummyBuf, 0, outBuf, &outSz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("DataUnwrap(sz=0) expected BADARGS, got %d\n", ret); + return WH_TEST_FAIL; + } + + /* wrappedDataSz=1: must return WH_ERROR_BADARGS, not underflow */ + outSz = sizeof(outBuf); + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, + dummyBuf, 1, outBuf, &outSz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("DataUnwrap(sz=1) expected BADARGS, got %d\n", ret); + return WH_TEST_FAIL; + } + + return WH_ERROR_OK; +} + +#endif /* HAVE_AESGCM */ + +int whTest_KeyWrap(whClientContext* client) +{ + int ret = 0; + WC_RNG rng[1]; + + if (client == NULL) { + return WH_ERROR_BADARGS; + } + + WH_TEST_RETURN_ON_FAIL(_InitServerKek(client)); + + ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(client)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + (void)_CleanupServerKek(client); + return ret; + } + +#ifdef HAVE_AESGCM + ret = _AesGcm_TestKeyWrap(client, rng); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _AesGcm_TestKeyWrap %d\n", ret); + } + + if (ret == WH_ERROR_OK) { + ret = _AesGcm_TestKeyUnwrapUnderflow(client); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _AesGcm_TestKeyUnwrapUnderflow %d\n", ret); + } + } + + if (ret == WH_ERROR_OK) { + ret = _AesGcm_TestDataWrap(client); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _AesGcm_TestDataWrap %d\n", ret); + } + } + + if (ret == WH_ERROR_OK) { + ret = _AesGcm_TestDataUnwrapUnderflow(client); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _AesGcm_TestDataUnwrapUnderflow %d\n", + ret); + } + } +#endif /* HAVE_AESGCM */ + + (void)_CleanupServerKek(client); + (void)wc_FreeRng(rng); + + return ret; +} + +#endif /* WOLFHSM_CFG_KEYWRAP && WOLFHSM_CFG_ENABLE_CLIENT && \ + !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index a5148c209..735256e4b 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -82,6 +82,7 @@ WH_TEST_DECL(whTest_NvmDma); WH_TEST_DECL(whTest_NvmOps); WH_TEST_DECL(whTest_ServerInfo); WH_TEST_DECL(whTest_WolfCryptTest); +WH_TEST_DECL(whTest_KeyWrap); WH_TEST_DECL(whTest_AuthBadArgs); WH_TEST_DECL(whTest_AuthLogin); WH_TEST_DECL(whTest_AuthLogout); @@ -142,6 +143,7 @@ const whTestCase whTestsClient[] = { {"whTest_NvmOps", whTest_NvmOps}, {"whTest_ServerInfo", whTest_ServerInfo}, {"whTest_WolfCryptTest", whTest_WolfCryptTest}, + {"whTest_KeyWrap", whTest_KeyWrap}, {"whTest_AuthBadArgs", whTest_AuthBadArgs}, {"whTest_AuthLogin", whTest_AuthLogin}, {"whTest_AuthLogout", whTest_AuthLogout}, From bc569a5f5a677ef42c79fbc0b690142e56e56d88 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 16 Jul 2026 17:11:59 -0700 Subject: [PATCH 2/2] Cleanup, merge two keywrap files --- test-refactor/README.md | 2 +- .../client-server/wh_test_crypto_keywrap.c | 403 ++++++++++++++++- test-refactor/client-server/wh_test_keywrap.c | 412 ------------------ test-refactor/wh_test_list.c | 2 - 4 files changed, 391 insertions(+), 428 deletions(-) delete mode 100644 test-refactor/client-server/wh_test_keywrap.c diff --git a/test-refactor/README.md b/test-refactor/README.md index d8af26daf..4e3aad0c2 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -100,7 +100,7 @@ Translated tests: | `wh_test_auth.c` (`whTest_AuthMEM` / `whTest_AuthTest` sub-tests) | `client-server/wh_test_auth.c::{whTest_AuthBadArgs, whTest_AuthLogin, whTest_AuthLogout, whTest_AuthAddUser, whTest_AuthDeleteUser, whTest_AuthSetPermissions, whTest_AuthSetCredentials, whTest_AuthRequestAuthorization}` | Client | Under `WOLFHSM_CFG_ENABLE_AUTHENTICATION` the POSIX server installs an auth context + admin user and the client logs in as admin at connect, so the ordinary client tests run authorized; each auth test brackets its own session (logout to start clean, restore admin on exit). Uses the blocking client API; the legacy own-server setup and single-thread manual-pump are dropped. Build with `make AUTH=1`. The TCP/client-only variant (`whTest_AuthTCP`) is not ported | | `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` | -| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | `client-server/wh_test_keywrap.c::whTest_KeyWrap` | Client | Caches a test KEK (`WH_NVM_FLAGS_USAGE_WRAP`) then runs the AES-GCM wrap path: wrap a random key, unwrap-and-cache it, AES-GCM round trip with the cached key, unwrap-and-export and match key + metadata, plus the data-wrap round trip and the key/data unwrap underflow bad-args checks. Gated on `WOLFHSM_CFG_KEYWRAP`; the legacy own-server/auth setup is dropped since the harness supplies a connected, authorized client. Covers the software-KEK path only; the legacy hardware-KEK keywrap suite (`whTest_Client_HwKeystore`) is not part of this test -- it lives in `misc/wh_test_hwkeystore.c` (wrap/data round trips and keystore rejections) and `server/wh_test_hwkeystore_server.c` (crypto-use rejection) | +| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | `client-server/wh_test_crypto_keywrap.c::whTest_Crypto_KeyWrap` | Client | Merged into the existing keywrap suite rather than a second file. Caches a test KEK (`WH_NVM_FLAGS_USAGE_WRAP`) then runs the AES-GCM wrap path: wrap a random key, unwrap-and-cache it, AES-GCM round trip with the cached key, unwrap-and-export and match key + metadata, plus the data-wrap round trip and the key/data unwrap underflow bad-args checks. `_AesGcm_TestKeyWrapNoNvm` maps to the suite's pre-existing `_whTest_CryptoKeyWrapNoNvmPersist`. Gated on `WOLFHSM_CFG_KEYWRAP` and `HAVE_AESGCM`; the legacy own-server/auth setup is dropped since the harness supplies a connected, authorized client. Covers the software-KEK path only; the legacy hardware-KEK keywrap suite (`whTest_Client_HwKeystore`) is not part of this test -- it lives in `misc/wh_test_hwkeystore.c` (wrap/data round trips and keystore rejections) and `server/wh_test_hwkeystore_server.c` (crypto-use rejection) | Not yet migrated (still live in `wolfHSM/test/`): diff --git a/test-refactor/client-server/wh_test_crypto_keywrap.c b/test-refactor/client-server/wh_test_crypto_keywrap.c index 42412c8e6..2a2fffa24 100644 --- a/test-refactor/client-server/wh_test_crypto_keywrap.c +++ b/test-refactor/client-server/wh_test_crypto_keywrap.c @@ -19,13 +19,33 @@ /* * test-refactor/client-server/wh_test_crypto_keywrap.c * - * Single-client key wrap tests over AES-GCM: - * _whTest_CryptoKeyWrapSameOwner - wrap a local key, then unwrap and - * export it as the owner and confirm the - * recovered key matches the original - * _whTest_CryptoKeyWrapNoNvmPersist - an unwrapped key is cache-only, so - * commit and erase must reject a wrapped - * key id while eviction still succeeds + * Single-client key wrap and data wrap tests over AES-GCM: + * _whTest_CryptoKeyWrapSameOwner - wrap a local key, then unwrap and + * export it as the owner and confirm + * the recovered key matches the + * original + * _whTest_CryptoKeyWrapNoNvmPersist - an unwrapped key is cache-only, so + * commit and erase must reject a + * wrapped key id while eviction still + * succeeds + * _whTest_CryptoKeyWrapAesGcm - wrap a random key under the server + * KEK, unwrap-and-cache it, use the + * cached key for an AES-GCM round trip, + * then unwrap-and-export and confirm + * the key and metadata match + * _whTest_CryptoKeyUnwrapUnderflow - tiny wrapped-key sizes must return + * WH_ERROR_BADARGS, not underflow + * _whTest_CryptoDataWrapAesGcm - wrap and unwrap opaque data, confirm + * the round trip preserves the bytes + * _whTest_CryptoDataUnwrapUnderflow - tiny wrapped-data sizes must return + * WH_ERROR_BADARGS, not underflow + * + * The shared port server provisions no KEK, so the subtests that wrap under a + * server KEK cache one first and evict it on exit. The legacy own-server/auth + * setup is dropped; the harness supplies a connected, authorized client. + * + * Hardware-KEK keywrap lives in misc/wh_test_hwkeystore.c and + * server/wh_test_hwkeystore_server.c. */ #include "wolfhsm/wh_settings.h" @@ -39,23 +59,49 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/aes.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 KEYWRAP_TEST_AESGCM_IV_SIZE 12 -#define KEYWRAP_TEST_AESGCM_TAG_SIZE 16 -#define KEYWRAP_TEST_WRAPPED_KEY_SIZE \ - (KEYWRAP_TEST_AESGCM_IV_SIZE + KEYWRAP_TEST_AESGCM_TAG_SIZE + \ - AES_256_KEY_SIZE + sizeof(whNvmMetadata)) +#define KEYWRAP_TEST_WRAPPED_KEY_SIZE \ + (WH_KEYWRAP_AES_GCM_HEADER_SIZE + AES_256_KEY_SIZE + \ + sizeof(whNvmMetadata)) #define KEYWRAP_TEST_WRAPKEY_ID 1 #define KEYWRAP_TEST_PLAINKEY_ID 2 +/* Server KEK cached by this suite, plus the id of the key wrapped under it */ +#define KEYWRAP_TEST_KEKID 10 +#define KEYWRAP_TEST_AESGCM_KEYID 20 + +static int _InitServerKek(whClientContext* client) +{ + /* IMPORTANT NOTE: Server KEK is typically intrinsic or set during + * provisioning. Uploading the KEK via the client is for testing purposes + * only and not intended as a recommendation */ + whKeyId serverKeyId = KEYWRAP_TEST_KEKID; + whNvmFlags flags = WH_NVM_FLAGS_NONEXPORTABLE | WH_NVM_FLAGS_USAGE_WRAP; + uint8_t label[WH_NVM_LABEL_LEN] = "Server KEK key"; + uint8_t kek[] = {0x03, 0x03, 0x0d, 0xd9, 0xeb, 0x18, 0x17, 0x2e, + 0x06, 0x6e, 0x19, 0xce, 0x98, 0x44, 0x54, 0x0d, + 0x78, 0xa0, 0xbe, 0xe7, 0x35, 0x43, 0x40, 0xa4, + 0x22, 0x8a, 0xd1, 0x0e, 0xa3, 0x63, 0x1c, 0x0b}; + + return wh_Client_KeyCache(client, flags, label, sizeof(label), kek, + sizeof(kek), &serverKeyId); +} + +static int _CleanupServerKek(whClientContext* client) +{ + return wh_Client_KeyEvict(client, KEYWRAP_TEST_KEKID); +} + /* * Wrap a local key, then unwrap and export it as the owning client. * The recovered key must match the original plaintext. @@ -163,14 +209,345 @@ static int _whTest_CryptoKeyWrapNoNvmPersist(whClientContext* client) return WH_ERROR_OK; } +/* + * Wrap a random key under the server KEK, unwrap-and-cache it, exercise the + * cached key through an AES-GCM round trip, then unwrap-and-export it and + * confirm both the key and its metadata survive the trip. + */ +static int _whTest_CryptoKeyWrapAesGcm(whClientContext* client, WC_RNG* rng) +{ + int ret = 0; + uint8_t plainKey[AES_256_KEY_SIZE]; + uint8_t tmpPlainKey[AES_256_KEY_SIZE]; + uint16_t tmpPlainKeySz = sizeof(tmpPlainKey); + uint8_t wrappedKey[KEYWRAP_TEST_WRAPPED_KEY_SIZE]; + uint16_t wrappedKeySz = sizeof(wrappedKey); + whKeyId wrappedKeyId = WH_KEYID_ERASED; + whNvmMetadata metadata = { + .id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, + KEYWRAP_TEST_AESGCM_KEYID), + .label = "AES Key Label", + .len = AES_256_KEY_SIZE, + .flags = WH_NVM_FLAGS_USAGE_ANY, + }; + whNvmMetadata tmpMetadata = {0}; + + Aes aes[1]; + const uint8_t plaintext[] = "hello, wolfSSL AES-GCM!"; + uint8_t ciphertext[sizeof(plaintext)]; + uint8_t decrypted[sizeof(plaintext)]; + + uint8_t tag[WH_KEYWRAP_AES_GCM_TAG_SIZE]; + uint8_t iv[WH_KEYWRAP_AES_GCM_IV_SIZE]; + const uint8_t aad[] = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2}; + + WH_TEST_DEBUG_PRINT("Test: server KEK wrap, unwrap-cache, AES-GCM\n"); + + ret = wc_RNG_GenerateBlock(rng, plainKey, sizeof(plainKey)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock for key data %d\n", ret); + return ret; + } + + ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, KEYWRAP_TEST_KEKID, + plainKey, sizeof(plainKey), &metadata, wrappedKey, + &wrappedKeySz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyWrap %d\n", ret); + return ret; + } + + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, + KEYWRAP_TEST_KEKID, wrappedKey, + wrappedKeySz, &wrappedKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyUnwrapAndCache %d\n", ret); + return ret; + } + + /* Initialize AES context */ + ret = wc_AesInit(aes, NULL, WH_CLIENT_DEVID(client)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesInit %d\n", ret); + return ret; + } + + ret = + wh_Client_AesSetKeyId(aes, WH_CLIENT_KEYID_MAKE_WRAPPED(wrappedKeyId)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_AesSetKeyId %d\n", ret); + return ret; + } + + /* Generate a random IV */ + ret = wc_RNG_GenerateBlock(rng, iv, sizeof(iv)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock for AES-GCM key %d\n", + ret); + return ret; + } + + /* Request the server to encrypt some data using the + * unwrapped and cached key via the key ID */ + ret = wc_AesGcmEncrypt(aes, ciphertext, plaintext, sizeof(plaintext), iv, + sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesGcmEncrypt %d\n", ret); + return ret; + } + + /* Request the server to decrypt the encrypted data using the + * unwrapped and cached key via the key ID */ + ret = wc_AesGcmDecrypt(aes, decrypted, /* out */ + ciphertext, sizeof(ciphertext), /* in, inLen */ + iv, sizeof(iv), /* iv, ivLen */ + tag, sizeof(tag), /* authTag, authTagSz */ + aad, sizeof(aad)); /* authIn (AAD), authInSz */ + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_AesGcmDecrypt %d\n", ret); + return ret; + } + + /* Check if the decrypted data matches an expected value */ + if (memcmp(decrypted, plaintext, sizeof(decrypted)) != 0) { + WH_ERROR_PRINT("Decrypted value does not match expected value\n"); + return WH_ERROR_ABORTED; + } + + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, + KEYWRAP_TEST_KEKID, wrappedKey, + wrappedKeySz, &tmpMetadata, tmpPlainKey, + &tmpPlainKeySz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wh_Client_KeyUnwrapAndExport %d\n", ret); + return ret; + } + + if (memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { + WH_ERROR_PRINT("AES GCM wrap/unwrap key failed to match\n"); + return WH_ERROR_ABORTED; + } + + if (memcmp(&metadata, &tmpMetadata, sizeof(metadata)) != 0) { + WH_ERROR_PRINT("AES GCM wrap/unwrap metadata failed to match\n"); + return WH_ERROR_ABORTED; + } + + /* Cache a local key using the same numeric ID to confirm coexistence */ + { + whKeyId localKeyId = KEYWRAP_TEST_AESGCM_KEYID; + uint8_t localLabel[WH_NVM_LABEL_LEN] = "LocalKeySameId"; + const uint8_t localKey[AES_256_KEY_SIZE] = {0}; + + ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE, localLabel, + (uint16_t)sizeof("LocalKeySameId"), + (uint8_t*)localKey, sizeof(localKey), + &localKeyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache local key with shared ID %d\n", ret); + return ret; + } + if (localKeyId != KEYWRAP_TEST_AESGCM_KEYID) { + WH_ERROR_PRINT("Local key ID mismatch (expected %u, got %u)\n", + KEYWRAP_TEST_AESGCM_KEYID, localKeyId); + return WH_ERROR_ABORTED; + } + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, localKeyId)); + } + + wh_Client_KeyEvict(client, wrappedKeyId); + wc_AesFree(aes); + + WH_TEST_PRINT(" PASS: server KEK wrap, unwrap-cache, AES-GCM\n"); + + return ret; +} + +static int _whTest_CryptoKeyUnwrapUnderflow(whClientContext* client) +{ + int ret; + uint8_t dummyBuf[1] = {0}; + whNvmMetadata tmpMetadata = {0}; + uint8_t tmpKey[AES_256_KEY_SIZE] = {0}; + uint16_t tmpKeySz = sizeof(tmpKey); + whKeyId wrappedKeyId = WH_KEYID_ERASED; + + WH_TEST_DEBUG_PRINT("Test: key unwrap underflow\n"); + + /* wrappedKeySz=0: must return WH_ERROR_BADARGS, not underflow */ + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, + KEYWRAP_TEST_KEKID, dummyBuf, 0, + &tmpMetadata, tmpKey, &tmpKeySz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndExport(sz=0) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + /* wrappedKeySz=1: must return WH_ERROR_BADARGS, not underflow */ + tmpKeySz = sizeof(tmpKey); + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, + KEYWRAP_TEST_KEKID, dummyBuf, 1, + &tmpMetadata, tmpKey, &tmpKeySz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndExport(sz=1) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + /* wrappedKeySz=0: test KeyUnwrapAndCache path */ + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, + KEYWRAP_TEST_KEKID, dummyBuf, 0, + &wrappedKeyId); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndCache(sz=0) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + /* wrappedKeySz=1: test KeyUnwrapAndCache path */ + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, + KEYWRAP_TEST_KEKID, dummyBuf, 1, + &wrappedKeyId); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndCache(sz=1) expected BADARGS, got %d\n", + ret); + return WH_TEST_FAIL; + } + + WH_TEST_PRINT(" PASS: key unwrap underflow\n"); + + return WH_ERROR_OK; +} + +static int _whTest_CryptoDataWrapAesGcm(whClientContext* client) +{ + int ret = 0; + uint8_t data[] = "Example data!"; + uint8_t unwrappedData[sizeof(data)] = {0}; + uint32_t unwrappedDataSz = sizeof(unwrappedData); + uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE] = {0}; + uint32_t wrappedDataSz = sizeof(wrappedData); + + WH_TEST_DEBUG_PRINT("Test: data wrap round trip\n"); + + ret = wh_Client_DataWrap(client, WC_CIPHER_AES_GCM, KEYWRAP_TEST_KEKID, + data, sizeof(data), wrappedData, &wrappedDataSz); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to wh_Client_DataWrap %d\n", ret); + return ret; + } + + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, KEYWRAP_TEST_KEKID, + wrappedData, sizeof(wrappedData), unwrappedData, + &unwrappedDataSz); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to wh_Client_DataUnwrap %d\n", ret); + return ret; + } + + if (memcmp(data, unwrappedData, sizeof(data)) != 0) { + WH_ERROR_PRINT("Unwrapped data failed to match input data\n"); + return WH_ERROR_ABORTED; + } + + WH_TEST_PRINT(" PASS: data wrap round trip\n"); + + return ret; +} + +static int _whTest_CryptoDataUnwrapUnderflow(whClientContext* client) +{ + int ret; + uint8_t dummyBuf[1] = {0}; + uint8_t outBuf[32] = {0}; + uint32_t outSz = sizeof(outBuf); + + WH_TEST_DEBUG_PRINT("Test: data unwrap underflow\n"); + + /* wrappedDataSz=0: must return WH_ERROR_BADARGS, not underflow */ + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, KEYWRAP_TEST_KEKID, + dummyBuf, 0, outBuf, &outSz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("DataUnwrap(sz=0) expected BADARGS, got %d\n", ret); + return WH_TEST_FAIL; + } + + /* wrappedDataSz=1: must return WH_ERROR_BADARGS, not underflow */ + outSz = sizeof(outBuf); + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, KEYWRAP_TEST_KEKID, + dummyBuf, 1, outBuf, &outSz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("DataUnwrap(sz=1) expected BADARGS, got %d\n", ret); + return WH_TEST_FAIL; + } + + WH_TEST_PRINT(" PASS: data unwrap underflow\n"); + + return WH_ERROR_OK; +} + int whTest_Crypto_KeyWrap(whClientContext* ctx) { + int ret; + WC_RNG rng[1]; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + /* 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); + + /* These subtests cache their own wrapping keys */ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoKeyWrapSameOwner(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoKeyWrapNoNvmPersist(ctx)); - return WH_ERROR_OK; + + /* The remaining subtests share a server KEK */ + WH_TEST_RETURN_ON_FAIL(_InitServerKek(ctx)); + + ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(ctx)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + (void)_CleanupServerKek(ctx); + return ret; + } + + ret = _whTest_CryptoKeyWrapAesGcm(ctx, rng); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _whTest_CryptoKeyWrapAesGcm %d\n", ret); + } + + if (ret == WH_ERROR_OK) { + ret = _whTest_CryptoKeyUnwrapUnderflow(ctx); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _whTest_CryptoKeyUnwrapUnderflow %d\n", + ret); + } + } + + if (ret == WH_ERROR_OK) { + ret = _whTest_CryptoDataWrapAesGcm(ctx); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _whTest_CryptoDataWrapAesGcm %d\n", ret); + } + } + + if (ret == WH_ERROR_OK) { + ret = _whTest_CryptoDataUnwrapUnderflow(ctx); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("Failed to _whTest_CryptoDataUnwrapUnderflow %d\n", + ret); + } + } + + (void)_CleanupServerKek(ctx); + (void)wc_FreeRng(rng); + + return ret; } #endif /* !WOLFHSM_CFG_NO_CRYPTO && WOLFHSM_CFG_KEYWRAP && \ diff --git a/test-refactor/client-server/wh_test_keywrap.c b/test-refactor/client-server/wh_test_keywrap.c deleted file mode 100644 index c0a215749..000000000 --- a/test-refactor/client-server/wh_test_keywrap.c +++ /dev/null @@ -1,412 +0,0 @@ -/* - * 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_keywrap.c - * - * Key-wrap and data-wrap lifecycle against the shared client group: - * _AesGcm_TestKeyWrap - wrap a random key under the server - * KEK, unwrap-and-cache it, use the - * cached key for an AES-GCM round trip, - * then unwrap-and-export and confirm the - * key and metadata match the originals - * _AesGcm_TestKeyUnwrapUnderflow - tiny wrapped-key sizes must return - * WH_ERROR_BADARGS, not underflow - * _AesGcm_TestDataWrap - wrap and unwrap opaque data, confirm - * the round trip preserves the bytes - * _AesGcm_TestDataUnwrapUnderflow- tiny wrapped-data sizes must return - * WH_ERROR_BADARGS, not underflow - * - * The shared port server provisions no KEK, so each entry point caches a - * test KEK first and evicts it on exit. The legacy own-server/auth setup - * is dropped; the harness supplies a connected, authorized client. - */ - -#include "wolfhsm/wh_settings.h" - -#include -#include /* For memset, memcpy */ - -#if defined(WOLFHSM_CFG_KEYWRAP) && defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ - !defined(WOLFHSM_CFG_NO_CRYPTO) - -#include "wolfssl/wolfcrypt/settings.h" -#include "wolfssl/wolfcrypt/types.h" -#include "wolfssl/wolfcrypt/aes.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" - -/* Common defines */ -#define WH_TEST_KEKID 10 - -/* AES GCM Specific defines */ -#ifdef HAVE_AESGCM - -#define WH_TEST_AESGCM_KEYID 20 -#define WH_TEST_AES_KEYSIZE 32 -#define WH_TEST_AES_WRAPPED_KEYSIZE \ - (WH_KEYWRAP_AES_GCM_HEADER_SIZE + WH_TEST_AES_KEYSIZE + \ - sizeof(whNvmMetadata)) - -#endif /* HAVE_AESGCM */ - -static int _InitServerKek(whClientContext* client) -{ - /* IMPORTANT NOTE: Server KEK is typically intrinsic or set during - * provisioning. Uploading the KEK via the client is for testing purposes - * only and not intended as a recommendation */ - whKeyId serverKeyId = WH_TEST_KEKID; - whNvmFlags flags = WH_NVM_FLAGS_NONEXPORTABLE | WH_NVM_FLAGS_USAGE_WRAP; - uint8_t label[WH_NVM_LABEL_LEN] = "Server KEK key"; - uint8_t kek[] = {0x03, 0x03, 0x0d, 0xd9, 0xeb, 0x18, 0x17, 0x2e, - 0x06, 0x6e, 0x19, 0xce, 0x98, 0x44, 0x54, 0x0d, - 0x78, 0xa0, 0xbe, 0xe7, 0x35, 0x43, 0x40, 0xa4, - 0x22, 0x8a, 0xd1, 0x0e, 0xa3, 0x63, 0x1c, 0x0b}; - - return wh_Client_KeyCache(client, flags, label, sizeof(label), kek, - sizeof(kek), &serverKeyId); -} - -static int _CleanupServerKek(whClientContext* client) -{ - return wh_Client_KeyEvict(client, WH_TEST_KEKID); -} - -#ifdef HAVE_AESGCM - -static int _AesGcm_TestKeyWrap(whClientContext* client, WC_RNG* rng) -{ - - int ret = 0; - uint8_t plainKey[WH_TEST_AES_KEYSIZE]; - uint8_t tmpPlainKey[WH_TEST_AES_KEYSIZE]; - uint16_t tmpPlainKeySz = sizeof(tmpPlainKey); - uint8_t wrappedKey[WH_TEST_AES_WRAPPED_KEYSIZE]; - uint16_t wrappedKeySz = sizeof(wrappedKey); - whKeyId wrappedKeyId = WH_KEYID_ERASED; - whNvmMetadata metadata = { - .id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, - WH_TEST_AESGCM_KEYID), - .label = "AES Key Label", - .len = WH_TEST_AES_KEYSIZE, - .flags = WH_NVM_FLAGS_USAGE_ANY, - }; - whNvmMetadata tmpMetadata = {0}; - - Aes aes[1]; - const uint8_t plaintext[] = "hello, wolfSSL AES-GCM!"; - uint8_t ciphertext[sizeof(plaintext)]; - uint8_t decrypted[sizeof(plaintext)]; - - uint8_t tag[WH_KEYWRAP_AES_GCM_TAG_SIZE]; - uint8_t iv[WH_KEYWRAP_AES_GCM_IV_SIZE]; - const uint8_t aad[] = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, - 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, - 0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2}; - - - ret = wc_RNG_GenerateBlock(rng, plainKey, sizeof(plainKey)); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock for key data %d\n", ret); - return ret; - } - - ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, plainKey, - sizeof(plainKey), &metadata, wrappedKey, - &wrappedKeySz); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wh_Client_AesGcmKeyWrap %d\n", ret); - return ret; - } - - ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - wrappedKey, wrappedKeySz, &wrappedKeyId); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wh_Client_AesGcmKeyWrapCache %d\n", ret); - return ret; - } - - /* Initialize AES context */ - ret = wc_AesInit(aes, NULL, WH_CLIENT_DEVID(client)); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wc_AesInit %d\n", ret); - return ret; - } - - ret = - wh_Client_AesSetKeyId(aes, WH_CLIENT_KEYID_MAKE_WRAPPED(wrappedKeyId)); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wh_Client_AesSetKeyId %d\n", ret); - return ret; - } - - /* Generate a random IV */ - ret = wc_RNG_GenerateBlock(rng, iv, sizeof(iv)); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock for AES-GCM key %d\n", - ret); - return ret; - } - - /* Request the server to encrypt some data using the - * unwrapped and cached key via the key ID */ - ret = wc_AesGcmEncrypt(aes, ciphertext, plaintext, sizeof(plaintext), iv, - sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wc_AesGcmEncrypt %d\n", ret); - return ret; - } - - /* Request the server to decrypt the encrypted data using the - * unwrapped and cached key via the key ID */ - ret = wc_AesGcmDecrypt(aes, decrypted, /* out */ - ciphertext, sizeof(ciphertext), /* in, inLen */ - iv, sizeof(iv), /* iv, ivLen */ - tag, sizeof(tag), /* authTag, authTagSz */ - aad, sizeof(aad)); /* authIn (AAD), authInSz */ - if (ret != 0) { - WH_ERROR_PRINT("Failed to wc_AesGcmDecrypt %d\n", ret); - return ret; - } - - /* Check if the decrypted data matches an expected value */ - if (memcmp(decrypted, plaintext, sizeof(decrypted)) != 0) { - WH_ERROR_PRINT("Decrypted value does not match expected value\n"); - return -1; - } - - ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - wrappedKey, wrappedKeySz, &tmpMetadata, - tmpPlainKey, &tmpPlainKeySz); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wh_Client_KeyUnwrapAndExport %d\n", ret); - return ret; - } - - if (memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { - WH_ERROR_PRINT("AES GCM wrap/unwrap key failed to match\n"); - return -1; - } - - if (memcmp(&metadata, &tmpMetadata, sizeof(metadata)) != 0) { - WH_ERROR_PRINT("AES GCM wrap/unwrap metadata failed to match\n"); - return -1; - } - - /* Cache a local key using the same numeric ID to confirm coexistence */ - { - whKeyId localKeyId = WH_TEST_AESGCM_KEYID; - uint8_t localLabel[WH_NVM_LABEL_LEN] = "LocalKeySameId"; - const uint8_t localKey[WH_TEST_AES_KEYSIZE] = {0}; - - ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE, localLabel, - (uint16_t)sizeof("LocalKeySameId"), - (uint8_t*)localKey, sizeof(localKey), - &localKeyId); - if (ret != 0) { - WH_ERROR_PRINT("Failed to cache local key with shared ID %d\n", ret); - return ret; - } - if (localKeyId != WH_TEST_AESGCM_KEYID) { - WH_ERROR_PRINT("Local key ID mismatch (expected %u, got %u)\n", - WH_TEST_AESGCM_KEYID, localKeyId); - return WH_ERROR_ABORTED; - } - WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, localKeyId)); - } - - wh_Client_KeyEvict(client, wrappedKeyId); - wc_AesFree(aes); - - return ret; -} - -static int _AesGcm_TestDataWrap(whClientContext* client) -{ - int ret = 0; - uint8_t data[] = "Example data!"; - uint8_t unwrappedData[sizeof(data)] = {0}; - uint32_t unwrappedDataSz = sizeof(unwrappedData); - uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE] = {0}; - uint32_t wrappedDataSz = sizeof(wrappedData); - - ret = wh_Client_DataWrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, data, - sizeof(data), wrappedData, &wrappedDataSz); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_DataWrap %d\n", ret); - return ret; - } - - ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - wrappedData, sizeof(wrappedData), unwrappedData, - &unwrappedDataSz); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to wh_Client_DataUnwrap %d\n", ret); - return ret; - } - - if (memcmp(data, unwrappedData, sizeof(data)) != 0) { - WH_ERROR_PRINT("Unwrapped data failed to match input data\n"); - return -1; - } - - return ret; -} - -static int _AesGcm_TestKeyUnwrapUnderflow(whClientContext* client) -{ - int ret; - uint8_t dummyBuf[1] = {0}; - whNvmMetadata tmpMetadata = {0}; - uint8_t tmpKey[WH_TEST_AES_KEYSIZE] = {0}; - uint16_t tmpKeySz = sizeof(tmpKey); - whKeyId wrappedKeyId = WH_KEYID_ERASED; - - /* wrappedKeySz=0: must return WH_ERROR_BADARGS, not underflow */ - ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - dummyBuf, 0, &tmpMetadata, tmpKey, - &tmpKeySz); - if (ret != WH_ERROR_BADARGS) { - WH_ERROR_PRINT("KeyUnwrapAndExport(sz=0) expected BADARGS, got %d\n", - ret); - return WH_TEST_FAIL; - } - - /* wrappedKeySz=1: must return WH_ERROR_BADARGS, not underflow */ - tmpKeySz = sizeof(tmpKey); - ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - dummyBuf, 1, &tmpMetadata, tmpKey, - &tmpKeySz); - if (ret != WH_ERROR_BADARGS) { - WH_ERROR_PRINT("KeyUnwrapAndExport(sz=1) expected BADARGS, got %d\n", - ret); - return WH_TEST_FAIL; - } - - /* wrappedKeySz=0: test KeyUnwrapAndCache path */ - ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - dummyBuf, 0, &wrappedKeyId); - if (ret != WH_ERROR_BADARGS) { - WH_ERROR_PRINT("KeyUnwrapAndCache(sz=0) expected BADARGS, got %d\n", - ret); - return WH_TEST_FAIL; - } - - /* wrappedKeySz=1: test KeyUnwrapAndCache path */ - ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - dummyBuf, 1, &wrappedKeyId); - if (ret != WH_ERROR_BADARGS) { - WH_ERROR_PRINT("KeyUnwrapAndCache(sz=1) expected BADARGS, got %d\n", - ret); - return WH_TEST_FAIL; - } - - return WH_ERROR_OK; -} - -static int _AesGcm_TestDataUnwrapUnderflow(whClientContext* client) -{ - int ret; - uint8_t dummyBuf[1] = {0}; - uint8_t outBuf[32] = {0}; - uint32_t outSz = sizeof(outBuf); - - /* wrappedDataSz=0: must return WH_ERROR_BADARGS, not underflow */ - ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - dummyBuf, 0, outBuf, &outSz); - if (ret != WH_ERROR_BADARGS) { - WH_ERROR_PRINT("DataUnwrap(sz=0) expected BADARGS, got %d\n", ret); - return WH_TEST_FAIL; - } - - /* wrappedDataSz=1: must return WH_ERROR_BADARGS, not underflow */ - outSz = sizeof(outBuf); - ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, WH_TEST_KEKID, - dummyBuf, 1, outBuf, &outSz); - if (ret != WH_ERROR_BADARGS) { - WH_ERROR_PRINT("DataUnwrap(sz=1) expected BADARGS, got %d\n", ret); - return WH_TEST_FAIL; - } - - return WH_ERROR_OK; -} - -#endif /* HAVE_AESGCM */ - -int whTest_KeyWrap(whClientContext* client) -{ - int ret = 0; - WC_RNG rng[1]; - - if (client == NULL) { - return WH_ERROR_BADARGS; - } - - WH_TEST_RETURN_ON_FAIL(_InitServerKek(client)); - - ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(client)); - if (ret != 0) { - WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); - (void)_CleanupServerKek(client); - return ret; - } - -#ifdef HAVE_AESGCM - ret = _AesGcm_TestKeyWrap(client, rng); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _AesGcm_TestKeyWrap %d\n", ret); - } - - if (ret == WH_ERROR_OK) { - ret = _AesGcm_TestKeyUnwrapUnderflow(client); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _AesGcm_TestKeyUnwrapUnderflow %d\n", ret); - } - } - - if (ret == WH_ERROR_OK) { - ret = _AesGcm_TestDataWrap(client); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _AesGcm_TestDataWrap %d\n", ret); - } - } - - if (ret == WH_ERROR_OK) { - ret = _AesGcm_TestDataUnwrapUnderflow(client); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("Failed to _AesGcm_TestDataUnwrapUnderflow %d\n", - ret); - } - } -#endif /* HAVE_AESGCM */ - - (void)_CleanupServerKek(client); - (void)wc_FreeRng(rng); - - return ret; -} - -#endif /* WOLFHSM_CFG_KEYWRAP && WOLFHSM_CFG_ENABLE_CLIENT && \ - !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 735256e4b..a5148c209 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -82,7 +82,6 @@ WH_TEST_DECL(whTest_NvmDma); WH_TEST_DECL(whTest_NvmOps); WH_TEST_DECL(whTest_ServerInfo); WH_TEST_DECL(whTest_WolfCryptTest); -WH_TEST_DECL(whTest_KeyWrap); WH_TEST_DECL(whTest_AuthBadArgs); WH_TEST_DECL(whTest_AuthLogin); WH_TEST_DECL(whTest_AuthLogout); @@ -143,7 +142,6 @@ const whTestCase whTestsClient[] = { {"whTest_NvmOps", whTest_NvmOps}, {"whTest_ServerInfo", whTest_ServerInfo}, {"whTest_WolfCryptTest", whTest_WolfCryptTest}, - {"whTest_KeyWrap", whTest_KeyWrap}, {"whTest_AuthBadArgs", whTest_AuthBadArgs}, {"whTest_AuthLogin", whTest_AuthLogin}, {"whTest_AuthLogout", whTest_AuthLogout},