diff --git a/test-refactor/README.md b/test-refactor/README.md index 12ed3ce17..4e3aad0c2 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_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/`): @@ -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_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 && \