diff --git a/test-refactor/README.md b/test-refactor/README.md index 406f9a617..d7811ce39 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -105,7 +105,7 @@ Translated tests: | `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | `server/wh_test_server_img_mgr.c::whTest_ServerImgMgr` | Server | Per-method subtests (ECC P256, AES-CMAC, RSA2048, wolfBoot RSA4096, and -- under `WOLFHSM_CFG_CERTIFICATE_MANAGER` -- wolfBoot cert chain) run against the shared server context instead of each building its own server/NVM/transport. Each subtest scrubs the NVM objects and key-cache entries it creates so the group's shared NVM stays clean. 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_she.c` (SHE<->keywrap interop + `wh_She_TestWrappedInterop`) | `misc/wh_test_she_keywrap.c::whTest_SheKeywrapInterop` | Misc | needs a server whose NVM carries a `WH_NVM_FLAGS_TRUSTED` KEK, so it runs its own client/server pair with a sequential split-API pump; covers wrap-export by id (incl. the SHE slot-0 gate regression), key-wrap vs data-wrap domain separation, the unwrap-and-export type gate (a SHE-typed blob must not export as plaintext), unwrap-and-cache priming, the counter rollback guard, the LoadKey stale-cache regression, and the two-session reboot interop. Secure boot is driven with raw INIT/UPDATE/FINISH messages (blocking-only client API) | | `wh_test_she_no_nvm.c::whTest_SheNoNvm` | `misc/wh_test_she_no_nvm.c::whTest_SheNoNvm` | Misc | NULL-NVM server (`whServerConfig.nvm == NULL`), so it runs its own client/server pair with a sequential split-API pump instead of the legacy pthread harness; same end-to-end flow (unwrap-and-cache provisioning incl. SHE slot 0, secure boot, LoadKey cache path, ECB/CBC/CMAC, ExportRamKey, PRNG, KEK immutability) | -| `wh_test_keywrap.c` (sw-KEK round trip, trusted-KEK policy negatives, data-wrap usage) | `client-server/wh_test_keywrap.c::whTest_KeyWrap` | Client | runs against any server: positive KeyWrap/KeyUnwrapAndExport under a plain client KEK, wrap-export/unwrap-and-cache refusal of a non-trusted KEK, the NVM-add / HKDF-cache / key-cache `WH_NVM_FLAGS_TRUSTED` forge negatives, and the data-wrap `USAGE_WRAP` negative. The trusted-KEK positive paths (wrap-export round-trip incl. NONEXPORTABLE enforcement, unwrap-and-cache, and the cache-only check that commit/erase reject an unwrapped key id) run in `misc/wh_test_hwkeystore.c` against the hardware KEK | +| `wh_test_keywrap.c` (sw-KEK round trip, trusted-KEK policy negatives, data-wrap usage) | `client-server/wh_test_keywrap.c::whTest_KeyWrap` | Client | runs against any server: positive KeyWrap/KeyUnwrapAndExport under a plain client KEK, wrap-export/unwrap-and-cache refusal of a non-trusted KEK, the NVM-add / HKDF-cache / key-cache `WH_NVM_FLAGS_TRUSTED` forge negatives, the data-wrap `USAGE_WRAP` negative, and the key/data unwrap underflow bad-args checks. The trusted-KEK positive paths (wrap-export round-trip incl. NONEXPORTABLE enforcement, unwrap-and-cache, and the cache-only check that commit/erase reject an unwrapped key id) run in `misc/wh_test_hwkeystore.c` against the hardware KEK | | `wh_test_log.c::whTest_Log` (frontend, macros, ring buffer, mock/ringbuf generic harness) | `misc/wh_test_log.c::whTest_Log` | Misc | Portable subset: frontend API, log macros, formatted macros, ring buffer backend, and the backend-agnostic harness against the mock and ring buffer backends. The harness itself lives in `misc/wh_test_log_backend.h` so the POSIX port reuses it | | `wh_test_log.c` (POSIX file backend, concurrent, client/server) | `posix/wh_test_log_posix.c::{whTest_LogPosixFile_Generic, whTest_LogPosixFile, whTest_LogPosixFileConcurrent, whTest_LogClientServerMemTransport}` | POSIX port-specific (`whTestGroup_RunOne`) | POSIX file backend (via the shared harness and directly), multi-threaded concurrent writers, and a client/server log smoke test over the mem transport. Need pthreads and the host file system, so they run from the port main, not the portable registry | @@ -116,7 +116,7 @@ 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 coverage is the custom-callback round-trip (`_testCallbacks`) and the server-side DMA register/copy/allowlist exercise (`_testDma`). The sequential test is dropped, as is the FLASH_LOG NVM matrix variant. | | `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_keywrap.c::whTest_KeyWrapClientConfig` | Policy coverage (sw-KEK round trip, trusted-KEK negatives, data-wrap usage) is ported to `client-server/wh_test_keywrap.c`; the remaining legacy coverage (hardware-KEK cryptocb use of a cached wrapped key, unwrap underflow/oversize negatives) is not | +| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | Policy coverage (sw-KEK round trip, trusted-KEK negatives, data-wrap usage) and the key/data unwrap underflow negatives are ported to `client-server/wh_test_keywrap.c`; the remaining legacy coverage (hardware-KEK cryptocb use of a cached wrapped key, unwrap oversize negatives) is not | | `wh_test_timeout.c::whTest_TimeoutPosix` | | | `wh_test_nvmflags.c::whTest_NvmFlags` | | | `wh_test_flash_fault_inject.c` | | diff --git a/test-refactor/client-server/wh_test_crypto_keywrap.c b/test-refactor/client-server/wh_test_crypto_keywrap.c index 3d0aec82a..d1768192b 100644 --- a/test-refactor/client-server/wh_test_crypto_keywrap.c +++ b/test-refactor/client-server/wh_test_crypto_keywrap.c @@ -75,9 +75,11 @@ static int _whTest_CryptoKeyWrapSameOwner(whClientContext* client) sizeof("WrapKeySameOwner"), wrapKey, sizeof(wrapKey), &wrapKeyId)); /* Wrap a local key owned by this client */ - meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, - KEYWRAP_TEST_PLAINKEY_ID); - meta.len = sizeof(plainKey); + meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, + KEYWRAP_TEST_PLAINKEY_ID); + meta.len = sizeof(plainKey); + meta.flags = WH_NVM_FLAGS_USAGE_ANY; + memcpy(meta.label, "SameOwner Label", sizeof("SameOwner Label")); WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, wrapKeyId, plainKey, sizeof(plainKey), &meta, @@ -88,9 +90,10 @@ static int _whTest_CryptoKeyWrapSameOwner(whClientContext* client) client, WC_CIPHER_AES_GCM, wrapKeyId, wrappedKey, sizeof(wrappedKey), &exportMeta, unwrappedKey, &unwrappedKeySz)); - /* Recovered key matches the original */ + /* Recovered key and metadata match the originals */ WH_TEST_ASSERT_RETURN(0 == memcmp(unwrappedKey, plainKey, sizeof(plainKey))); + WH_TEST_ASSERT_RETURN(0 == memcmp(&meta, &exportMeta, sizeof(meta))); /* Evict the wrapping key */ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, wrapKeyId)); diff --git a/test-refactor/client-server/wh_test_keywrap.c b/test-refactor/client-server/wh_test_keywrap.c index 174a9f811..be82a148c 100644 --- a/test-refactor/client-server/wh_test_keywrap.c +++ b/test-refactor/client-server/wh_test_keywrap.c @@ -19,21 +19,23 @@ /* * test-refactor/client-server/wh_test_keywrap.c * - * Keywrap policy coverage that runs against any server (no trusted KEK - * needed on the positive paths, and the negatives prove a client cannot - * mint one): - * _whTest_KeywrapSwKekRoundTrip - wrap a plaintext key under a plain - * client-cached KEK and unwrap-and-export - * it back (only unwrap-and-cache and - * wrap-export require a trusted KEK) + * Keywrap policy and input-validation coverage that runs against any server: + * the negatives prove a client cannot mint a trusted KEK or slip an + * undersized blob past the size checks. * _whTest_KeywrapTrustedKekPolicy - wrap-export and unwrap-and-cache must * refuse a plain client KEK, and a client * cannot forge WH_NVM_FLAGS_TRUSTED via the * NVM add, HKDF cache, or key cache paths * _whTest_KeywrapDataWrapUsage - data wrap requires USAGE_WRAP on the KEK + * _whTest_KeywrapKeyUnwrapUnderflow - undersized wrapped-key blobs must + * return WH_ERROR_BADARGS, not underflow + * _whTest_KeywrapDataUnwrapUnderflow - undersized wrapped-data blobs must + * return WH_ERROR_BADARGS, not underflow * - * The trusted-KEK positive paths (wrap-export round-trip, unwrap-and-cache) - * live in misc/wh_test_hwkeystore.c against the hardware KEK, and in + * The positive wrap/unwrap-and-export round trip under a plain client KEK + * lives in wh_test_crypto_keywrap.c. The trusted-KEK positive paths + * (wrap-export round-trip, unwrap-and-cache) live in + * misc/wh_test_hwkeystore.c against the hardware KEK, and in * misc/wh_test_multiclient.c against an NVM-provisioned KEK. */ @@ -92,68 +94,6 @@ static int _CacheSwKek(whClientContext* client, whKeyId* outKekId) return WH_ERROR_OK; } -/* Positive software-KEK round trip; needs no trusted KEK. Wrap a plaintext key - * under the plain KEK, unwrap-and-export the blob, and confirm the key - * material and metadata come back unchanged */ -static int _whTest_KeywrapSwKekRoundTrip(whClientContext* client) -{ - int ret; - whKeyId kekId = WH_KEYID_ERASED; - uint8_t plainKey[WH_TEST_KW_KEYSIZE]; - uint8_t tmpPlainKey[WH_TEST_KW_KEYSIZE]; - uint16_t tmpPlainKeySz = sizeof(tmpPlainKey); - uint8_t wrappedKey[WH_TEST_KW_WRAPPED_KEYSIZE]; - uint16_t wrappedKeySz = sizeof(wrappedKey); - whNvmMetadata metadata = {0}; - whNvmMetadata tmpMetadata = {0}; - size_t i; - - metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(client->comm->client_id, - WH_TEST_KW_META_ID); - metadata.len = WH_TEST_KW_KEYSIZE; - metadata.flags = WH_NVM_FLAGS_USAGE_ANY; - memcpy(metadata.label, "SwKek Key Label", sizeof("SwKek Key Label")); - - for (i = 0; i < sizeof(plainKey); i++) { - plainKey[i] = (uint8_t)(0x7B ^ i); - } - - WH_TEST_RETURN_ON_FAIL(_CacheSwKek(client, &kekId)); - - /* Wrap the plaintext key under the plain software KEK */ - ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, kekId, plainKey, - sizeof(plainKey), &metadata, wrappedKey, - &wrappedKeySz); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("sw-kek: KeyWrap failed %d\n", ret); - (void)wh_Client_KeyEvict(client, kekId); - return ret; - } - - /* Unwrap-and-export the blob and confirm the material round-trips */ - ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, kekId, - wrappedKey, wrappedKeySz, &tmpMetadata, - tmpPlainKey, &tmpPlainKeySz); - (void)wh_Client_KeyEvict(client, kekId); - if (ret != WH_ERROR_OK) { - WH_ERROR_PRINT("sw-kek: KeyUnwrapAndExport failed %d\n", ret); - return ret; - } - - if (tmpPlainKeySz != sizeof(plainKey) || - memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { - WH_ERROR_PRINT("sw-kek: unwrapped key material mismatch\n"); - return WH_ERROR_ABORTED; - } - - if (memcmp(&metadata, &tmpMetadata, sizeof(metadata)) != 0) { - WH_ERROR_PRINT("sw-kek: unwrapped metadata mismatch\n"); - return WH_ERROR_ABORTED; - } - - return WH_ERROR_OK; -} - /* The wrap-export and unwrap-and-cache operations require a trusted KEK (HW or * WH_NVM_FLAGS_TRUSTED). Prove that (a) a plain client-cached USAGE_WRAP key is * refused as their KEK, and that a client cannot forge a trusted KEK by @@ -397,11 +337,89 @@ static int _whTest_KeywrapDataWrapUsage(whClientContext* client) return WH_ERROR_OK; } +/* A wrapped blob smaller than its own header must be refused with + * WH_ERROR_BADARGS rather than underflowing the payload length. A usable KEK + * is cached first so the rejection can only come from the size check, not + * from KEK resolution */ +static int _whTest_KeywrapKeyUnwrapUnderflow(whClientContext* client) +{ + const uint16_t shortSizes[] = {0, 1}; + int ret = WH_ERROR_OK; + whKeyId kekId = WH_KEYID_ERASED; + uint8_t blob[1] = {0}; + size_t i; + + WH_TEST_RETURN_ON_FAIL(_CacheSwKek(client, &kekId)); + + for (i = 0; i < sizeof(shortSizes) / sizeof(shortSizes[0]); i++) { + whNvmMetadata meta = {0}; + uint8_t keyOut[WH_TEST_KW_KEYSIZE] = {0}; + uint16_t keyOutSz = sizeof(keyOut); + uint16_t cachedId = WH_KEYID_ERASED; + + ret = wh_Client_KeyUnwrapAndExport(client, WC_CIPHER_AES_GCM, kekId, + blob, shortSizes[i], &meta, keyOut, + &keyOutSz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndExport(sz=%u) expected BADARGS, " + "got %d\n", + (unsigned)shortSizes[i], ret); + ret = WH_ERROR_ABORTED; + break; + } + + ret = wh_Client_KeyUnwrapAndCache(client, WC_CIPHER_AES_GCM, kekId, + blob, shortSizes[i], &cachedId); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("KeyUnwrapAndCache(sz=%u) expected BADARGS, " + "got %d\n", + (unsigned)shortSizes[i], ret); + ret = WH_ERROR_ABORTED; + break; + } + ret = WH_ERROR_OK; + } + + (void)wh_Client_KeyEvict(client, kekId); + return ret; +} + +/* Same underflow guard on the opaque data path */ +static int _whTest_KeywrapDataUnwrapUnderflow(whClientContext* client) +{ + const uint32_t shortSizes[] = {0, 1}; + int ret = WH_ERROR_OK; + whKeyId kekId = WH_KEYID_ERASED; + uint8_t blob[1] = {0}; + size_t i; + + WH_TEST_RETURN_ON_FAIL(_CacheSwKek(client, &kekId)); + + for (i = 0; i < sizeof(shortSizes) / sizeof(shortSizes[0]); i++) { + uint8_t dataOut[WH_TEST_KW_KEYSIZE] = {0}; + uint32_t dataOutSz = sizeof(dataOut); + + ret = wh_Client_DataUnwrap(client, WC_CIPHER_AES_GCM, kekId, blob, + shortSizes[i], dataOut, &dataOutSz); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("DataUnwrap(sz=%u) expected BADARGS, got %d\n", + (unsigned)shortSizes[i], ret); + ret = WH_ERROR_ABORTED; + break; + } + ret = WH_ERROR_OK; + } + + (void)wh_Client_KeyEvict(client, kekId); + return ret; +} + int whTest_KeyWrap(whClientContext* ctx) { - WH_TEST_RETURN_ON_FAIL(_whTest_KeywrapSwKekRoundTrip(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_KeywrapTrustedKekPolicy(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_KeywrapDataWrapUsage(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_KeywrapKeyUnwrapUnderflow(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_KeywrapDataUnwrapUnderflow(ctx)); WH_TEST_PRINT("KEYWRAP POLICY SUCCESS\n"); return 0;