diff --git a/test-refactor/README.md b/test-refactor/README.md
index bdf1397e6..76e1cf8bb 100644
--- a/test-refactor/README.md
+++ b/test-refactor/README.md
@@ -79,7 +79,7 @@ Translated tests:
|---|---|---|---|
| `wh_test_dma.c::whTest_Dma` | `misc/wh_test_dma.c::whTest_Dma` | Misc | |
| `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group |
-| `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto.c::{whTest_CryptoSha256, whTest_CryptoAes, whTest_CryptoEcc256}` | Client | Subset only; remaining cases listed below |
+| `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` |
| `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 |
@@ -92,7 +92,8 @@ Not yet migrated (still live in `wolfHSM/test/`):
|---|---|
| `wh_test_comm.c::whTest_Comm` | |
| `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` | RNG, key cache, key-cache enforcement, RSA, CMAC, Curve25519, ML-DSA, key usage policies, key revocation |
+| `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.c b/test-refactor/client-server/wh_test_crypto.c
deleted file mode 100644
index ad7f8f965..000000000
--- a/test-refactor/client-server/wh_test_crypto.c
+++ /dev/null
@@ -1,440 +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/wh_test_crypto.c
- *
- * Basic crypto test suite. Minimal SHA256 and AES-CBC
- * round-trips routed through the server via WH_DEV_ID.
- */
-
-#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/aes.h"
-#include "wolfssl/wolfcrypt/ecc.h"
-#include "wolfssl/wolfcrypt/ed25519.h"
-#include "wolfssl/wolfcrypt/random.h"
-#include "wolfssl/wolfcrypt/rsa.h"
-#include "wolfssl/wolfcrypt/sha256.h"
-#ifdef HAVE_DILITHIUM
-#include "wolfssl/wolfcrypt/dilithium.h"
-#endif
-
-#include "wolfhsm/wh_error.h"
-#include "wolfhsm/wh_client.h"
-#include "wolfhsm/wh_client_crypto.h"
-
-#include "wh_test_common.h"
-#include "wh_test_list.h"
-
-#ifndef NO_SHA256
-int whTest_CryptoSha256(whClientContext* ctx)
-{
- int devId = WH_DEV_ID;
- int ret = WH_ERROR_OK;
- wc_Sha256 sha256[1];
- uint8_t out[WC_SHA256_DIGEST_SIZE];
- /* Vector exactly one block size in length */
- const char inOne[] =
- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
- const uint8_t expectedOutOne[WC_SHA256_DIGEST_SIZE] = {
- 0xff, 0xe0, 0x54, 0xfe, 0x7a, 0xe0, 0xcb, 0x6d, 0xc6, 0x5c, 0x3a,
- 0xf9, 0xb6, 0x1d, 0x52, 0x09, 0xf4, 0x39, 0x85, 0x1d, 0xb4, 0x3d,
- 0x0b, 0xa5, 0x99, 0x73, 0x37, 0xdf, 0x15, 0x46, 0x68, 0xeb};
-
- (void)ctx;
-
- /* Initialize SHA256 structure */
- ret = wc_InitSha256_ex(sha256, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_InitSha256 on devId 0x%X: %d\n", devId,
- ret);
- } else {
- /* Single-block update should trigger a server transaction */
- ret = wc_Sha256Update(sha256,
- (const byte*)inOne,
- WC_SHA256_BLOCK_SIZE);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_Sha256Update %d\n", ret);
- } else {
- /* Finalize should trigger a server transaction with empty buffer */
- ret = wc_Sha256Final(sha256, out);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_Sha256Final %d\n", ret);
- } else {
- /* Compare the computed hash with the expected output */
- if (memcmp(out, expectedOutOne, WC_SHA256_DIGEST_SIZE) != 0) {
- WH_ERROR_PRINT("SHA256 hash does not match expected.\n");
- ret = -1;
- }
- }
- }
- (void)wc_Sha256Free(sha256);
- }
- if (ret == 0) {
- WH_TEST_PRINT("SHA256 DEVID=0x%X SUCCESS\n", devId);
- }
- return ret;
-}
-#endif /* !NO_SHA256 */
-
-
-#if !defined(NO_AES) && defined(HAVE_AES_CBC)
-int whTest_CryptoAes(whClientContext* ctx)
-{
- int devId = WH_DEV_ID;
- int ret = 0;
- Aes aes[1];
- uint8_t cipher[AES_BLOCK_SIZE] = {0};
- uint8_t plainOut[AES_BLOCK_SIZE] = {0};
- /* NIST SP 800-38B test vectors (same k128 / m used by the CMAC test
- * in test/wh_test_crypto.c). Using a fixed vector keeps this suite
- * self-contained, no RNG needed. */
- const uint8_t key[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
- 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
- const uint8_t iv[AES_BLOCK_SIZE] = {
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
- const uint8_t plainIn[AES_BLOCK_SIZE] = {
- 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
- 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a};
-
- (void)ctx;
-
- /* test aes CBC with client side key */
- ret = wc_AesInit(aes, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_AesInit %d\n", ret);
- } else {
- ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_AesSetKey %d\n", ret);
- } else {
- ret = wc_AesCbcEncrypt(aes, cipher, plainIn,
- sizeof(plainIn));
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_AesCbcEncrypt %d\n", ret);
- } else {
- ret = wc_AesSetKey(aes, key, sizeof(key), iv,
- AES_DECRYPTION);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_AesSetKey %d\n", ret);
- } else {
- ret = wc_AesCbcDecrypt(aes, plainOut, cipher,
- sizeof(cipher));
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_AesCbcDecrypt %d\n",
- ret);
- } else {
- if (memcmp(plainIn, plainOut, sizeof(plainIn)) !=
- 0) {
- WH_ERROR_PRINT("Failed to match AES-CBC\n");
- ret = -1;
- }
- }
- }
- }
- }
- (void)wc_AesFree(aes);
- }
- if (ret == 0) {
- WH_TEST_PRINT("AES CBC DEVID=0x%X SUCCESS\n", devId);
- }
- return ret;
-}
-#endif /* !NO_AES && HAVE_AES_CBC */
-
-
-#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY)
-int whTest_CryptoEcc256(whClientContext* ctx)
-{
- int devId = WH_DEV_ID;
- int ret = 0;
- WC_RNG rng[1];
- ecc_key key[1];
- /* Non-zero digest: wolfCrypt rejects all-zero hashes with ECC_BAD_ARG_E
- * unless WC_ALLOW_ECC_ZERO_HASH is defined. */
- uint8_t hash[32];
- uint8_t sig[ECC_MAX_SIG_SIZE] = {0};
- word32 sigLen = sizeof(sig);
- int verify = 0;
- word32 i;
-
- (void)ctx;
-
- for (i = 0; i < sizeof(hash); i++) {
- hash[i] = (uint8_t)(i + 1);
- }
-
- /* Minimal P-256 sign/verify round-trip routed through the server via
- * WH_DEV_ID. Key size 32 selects SECP256R1 as wolfCrypt's default. */
- ret = wc_InitRng_ex(rng, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
- } else {
- ret = wc_ecc_init_ex(key, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_ecc_init_ex %d\n", ret);
- } else {
- ret = wc_ecc_make_key(rng, 32, key);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_ecc_make_key %d\n", ret);
- } else {
- ret = wc_ecc_sign_hash(hash, sizeof(hash),
- sig, &sigLen, rng, key);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_ecc_sign_hash %d\n",
- ret);
- } else {
- ret = wc_ecc_verify_hash(sig, sigLen,
- hash, sizeof(hash), &verify, key);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_ecc_verify_hash %d\n",
- ret);
- } else if (verify != 1) {
- WH_ERROR_PRINT("ECC256 verify mismatch\n");
- ret = -1;
- }
- }
- }
- (void)wc_ecc_free(key);
- }
- (void)wc_FreeRng(rng);
- }
- if (ret == 0) {
- WH_TEST_PRINT("ECC256 DEVID=0x%X SUCCESS\n", devId);
- }
- return ret;
-}
-#endif /* HAVE_ECC && HAVE_ECC_SIGN && HAVE_ECC_VERIFY */
-
-
-#ifdef HAVE_ED25519
-/* Exercises wh_Client_Ed25519Sign with an undersized output buffer. The
- * client must return WH_ERROR_BUFFER_SIZE and report the required size
- * in *inout_sig_len rather than silently truncating the signature. */
-int whTest_CryptoEd25519BufferTooSmall(whClientContext* ctx)
-{
- int devId = WH_DEV_ID;
- int ret;
- WC_RNG rng[1];
- ed25519_key key[1];
- const byte msg[] = "ed25519 buf size test";
- uint8_t small_sig[ED25519_SIG_SIZE - 1] = {0};
- uint8_t full_sig[ED25519_SIG_SIZE] = {0};
- uint32_t sig_len;
-
- ret = wc_InitRng_ex(rng, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
- return ret;
- }
-
- ret = wc_ed25519_init_ex(key, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_ed25519_init_ex %d\n", ret);
- (void)wc_FreeRng(rng);
- return ret;
- }
-
- ret = wc_ed25519_make_key(rng, ED25519_KEY_SIZE, key);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_ed25519_make_key %d\n", ret);
- goto done;
- }
-
- sig_len = (uint32_t)sizeof(small_sig);
- ret = wh_Client_Ed25519Sign(ctx, key, msg, (uint32_t)sizeof(msg),
- (uint8_t)Ed25519, NULL, 0, small_sig,
- &sig_len);
- if (ret != WH_ERROR_BUFFER_SIZE) {
- WH_ERROR_PRINT(
- "Ed25519Sign small buf expected WH_ERROR_BUFFER_SIZE, got %d\n",
- ret);
- ret = WH_TEST_FAIL;
- goto done;
- }
- if (sig_len != ED25519_SIG_SIZE) {
- WH_ERROR_PRINT(
- "Ed25519Sign small buf reported size %u, expected %u\n",
- (unsigned)sig_len, (unsigned)ED25519_SIG_SIZE);
- ret = WH_TEST_FAIL;
- goto done;
- }
-
- sig_len = (uint32_t)sizeof(full_sig);
- ret = wh_Client_Ed25519Sign(ctx, key, msg, (uint32_t)sizeof(msg),
- (uint8_t)Ed25519, NULL, 0, full_sig,
- &sig_len);
- if (ret != 0) {
- WH_ERROR_PRINT("Ed25519Sign full buf failed: %d\n", ret);
- goto done;
- }
- if (sig_len != ED25519_SIG_SIZE) {
- WH_ERROR_PRINT("Ed25519Sign full buf size %u, expected %u\n",
- (unsigned)sig_len, (unsigned)ED25519_SIG_SIZE);
- ret = WH_TEST_FAIL;
- goto done;
- }
-
- WH_TEST_PRINT("Ed25519 buffer-size DEVID=0x%X SUCCESS\n", devId);
- ret = 0;
-
-done:
- (void)wc_ed25519_free(key);
- (void)wc_FreeRng(rng);
- return ret;
-}
-#endif /* HAVE_ED25519 */
-
-
-#ifndef NO_RSA
-/* Exercises wh_Client_RsaFunction with an undersized output buffer. */
-int whTest_CryptoRsaBufferTooSmall(whClientContext* ctx)
-{
- const int devId = WH_DEV_ID;
- const int rsa_key_bits = 2048;
- const int rsa_key_bytes = rsa_key_bits / 8;
- int ret;
- RsaKey rsa[1];
- const byte plain[] = "rsa buf size test";
- uint8_t small_out[16] = {0};
- uint8_t full_out[256 /* RSA-2048 modulus */] = {0};
- uint16_t out_len;
-
- ret = wc_InitRsaKey_ex(rsa, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_InitRsaKey_ex %d\n", ret);
- return ret;
- }
-
- ret = wh_Client_RsaMakeExportKey(ctx, rsa_key_bits, WC_RSA_EXPONENT, rsa);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wh_Client_RsaMakeExportKey %d\n", ret);
- goto done;
- }
-
- out_len = (uint16_t)sizeof(small_out);
- ret = wh_Client_RsaFunction(ctx, rsa, RSA_PUBLIC_ENCRYPT, plain,
- (uint16_t)sizeof(plain), small_out,
- &out_len);
- /* Server's wc_RsaFunction pre-checks the buffer and returns
- RSA_BUFFER_E. */
- if (ret != WH_ERROR_BUFFER_SIZE && ret != RSA_BUFFER_E) {
- WH_ERROR_PRINT(
- "RsaFunction small buf expected WH_ERROR_BUFFER_SIZE or "
- "RSA_BUFFER_E, got %d\n",
- ret);
- ret = WH_TEST_FAIL;
- goto done;
- }
-
- out_len = (uint16_t)sizeof(full_out);
- ret = wh_Client_RsaFunction(ctx, rsa, RSA_PUBLIC_ENCRYPT, plain,
- (uint16_t)sizeof(plain), full_out,
- &out_len);
- if (ret != 0) {
- WH_ERROR_PRINT("RsaFunction full buf failed: %d\n", ret);
- goto done;
- }
- if (out_len != (uint16_t)rsa_key_bytes) {
- WH_ERROR_PRINT("RsaFunction full buf size %u, expected %d\n",
- (unsigned)out_len, rsa_key_bytes);
- ret = WH_TEST_FAIL;
- goto done;
- }
-
- WH_TEST_PRINT("RSA buffer-size DEVID=0x%X SUCCESS\n", devId);
- ret = 0;
-
-done:
- (void)wc_FreeRsaKey(rsa);
- return ret;
-}
-#endif /* !NO_RSA */
-
-
-#if defined(HAVE_DILITHIUM) && \
- !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \
- !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && !defined(WOLFSSL_NO_ML_DSA_44)
-/* Exercises wh_Client_MlDsaSign with an undersized output buffer. */
-int whTest_CryptoMlDsaBufferTooSmall(whClientContext* ctx)
-{
- int devId = WH_DEV_ID;
- int ret;
- MlDsaKey key[1];
- const byte msg[] = "ml-dsa buf size test";
- uint8_t small_sig[16] = {0};
- uint8_t full_sig[DILITHIUM_MAX_SIG_SIZE] = {0};
- word32 small_buf_sz = (word32)sizeof(small_sig);
- word32 sig_len;
-
- ret = wc_MlDsaKey_Init(key, NULL, devId);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wc_MlDsaKey_Init %d\n", ret);
- return ret;
- }
-
- ret = wh_Client_MlDsaMakeExportKey(ctx, WC_ML_DSA_44, 0, key);
- if (ret != 0) {
- WH_ERROR_PRINT("Failed to wh_Client_MlDsaMakeExportKey %d\n", ret);
- goto done;
- }
-
- sig_len = small_buf_sz;
- ret = wh_Client_MlDsaSign(ctx, msg, (word32)sizeof(msg), small_sig,
- &sig_len, key, NULL, 0, WC_HASH_TYPE_NONE);
- if (ret != WH_ERROR_BUFFER_SIZE) {
- WH_ERROR_PRINT(
- "MlDsaSign small buf expected WH_ERROR_BUFFER_SIZE, got %d\n", ret);
- ret = WH_TEST_FAIL;
- goto done;
- }
- if (sig_len <= small_buf_sz) {
- WH_ERROR_PRINT(
- "MlDsaSign small buf reported size %u not greater than %u\n",
- (unsigned)sig_len, (unsigned)small_buf_sz);
- ret = WH_TEST_FAIL;
- goto done;
- }
-
- sig_len = (word32)sizeof(full_sig);
- ret = wh_Client_MlDsaSign(ctx, msg, (word32)sizeof(msg), full_sig,
- &sig_len, key, NULL, 0, WC_HASH_TYPE_NONE);
- if (ret != 0) {
- WH_ERROR_PRINT("MlDsaSign full buf failed: %d\n", ret);
- goto done;
- }
-
- WH_TEST_PRINT("ML-DSA buffer-size DEVID=0x%X SUCCESS\n", devId);
- ret = 0;
-
-done:
- wc_MlDsaKey_Free(key);
- return ret;
-}
-#endif /* HAVE_DILITHIUM && ML-DSA-44 enabled */
-
-#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_aes.c b/test-refactor/client-server/wh_test_crypto_aes.c
new file mode 100644
index 000000000..2021d9884
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_aes.c
@@ -0,0 +1,465 @@
+/*
+ * 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_aes.c
+ *
+ * AES round-trips routed through the server via WH_DEV_ID. Covers CBC, CTR,
+ * ECB, and GCM in both client-side-key and HSM-cached-key forms, plus the
+ * AES-CBC streaming request/response API. Under a WOLFHSM_CFG_DMA build the
+ * wc_Aes* calls dispatch to the *Dma wrappers via the cryptocb.
+ */
+
+#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/aes.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"
+
+#if !defined(NO_AES) && \
+ (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \
+ defined(HAVE_AES_ECB) || defined(HAVE_AESGCM))
+static int whTest_CryptoAesImpl(whClientContext* ctx, int devId)
+{
+ int ret = 0;
+ Aes aes[1];
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t labelIn[WH_NVM_LABEL_LEN] = "AES Key Label";
+
+ /* NIST SP 800-38A / SP 800-38B test vectors (key + 64-byte message
+ * spanning four AES blocks). Deterministic vectors keep the test
+ * self-contained, no RNG needed. */
+ const uint8_t key[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae,
+ 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88,
+ 0x09, 0xcf, 0x4f, 0x3c};
+ const uint8_t iv[AES_BLOCK_SIZE] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f};
+ const uint8_t plainIn[64] = {
+ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e,
+ 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03,
+ 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30,
+ 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19,
+ 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b,
+ 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10};
+ uint8_t cipher[sizeof(plainIn)];
+ uint8_t plainOut[sizeof(plainIn)];
+
+#ifdef HAVE_AES_CBC
+ /* CBC with client-side key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_AesInit %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ }
+ if (ret == 0) {
+ ret = wc_AesCbcEncrypt(aes, cipher, plainIn, sizeof(plainIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_DECRYPTION);
+ }
+ if (ret == 0) {
+ ret = wc_AesCbcDecrypt(aes, plainOut, cipher, sizeof(cipher));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-CBC client key failed to match\n");
+ ret = -1;
+ }
+ (void)wc_AesFree(aes);
+ }
+
+ /* CBC with HSM-cached key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(
+ ctx, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT,
+ labelIn, sizeof(labelIn), key, sizeof(key), &keyId);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ }
+ if (ret == 0) {
+ ret = wc_AesCbcEncrypt(aes, cipher, plainIn, sizeof(plainIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ }
+ if (ret == 0) {
+ ret = wc_AesCbcDecrypt(aes, plainOut, cipher, sizeof(plainIn));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-CBC HSM key failed to match\n");
+ ret = -1;
+ }
+ if (keyId != WH_KEYID_ERASED) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ keyId = WH_KEYID_ERASED;
+ }
+ (void)wc_AesFree(aes);
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("AES CBC DEVID=0x%X SUCCESS\n", devId);
+ }
+#endif /* HAVE_AES_CBC */
+
+#ifdef WOLFSSL_AES_COUNTER
+ /* CTR with client-side key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesSetKeyDirect(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ }
+ if (ret == 0) {
+ ret = wc_AesCtrEncrypt(aes, cipher, plainIn, sizeof(plainIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesSetKeyDirect(aes, key, sizeof(key), iv, AES_DECRYPTION);
+ }
+ if (ret == 0) {
+ ret = wc_AesCtrEncrypt(aes, plainOut, cipher, sizeof(cipher));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-CTR client key failed to match\n");
+ ret = -1;
+ }
+ (void)wc_AesFree(aes);
+ }
+
+ /* CTR with HSM-cached key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(
+ ctx, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT,
+ labelIn, sizeof(labelIn), key, sizeof(key), &keyId);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ }
+ if (ret == 0) {
+ ret = wc_AesCtrEncrypt(aes, cipher, plainIn, sizeof(plainIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ }
+ if (ret == 0) {
+ ret = wc_AesCtrEncrypt(aes, plainOut, cipher, sizeof(plainIn));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-CTR HSM key failed to match\n");
+ ret = -1;
+ }
+ if (keyId != WH_KEYID_ERASED) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ keyId = WH_KEYID_ERASED;
+ }
+ (void)wc_AesFree(aes);
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("AES CTR DEVID=0x%X SUCCESS\n", devId);
+ }
+#endif /* WOLFSSL_AES_COUNTER */
+
+#ifdef HAVE_AES_ECB
+ /* ECB with client-side key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ /* AES-ECB does not use an IV */
+ ret = wc_AesSetKey(aes, key, sizeof(key), NULL, AES_ENCRYPTION);
+ }
+ if (ret == 0) {
+ ret = wc_AesEcbEncrypt(aes, cipher, plainIn, sizeof(plainIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), NULL, AES_DECRYPTION);
+ }
+ if (ret == 0) {
+ ret = wc_AesEcbDecrypt(aes, plainOut, cipher, sizeof(cipher));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-ECB client key failed to match\n");
+ ret = -1;
+ }
+ (void)wc_AesFree(aes);
+ }
+
+ /* ECB with HSM-cached key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(
+ ctx, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT,
+ labelIn, sizeof(labelIn), key, sizeof(key), &keyId);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, NULL);
+ }
+ if (ret == 0) {
+ ret = wc_AesEcbEncrypt(aes, cipher, plainIn, sizeof(plainIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, NULL);
+ }
+ if (ret == 0) {
+ ret = wc_AesEcbDecrypt(aes, plainOut, cipher, sizeof(plainIn));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-ECB HSM key failed to match\n");
+ ret = -1;
+ }
+ if (keyId != WH_KEYID_ERASED) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ keyId = WH_KEYID_ERASED;
+ }
+ (void)wc_AesFree(aes);
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("AES ECB DEVID=0x%X SUCCESS\n", devId);
+ }
+#endif /* HAVE_AES_ECB */
+
+#ifdef HAVE_AESGCM
+ {
+ const uint8_t authIn[16] = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad,
+ 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce,
+ 0xde, 0xad, 0xbe, 0xef};
+ uint8_t authTag[16];
+
+ /* GCM with client-side key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ memset(authTag, 0, sizeof(authTag));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesGcmSetKey(aes, key, sizeof(key));
+ }
+ if (ret == 0) {
+ ret = wc_AesGcmEncrypt(aes, cipher, plainIn, sizeof(plainIn),
+ iv, sizeof(iv), authTag, sizeof(authTag),
+ authIn, sizeof(authIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesGcmDecrypt(aes, plainOut, cipher, sizeof(plainIn),
+ iv, sizeof(iv), authTag, sizeof(authTag),
+ authIn, sizeof(authIn));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-GCM client key failed to match\n");
+ ret = -1;
+ }
+ (void)wc_AesFree(aes);
+ }
+
+ /* GCM with HSM-cached key */
+ if (ret == 0) {
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+ memset(authTag, 0, sizeof(authTag));
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx,
+ WH_NVM_FLAGS_USAGE_ENCRYPT |
+ WH_NVM_FLAGS_USAGE_DECRYPT,
+ labelIn, sizeof(labelIn), key,
+ sizeof(key), &keyId);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ ret = wc_AesGcmEncrypt(aes, cipher, plainIn, sizeof(plainIn),
+ iv, sizeof(iv), authTag, sizeof(authTag),
+ authIn, sizeof(authIn));
+ }
+ if (ret == 0) {
+ ret = wc_AesGcmDecrypt(aes, plainOut, cipher, sizeof(plainIn),
+ iv, sizeof(iv), authTag, sizeof(authTag),
+ authIn, sizeof(authIn));
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-GCM HSM key failed to match\n");
+ ret = -1;
+ }
+ if (keyId != WH_KEYID_ERASED) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ keyId = WH_KEYID_ERASED;
+ }
+ (void)wc_AesFree(aes);
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("AES GCM DEVID=0x%X SUCCESS\n", devId);
+ }
+ }
+#endif /* HAVE_AESGCM */
+
+ return ret;
+}
+
+#ifdef HAVE_AES_CBC
+/*
+ * AES-CBC streaming via the explicit request/response API. Drives the server
+ * directly with INVALID_DEVID so the cryptocb is not invoked, covering
+ * wh_Client_AesCbcRequest / wh_Client_AesCbcResponse and IV chaining across two
+ * halves. Not devId-routed, so this runs once rather than per devId.
+ */
+static int whTest_CryptoAesCbcStreaming(whClientContext* ctx)
+{
+ int ret = 0;
+ Aes aes[1];
+ const uint8_t key[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae,
+ 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88,
+ 0x09, 0xcf, 0x4f, 0x3c};
+ const uint8_t iv[AES_BLOCK_SIZE] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f};
+ const uint8_t plainIn[64] = {
+ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e,
+ 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03,
+ 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30,
+ 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19,
+ 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b,
+ 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10};
+ uint8_t cipher[sizeof(plainIn)];
+ uint8_t plainOut[sizeof(plainIn)];
+ const uint32_t halfSize = sizeof(plainIn) / 2;
+ uint32_t outSize = 0;
+
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
+
+ ret = wc_AesInit(aes, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesCbcRequest(ctx, aes, 1, plainIn, halfSize);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_AesCbcResponse(ctx, aes, cipher, &outSize);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesCbcRequest(ctx, aes, 1, plainIn + halfSize,
+ halfSize);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_AesCbcResponse(ctx, aes, cipher + halfSize,
+ &outSize);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_DECRYPTION);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesCbcRequest(ctx, aes, 0, cipher, halfSize);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_AesCbcResponse(ctx, aes, plainOut, &outSize);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesCbcRequest(ctx, aes, 0, cipher + halfSize,
+ halfSize);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_AesCbcResponse(ctx, aes, plainOut + halfSize,
+ &outSize);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-CBC streaming failed to match\n");
+ ret = -1;
+ }
+ (void)wc_AesFree(aes);
+ if (ret == 0) {
+ WH_TEST_PRINT("AES CBC ASYNC STREAMING SUCCESS\n");
+ }
+ return ret;
+}
+#endif /* HAVE_AES_CBC */
+
+int whTest_Crypto_Aes(whClientContext* ctx)
+{
+ int i, devId;
+
+ /* AES round-trips dispatch through the cryptocb, so run on every devId to
+ * cover both the normal and DMA server transports. */
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(whTest_CryptoAesImpl(ctx, devId));
+ }
+#ifdef HAVE_AES_CBC
+ /* CBC streaming drives the request/response API directly (INVALID_DEVID),
+ * so it is not devId-routed -- run it once. */
+ WH_TEST_RETURN_ON_FAIL(whTest_CryptoAesCbcStreaming(ctx));
+#endif
+ /* TODO: port legacy AES async + DMA-async coverage (comm-buffer & DMA, round-trip & KAT) -- the only remaining legacy crypto parity gap; deferred to follow-up PR. */
+ return 0;
+}
+#endif /* !NO_AES && (HAVE_AES_CBC || WOLFSSL_AES_COUNTER || HAVE_AES_ECB || \
+ HAVE_AESGCM) */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_cmac.c b/test-refactor/client-server/wh_test_crypto_cmac.c
new file mode 100644
index 000000000..123b0ee87
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_cmac.c
@@ -0,0 +1,378 @@
+/*
+ * 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_cmac.c
+ *
+ * AES-CMAC tests against NIST SP 800-38B vectors. For each (key, msg, tag)
+ * triple, exercises (a) one-shot generate with a cached server key,
+ * (b) one-shot verify, and (c) incremental update/finalize. Also commits a
+ * key to NVM and verifies through the cache->NVM->cache fetch path.
+ */
+
+#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/aes.h"
+#include "wolfssl/wolfcrypt/cmac.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"
+
+#if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT)
+static int whTest_CryptoCmacImpl(whClientContext* ctx, int devId)
+{
+ int ret = 0;
+ Cmac cmac[1];
+ uint8_t tag[AES_BLOCK_SIZE] = {0};
+ word32 tagSz;
+ whKeyId keyId;
+ uint8_t labelIn[WH_NVM_LABEL_LEN] = "CMAC Key Label";
+ word32 i;
+
+ /* NIST SP 800-38B test vectors */
+#ifdef WOLFSSL_AES_128
+ const byte k128[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
+#endif
+#ifdef WOLFSSL_AES_192
+ const byte k192[] = {0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52,
+ 0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5,
+ 0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b};
+#endif
+#ifdef WOLFSSL_AES_256
+ const byte k256[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
+ 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
+ 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
+ 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4};
+#endif
+
+ const byte m[] = {
+ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e,
+ 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03,
+ 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30,
+ 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19,
+ 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b,
+ 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10};
+
+ enum {
+ CMAC_MLEN_0 = 0,
+ CMAC_MLEN_128 = 128 / 8,
+ CMAC_MLEN_319 = 320 / 8 - 1,
+ CMAC_MLEN_320 = 320 / 8,
+ CMAC_MLEN_512 = 512 / 8,
+ };
+
+#ifdef WOLFSSL_AES_128
+ const byte t128_0[] = {0xbb, 0x1d, 0x69, 0x29, 0xe9, 0x59, 0x37, 0x28,
+ 0x7f, 0xa3, 0x7d, 0x12, 0x9b, 0x75, 0x67, 0x46};
+ const byte t128_128[] = {0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44,
+ 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c};
+ const byte t128_319[] = {0x2c, 0x17, 0x84, 0x4c, 0x93, 0x1c, 0x07, 0x95,
+ 0x15, 0x92, 0x73, 0x0a, 0x34, 0xd0, 0xd9, 0xd2};
+ const byte t128_320[] = {0xdf, 0xa6, 0x67, 0x47, 0xde, 0x9a, 0xe6, 0x30,
+ 0x30, 0xca, 0x32, 0x61, 0x14, 0x97, 0xc8, 0x27};
+ const byte t128_512[] = {0x51, 0xf0, 0xbe, 0xbf, 0x7e, 0x3b, 0x9d, 0x92,
+ 0xfc, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3c, 0xfe};
+#endif
+#ifdef WOLFSSL_AES_192
+ const byte t192_0[] = {0xd1, 0x7d, 0xdf, 0x46, 0xad, 0xaa, 0xcd, 0xe5,
+ 0x31, 0xca, 0xc4, 0x83, 0xde, 0x7a, 0x93, 0x67};
+ const byte t192_128[] = {0x9e, 0x99, 0xa7, 0xbf, 0x31, 0xe7, 0x10, 0x90,
+ 0x06, 0x62, 0xf6, 0x5e, 0x61, 0x7c, 0x51, 0x84};
+ const byte t192_320[] = {0x8a, 0x1d, 0xe5, 0xbe, 0x2e, 0xb3, 0x1a, 0xad,
+ 0x08, 0x9a, 0x82, 0xe6, 0xee, 0x90, 0x8b, 0x0e};
+ const byte t192_512[] = {0xa1, 0xd5, 0xdf, 0x0e, 0xed, 0x79, 0x0f, 0x79,
+ 0x4d, 0x77, 0x58, 0x96, 0x59, 0xf3, 0x9a, 0x11};
+#endif
+#ifdef WOLFSSL_AES_256
+ const byte t256_0[] = {0x02, 0x89, 0x62, 0xf6, 0x1b, 0x7b, 0xf8, 0x9e,
+ 0xfc, 0x6b, 0x55, 0x1f, 0x46, 0x67, 0xd9, 0x83};
+ const byte t256_128[] = {0x28, 0xa7, 0x02, 0x3f, 0x45, 0x2e, 0x8f, 0x82,
+ 0xbd, 0x4b, 0xf2, 0x8d, 0x8c, 0x37, 0xc3, 0x5c};
+ const byte t256_320[] = {0xaa, 0xf3, 0xd8, 0xf1, 0xde, 0x56, 0x40, 0xc2,
+ 0x32, 0xf5, 0xb1, 0x69, 0xb9, 0xc9, 0x11, 0xe6};
+ const byte t256_512[] = {0xe1, 0x99, 0x21, 0x90, 0x54, 0x9f, 0x6e, 0xd5,
+ 0x69, 0x6a, 0x2c, 0x05, 0x6c, 0x31, 0x54, 0x10};
+#endif
+
+ typedef struct {
+ const byte* k;
+ word32 kSz;
+ const byte* m;
+ word32 mSz;
+ const byte* t;
+ word32 tSz;
+ word32 partial;
+ } CmacTestCase;
+
+ const CmacTestCase testCases[] = {
+#ifdef WOLFSSL_AES_128
+ {k128, sizeof(k128), m, CMAC_MLEN_0, t128_0, AES_BLOCK_SIZE, 0},
+ {k128, sizeof(k128), m, CMAC_MLEN_128, t128_128, AES_BLOCK_SIZE, 0},
+ {k128, sizeof(k128), m, CMAC_MLEN_319, t128_319, AES_BLOCK_SIZE, 0},
+ {k128, sizeof(k128), m, CMAC_MLEN_320, t128_320, AES_BLOCK_SIZE, 0},
+ {k128, sizeof(k128), m, CMAC_MLEN_512, t128_512, AES_BLOCK_SIZE, 0},
+ {k128, sizeof(k128), m, CMAC_MLEN_512, t128_512, AES_BLOCK_SIZE, 5},
+#endif
+#ifdef WOLFSSL_AES_192
+ {k192, sizeof(k192), m, CMAC_MLEN_0, t192_0, AES_BLOCK_SIZE, 0},
+ {k192, sizeof(k192), m, CMAC_MLEN_128, t192_128, AES_BLOCK_SIZE, 0},
+ {k192, sizeof(k192), m, CMAC_MLEN_320, t192_320, AES_BLOCK_SIZE, 0},
+ {k192, sizeof(k192), m, CMAC_MLEN_512, t192_512, AES_BLOCK_SIZE, 0},
+#endif
+#ifdef WOLFSSL_AES_256
+ {k256, sizeof(k256), m, CMAC_MLEN_0, t256_0, AES_BLOCK_SIZE, 0},
+ {k256, sizeof(k256), m, CMAC_MLEN_128, t256_128, AES_BLOCK_SIZE, 0},
+ {k256, sizeof(k256), m, CMAC_MLEN_320, t256_320, AES_BLOCK_SIZE, 0},
+ {k256, sizeof(k256), m, CMAC_MLEN_512, t256_512, AES_BLOCK_SIZE, 0},
+#endif
+ };
+ const word32 numCases = sizeof(testCases) / sizeof(testCases[0]);
+
+ for (i = 0; i < numCases && ret == 0; i++) {
+ const CmacTestCase* tc = &testCases[i];
+
+ /* (a) One-shot generate with cached key */
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_SIGN, labelIn,
+ sizeof(labelIn), (uint8_t*)tc->k, tc->kSz,
+ &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache (gen) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_InitCmac_ex (gen) tc=%d %d\n", i, ret);
+ break;
+ }
+ ret = wh_Client_CmacSetKeyId(cmac, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_CmacSetKeyId (gen) tc=%d %d\n", i, ret);
+ break;
+ }
+ memset(tag, 0, sizeof(tag));
+ tagSz = sizeof(tag);
+ ret = wc_AesCmacGenerate_ex(cmac, tag, &tagSz, tc->m, tc->mSz, NULL, 0,
+ NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_AesCmacGenerate_ex tc=%d %d\n", i, ret);
+ break;
+ }
+ if (memcmp(tag, tc->t, AES_BLOCK_SIZE) != 0) {
+ WH_ERROR_PRINT("CMAC generate mismatch tc=%d\n", i);
+ ret = -1;
+ break;
+ }
+ ret = wh_Client_KeyEvict(ctx, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyEvict (gen) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+
+ /* (b) One-shot verify with cached key */
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_VERIFY, labelIn,
+ sizeof(labelIn), (uint8_t*)tc->k, tc->kSz,
+ &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache (ver) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ ret = wh_Client_CmacSetKeyId(cmac, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_CmacSetKeyId (ver) tc=%d %d\n", i, ret);
+ break;
+ }
+ ret = wc_AesCmacVerify_ex(cmac, tc->t, tc->tSz, tc->m, tc->mSz, NULL,
+ 0, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_AesCmacVerify_ex tc=%d %d\n", i, ret);
+ break;
+ }
+ wc_CmacFree(cmac);
+ ret = wh_Client_KeyEvict(ctx, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyEvict (ver) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+
+ /* (c) Incremental init/update/final with cached key */
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_SIGN, labelIn,
+ sizeof(labelIn), (uint8_t*)tc->k, tc->kSz,
+ &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache (inc) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_InitCmac_ex (inc) tc=%d %d\n", i, ret);
+ break;
+ }
+ ret = wh_Client_CmacSetKeyId(cmac, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_CmacSetKeyId (inc) tc=%d %d\n", i, ret);
+ break;
+ }
+ if (tc->partial > 0) {
+ word32 firstSz = tc->mSz / 2 - tc->partial;
+ word32 secondSz = tc->mSz / 2 + tc->partial;
+ ret = wc_CmacUpdate(cmac, tc->m, firstSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_CmacUpdate (inc1) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ ret = wc_CmacUpdate(cmac, tc->m + firstSz, secondSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_CmacUpdate (inc2) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ }
+ else {
+ ret = wc_CmacUpdate(cmac, tc->m, tc->mSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_CmacUpdate (inc) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ }
+ memset(tag, 0, sizeof(tag));
+ tagSz = sizeof(tag);
+ ret = wc_CmacFinal(cmac, tag, &tagSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_CmacFinal (inc) tc=%d %d\n", i, ret);
+ break;
+ }
+ if (memcmp(tag, tc->t, AES_BLOCK_SIZE) != 0) {
+ WH_ERROR_PRINT("CMAC incremental mismatch tc=%d\n", i);
+ ret = -1;
+ break;
+ }
+ wc_CmacFree(cmac);
+ ret = wh_Client_KeyEvict(ctx, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyEvict (inc) tc=%d %d\n", i,
+ ret);
+ break;
+ }
+ }
+
+ /* Round-trip a key through commit / evict / re-cache, then verify a tag
+ * computed from the still-valid key material. */
+ if (ret == 0) {
+#ifdef WOLFSSL_AES_128
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_VERIFY, labelIn,
+ sizeof(labelIn), (uint8_t*)k128,
+ sizeof(k128), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache (commit) %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 {
+ ret = wc_InitCmac_ex(cmac, k128, sizeof(k128), WC_CMAC_AES,
+ NULL, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitCmac_ex %d\n", ret);
+ }
+ else {
+ ret = wh_Client_CmacSetKeyId(cmac, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_CmacSetKeyId %d\n", ret);
+ }
+ else {
+ tagSz = sizeof(tag);
+ ret = wc_AesCmacVerify_ex(
+ cmac, t128_512, sizeof(t128_512), m,
+ CMAC_MLEN_512, NULL, 0, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed wc_AesCmacVerify_ex (commit) "
+ "%d\n",
+ ret);
+ }
+ else {
+ ret = wh_Client_KeyErase(ctx, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_KeyErase %d\n",
+ ret);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#endif /* WOLFSSL_AES_128 */
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("CMAC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+int whTest_Crypto_Cmac(whClientContext* ctx)
+{
+ int i, devId;
+
+ /* CMAC dispatches through the cryptocb, so run on every devId to cover both
+ * the normal and DMA server transports. */
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(whTest_CryptoCmacImpl(ctx, devId));
+ }
+ return 0;
+}
+#endif /* WOLFSSL_CMAC && !NO_AES && WOLFSSL_AES_DIRECT */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_curve25519.c b/test-refactor/client-server/wh_test_crypto_curve25519.c
new file mode 100644
index 000000000..ab3b7f141
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_curve25519.c
@@ -0,0 +1,365 @@
+/*
+ * 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_curve25519.c
+ *
+ * Curve25519 ECDH round-trips routed through the server via WH_DEV_ID,
+ * across ephemeral / server-export / server-cache key 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/curve25519.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"
+
+#ifdef HAVE_CURVE25519
+static int _whTest_CryptoCurve25519(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ curve25519_key key_a[1] = {0};
+ curve25519_key key_b[1] = {0};
+ uint8_t shared_ab[CURVE25519_KEYSIZE] = {0};
+ uint8_t shared_ba[CURVE25519_KEYSIZE] = {0};
+ int key_size = CURVE25519_KEYSIZE;
+ whNvmFlags flags = WH_NVM_FLAGS_USAGE_DERIVE;
+ whKeyId key_id_a = WH_KEYID_ERASED;
+ uint8_t label_a[WH_NVM_LABEL_LEN] = "Curve25519 Label A";
+ whKeyId key_id_b = 42;
+ uint8_t label_b[WH_NVM_LABEL_LEN] = "Curve25519 Label B";
+ word32 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;
+ }
+
+ /* Test 1: ephemeral wolfCrypt keys via WH_DEV_ID */
+ ret = wc_curve25519_init_ex(key_a, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret);
+ }
+ else {
+ ret = wc_curve25519_init_ex(key_b, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret);
+ }
+ else {
+ ret = wc_curve25519_make_key(rng, key_size, key_a);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_make_key %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wc_curve25519_make_key(rng, key_size, key_b);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_make_key %d\n",
+ ret);
+ }
+ }
+ if (ret == 0) {
+ len = sizeof(shared_ab);
+ ret =
+ wc_curve25519_shared_secret(key_a, key_b, shared_ab, &len);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to compute shared secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ len = sizeof(shared_ba);
+ ret =
+ wc_curve25519_shared_secret(key_b, key_a, shared_ba, &len);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to compute shared secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ if (memcmp(shared_ab, shared_ba, len) != 0) {
+ WH_ERROR_PRINT("CURVE25519 secrets don't match\n");
+ ret = -1;
+ }
+ }
+ wc_curve25519_free(key_b);
+ }
+ wc_curve25519_free(key_a);
+ }
+
+ /* Test 2: server creates keys and exports them to client */
+ if (ret == 0) {
+ ret = wc_curve25519_init_ex(key_a, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret);
+ }
+ else {
+ ret = wc_curve25519_init_ex(key_b, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret);
+ }
+ else {
+ ret = wh_Client_Curve25519MakeExportKey(ctx, key_size, key_a);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make exported key %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Curve25519MakeExportKey(ctx, key_size,
+ key_b);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make exported key %d\n",
+ ret);
+ }
+ }
+ if (ret == 0) {
+ len = sizeof(shared_ab);
+ ret = wc_curve25519_shared_secret(key_a, key_b, shared_ab,
+ &len);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute shared secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ len = sizeof(shared_ba);
+ ret = wc_curve25519_shared_secret(key_b, key_a, shared_ba,
+ &len);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute shared secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ if (memcmp(shared_ab, shared_ba, len) != 0) {
+ WH_ERROR_PRINT("CURVE25519 secrets don't match\n");
+ ret = -1;
+ }
+ }
+ wc_curve25519_free(key_b);
+ }
+ wc_curve25519_free(key_a);
+ }
+ }
+
+ /* Test 3: server-cached keys referenced via keyId */
+ if (ret == 0) {
+ ret = wc_curve25519_init_ex(key_a, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret);
+ }
+ else {
+ ret = wc_curve25519_init_ex(key_b, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret);
+ }
+ else {
+ ret = wh_Client_Curve25519MakeCacheKey(
+ ctx, key_size, &key_id_a, flags, label_a, sizeof(label_a));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make cached key %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Curve25519MakeCacheKey(
+ ctx, key_size, &key_id_b, flags, label_b,
+ sizeof(label_b));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make cached key %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ len = sizeof(shared_ab);
+ wh_Client_Curve25519SetKeyId(key_a, key_id_a);
+ wh_Client_Curve25519SetKeyId(key_b, key_id_b);
+ ret = wc_curve25519_shared_secret(key_a, key_b, shared_ab,
+ &len);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute shared secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ len = sizeof(shared_ba);
+ ret = wc_curve25519_shared_secret(key_b, key_a, shared_ba,
+ &len);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute shared secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ if (memcmp(shared_ab, shared_ba, len) != 0) {
+ WH_ERROR_PRINT("CURVE25519 secrets don't match\n");
+ ret = -1;
+ }
+ }
+ if (!WH_KEYID_ISERASED(key_id_a)) {
+ (void)wh_Client_KeyEvict(ctx, key_id_a);
+ }
+ if (!WH_KEYID_ISERASED(key_id_b)) {
+ (void)wh_Client_KeyEvict(ctx, key_id_b);
+ }
+ wc_curve25519_free(key_b);
+ }
+ wc_curve25519_free(key_a);
+ }
+ }
+
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("CURVE25519 SUCCESS\n");
+ }
+ return ret;
+}
+
+/* Cache a NONEXPORTABLE Curve25519 keypair on the server, then verify the
+ * server-side ECDH (private cached, public local) produces the same shared
+ * secret as a client-side ECDH (private local, public exported via
+ * wh_Client_Curve25519ExportPublicKey). */
+static int _whTest_CryptoCurve25519ExportPublicKey(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ curve25519_key hsmPub[1] = {0};
+ curve25519_key hsmPriv[1] = {0};
+ curve25519_key localKey[1] = {0};
+ uint8_t sharedHsm[CURVE25519_KEYSIZE] = {0};
+ uint8_t sharedLocal[CURVE25519_KEYSIZE] = {0};
+ word32 secLen = 0;
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t denyBuf[256];
+ uint16_t denyLen = sizeof(denyBuf);
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wh_Client_Curve25519MakeCacheKey(
+ ctx, (uint16_t)CURVE25519_KEYSIZE, &keyId,
+ WH_NVM_FLAGS_USAGE_DERIVE | WH_NVM_FLAGS_NONEXPORTABLE, NULL, 0);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to make NONEXPORTABLE cached Curve25519 key %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ /* Full export must be denied by the NONEXPORTABLE policy. */
+ {
+ int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf,
+ &denyLen);
+ if (denyRet != WH_ERROR_ACCESS) {
+ WH_ERROR_PRINT(
+ "NONEXPORTABLE Curve25519 full export was not denied: %d\n",
+ denyRet);
+ ret = -1;
+ }
+ }
+
+ /* Public-only export must succeed and parse into a usable key struct. */
+ if (ret == 0) {
+ ret = wc_curve25519_init_ex(hsmPub, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wh_Client_Curve25519ExportPublicKey(ctx, keyId, hsmPub, 0,
+ NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "wh_Client_Curve25519ExportPublicKey failed %d\n", ret);
+ }
+ }
+ }
+
+ /* Generate a local ephemeral keypair to ECDH against the exported pub. */
+ if (ret == 0) {
+ ret = wc_curve25519_init_ex(localKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, localKey);
+ }
+ }
+
+ /* Local side: localPriv * exportedHsmPub. */
+ if (ret == 0) {
+ secLen = sizeof(sharedLocal);
+ ret = wc_curve25519_shared_secret(localKey, hsmPub, sharedLocal,
+ &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Local Curve25519 shared secret failed %d\n", ret);
+ }
+ }
+
+ /* HSM side: hsmPriv-by-keyId * localPub, dispatched through cryptoCb. */
+ if (ret == 0) {
+ ret = wc_curve25519_init_ex(hsmPriv, NULL, devId);
+ if (ret == 0) {
+ ret = wh_Client_Curve25519SetKeyId(hsmPriv, keyId);
+ }
+ if (ret == 0) {
+ secLen = sizeof(sharedHsm);
+ ret = wc_curve25519_shared_secret(hsmPriv, localKey, sharedHsm,
+ &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("HSM Curve25519 shared secret failed %d\n", ret);
+ }
+ }
+ wc_curve25519_free(hsmPriv);
+ }
+
+ if (ret == 0 && memcmp(sharedHsm, sharedLocal, secLen) != 0) {
+ WH_ERROR_PRINT("Curve25519 shared secrets don't match\n");
+ ret = -1;
+ }
+
+ wc_curve25519_free(localKey);
+ wc_curve25519_free(hsmPub);
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("CURVE25519 EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+int whTest_Crypto_Curve25519(whClientContext* ctx)
+{
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519ExportPublicKey(ctx));
+ return 0;
+}
+#endif /* HAVE_CURVE25519 */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_ecc.c b/test-refactor/client-server/wh_test_crypto_ecc.c
new file mode 100644
index 000000000..08903968f
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_ecc.c
@@ -0,0 +1,1757 @@
+/*
+ * 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_ecc.c
+ *
+ * ECC tests routed through the server via WH_DEV_ID:
+ * _whTest_CryptoEcc - ECDH + ECDSA across ephemeral / export /
+ * cache key paths
+ * _whTest_CryptoEccCacheDuplicate - cache slot replacement semantics
+ * _whTest_CryptoEccCrossVerify - HSM<->SW signature interop, P-256/384/521
+ * _whTest_CryptoEccAsync - async sign/verify, ECDH, server keygen
+ */
+
+#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/ecc.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 "wolfhsm/wh_crypto.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY)
+
+/* Full coverage of ECDH + ECDSA across the three key-management paths:
+ * - ephemeral wolfCrypt keys with WH_DEV_ID
+ * - server-generated keys exported back to the client
+ * - server-cached keys referenced via keyId */
+#define TEST_ECC_KEYSIZE 32
+#define TEST_ECC_CURVE_ID ECC_SECP256R1
+
+static int _whTest_CryptoEcc(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ WC_RNG rng[1];
+ ecc_key bobKey[1];
+ ecc_key aliceKey[1];
+ uint8_t shared_ab[TEST_ECC_KEYSIZE] = {0};
+ uint8_t shared_ba[TEST_ECC_KEYSIZE] = {0};
+ uint8_t hash[TEST_ECC_KEYSIZE] = {0};
+ uint8_t sig[ECC_MAX_SIG_SIZE] = {0};
+ whKeyId keyIdPrivate = WH_KEYID_ERASED;
+ whKeyId checkKeyId = WH_KEYID_ERASED;
+ whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY |
+ WH_NVM_FLAGS_USAGE_DERIVE;
+ uint8_t labelPrivate[WH_NVM_LABEL_LEN] = "ECC Private Key";
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ /* Test Case 1: Using ephemeral key (normal wolfCrypt flow) */
+ ret = wc_ecc_init_ex(bobKey, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_init_ex %d\n", ret);
+ }
+ else {
+ ret = wc_ecc_init_ex(aliceKey, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_init_ex %d\n", ret);
+ }
+ else {
+ ret = wc_ecc_make_key(rng, TEST_ECC_KEYSIZE, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_make_key %d\n", ret);
+ }
+ else {
+ ret = wc_ecc_make_key(rng, TEST_ECC_KEYSIZE, aliceKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_make_key %d\n", ret);
+ }
+ else {
+ word32 secLen = TEST_ECC_KEYSIZE;
+ ret = wc_ecc_shared_secret(
+ bobKey, aliceKey, (byte*)shared_ab, &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to compute secret %d\n", ret);
+ }
+ else {
+ ret = wc_ecc_shared_secret(aliceKey, bobKey,
+ (byte*)shared_ba, &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to compute secret %d\n",
+ ret);
+ }
+ else if (memcmp(shared_ab, shared_ba, secLen) == 0) {
+ WH_TEST_PRINT("ECC ephemeral ECDH SUCCESS\n");
+ }
+ else {
+ WH_ERROR_PRINT(
+ "ECC ephemeral ECDH FAILED TO MATCH\n");
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ word32 sigLen = sizeof(sig);
+ memcpy(hash, shared_ba, sizeof(hash));
+ ret = wc_ecc_sign_hash((void*)hash, sizeof(hash),
+ (void*)sig, &sigLen, rng,
+ bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_sign_hash %d\n",
+ ret);
+ }
+ else {
+ int res = 0;
+ ret = wc_ecc_verify_hash(
+ (void*)sig, sigLen, (void*)hash,
+ sizeof(hash), &res, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wc_ecc_verify_hash %d\n", ret);
+ }
+ else if (res == 1) {
+ WH_TEST_PRINT(
+ "ECC ephemeral SIGN/VERIFY SUCCESS\n");
+ }
+ else {
+ WH_ERROR_PRINT(
+ "ECC ephemeral SIGN/VERIFY FAIL\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ }
+ wc_ecc_free(aliceKey);
+ }
+ wc_ecc_free(bobKey);
+ }
+
+ /* Test Case 2: Server creates the keys and exports them to the client. */
+ if (ret == 0) {
+ memset(shared_ab, 0, sizeof(shared_ab));
+ memset(shared_ba, 0, sizeof(shared_ba));
+ memset(sig, 0, sizeof(sig));
+
+ ret = wc_ecc_init_ex(bobKey, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_init_ex for export key %d\n", ret);
+ }
+ else {
+ ret = wc_ecc_init_ex(aliceKey, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_init_ex for export key %d\n",
+ ret);
+ }
+ else {
+ ret = wh_Client_EccMakeExportKey(ctx, TEST_ECC_KEYSIZE,
+ TEST_ECC_CURVE_ID, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_EccMakeExportKey %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wh_Client_EccMakeExportKey(
+ ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, aliceKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wh_Client_EccMakeExportKey %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ word32 secLen = TEST_ECC_KEYSIZE;
+ ret = wc_ecc_shared_secret(
+ bobKey, aliceKey, (byte*)shared_ab, &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute export key secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ word32 secLen = TEST_ECC_KEYSIZE;
+ ret = wc_ecc_shared_secret(
+ aliceKey, bobKey, (byte*)shared_ba, &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute export key secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ if (memcmp(shared_ab, shared_ba, TEST_ECC_KEYSIZE) != 0) {
+ WH_ERROR_PRINT(
+ "ECC export key ECDH FAILED TO MATCH\n");
+ ret = -1;
+ }
+ else {
+ WH_TEST_PRINT("ECC export key ECDH SUCCESS\n");
+ }
+ }
+ if (ret == 0) {
+ word32 sigLen = sizeof(sig);
+ memcpy(hash, shared_ba, sizeof(hash));
+ ret = wc_ecc_sign_hash((void*)hash, sizeof(hash),
+ (void*)sig, &sigLen, rng, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign with export key %d\n",
+ ret);
+ }
+ else {
+ int res = 0;
+ ret = wc_ecc_verify_hash((void*)sig, sigLen,
+ (void*)hash, sizeof(hash),
+ &res, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to verify with export key %d\n", ret);
+ }
+ else if (res != 1) {
+ WH_ERROR_PRINT(
+ "ECC export key SIGN/VERIFY FAIL\n");
+ ret = -1;
+ }
+ else {
+ WH_TEST_PRINT(
+ "ECC export key SIGN/VERIFY SUCCESS\n");
+ }
+ }
+ }
+ wc_ecc_free(aliceKey);
+ }
+ wc_ecc_free(bobKey);
+ }
+ }
+
+ /* Test Case 3: Use ONE server-cached key plus an ephemeral peer for ECDH.
+ * Limited to a single cached key so we don't blow the cache budget. */
+ if (ret == 0) {
+ memset(shared_ab, 0, sizeof(shared_ab));
+ memset(shared_ba, 0, sizeof(shared_ba));
+ memset(sig, 0, sizeof(sig));
+ keyIdPrivate = WH_KEYID_ERASED;
+
+ ret = wh_Client_EccMakeCacheKey(ctx, TEST_ECC_KEYSIZE,
+ TEST_ECC_CURVE_ID, &keyIdPrivate, flags,
+ sizeof(labelPrivate), labelPrivate);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_EccMakeCacheKey %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(bobKey, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_init_ex for cache key %d\n",
+ ret);
+ }
+ else {
+ ret = wh_Client_EccSetKeyId(bobKey, keyIdPrivate);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_EccSetKeyId %d\n", ret);
+ }
+ if (ret == 0) {
+ ret = wh_Client_EccGetKeyId(bobKey, &checkKeyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_EccGetKeyId %d\n",
+ ret);
+ }
+ else if (checkKeyId != keyIdPrivate) {
+ WH_ERROR_PRINT(
+ "ECC key ID mismatch: got %u, expected %u\n",
+ checkKeyId, keyIdPrivate);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ /* Required: cached key has no usable curve params yet. */
+ ret = wc_ecc_set_curve(bobKey, TEST_ECC_KEYSIZE,
+ TEST_ECC_CURVE_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ecc_set_curve %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(aliceKey, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wc_ecc_init_ex for peer key %d\n", ret);
+ }
+ else {
+ ret = wc_ecc_make_key(rng, TEST_ECC_KEYSIZE, aliceKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to wc_ecc_make_key for peer %d\n", ret);
+ }
+ if (ret == 0) {
+ word32 secLen = TEST_ECC_KEYSIZE;
+ ret = wc_ecc_shared_secret(
+ bobKey, aliceKey, (byte*)shared_ab, &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute cache key secret %d\n",
+ ret);
+ }
+ }
+ if (ret == 0) {
+ word32 secLen = TEST_ECC_KEYSIZE;
+ ret = wc_ecc_shared_secret(
+ aliceKey, bobKey, (byte*)shared_ba, &secLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to compute peer secret %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ if (memcmp(shared_ab, shared_ba,
+ TEST_ECC_KEYSIZE) != 0) {
+ WH_ERROR_PRINT(
+ "ECC cache key ECDH FAILED TO MATCH\n");
+ ret = -1;
+ }
+ else {
+ WH_TEST_PRINT("ECC cache key ECDH SUCCESS\n");
+ }
+ }
+ wc_ecc_free(aliceKey);
+ }
+ }
+ if (ret == 0) {
+ word32 sigLen = sizeof(sig);
+ memcpy(hash, shared_ba, sizeof(hash));
+ ret = wc_ecc_sign_hash((void*)hash, sizeof(hash),
+ (void*)sig, &sigLen, rng, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign with cache key %d\n",
+ ret);
+ }
+ else {
+ int res = 0;
+ ret = wc_ecc_verify_hash((void*)sig, sigLen,
+ (void*)hash, sizeof(hash),
+ &res, bobKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to verify with cache key %d\n", ret);
+ }
+ else if (res != 1) {
+ WH_ERROR_PRINT(
+ "ECC cache key SIGN/VERIFY FAIL\n");
+ ret = -1;
+ }
+ else {
+ WH_TEST_PRINT(
+ "ECC cache key SIGN/VERIFY SUCCESS\n");
+ }
+ }
+ }
+ wc_ecc_free(bobKey);
+ }
+ }
+ if (!WH_KEYID_ISERASED(keyIdPrivate)) {
+ (void)wh_Client_KeyEvict(ctx, keyIdPrivate);
+ }
+ }
+
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ECC SUCCESS\n");
+ }
+ return ret;
+}
+
+/* Cache slot replacement: a second MakeCacheKey on the same keyId must
+ * return the new key on subsequent export, not the original. */
+static int _whTest_CryptoEccCacheDuplicate(whClientContext* ctx)
+{
+ int ret = WH_ERROR_OK;
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t key1[ECC_BUFSIZE];
+ uint8_t key2[ECC_BUFSIZE];
+ uint16_t key1Len = sizeof(key1);
+ uint16_t key2Len = sizeof(key2);
+
+ WH_TEST_PRINT(" Testing ECC cache duplicate returns latest key...\n");
+
+ ret = wh_Client_EccMakeCacheKey(ctx, 32, ECC_SECP256R1, &keyId,
+ WH_NVM_FLAGS_NONE, 0, NULL);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Client_KeyExport(ctx, keyId, NULL, 0, key1, &key1Len);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Client_EccMakeCacheKey(ctx, 32, ECC_SECP256R1, &keyId,
+ WH_NVM_FLAGS_NONE, 0, NULL);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ key2Len = sizeof(key2);
+ ret = wh_Client_KeyExport(ctx, keyId, NULL, 0, key2, &key2Len);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ if ((key1Len == key2Len) && (memcmp(key1, key2, key1Len) == 0)) {
+ WH_ERROR_PRINT(" FAIL: Export returned original ECC key after "
+ "duplicate insert\n");
+ ret = WH_ERROR_ABORTED;
+ }
+ else {
+ WH_TEST_PRINT(
+ " PASS: Export returned most recent cached ECC key\n");
+ }
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ return ret;
+}
+
+/* Cache a NONEXPORTABLE ECC P-256 keypair on the server, sign a hash there,
+ * then export only the public half via wh_Client_EccExportPublicKey and
+ * verify the signature client-side. */
+static int _whTest_CryptoEccExportPublicKey(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ ecc_key hsmKey[1];
+ ecc_key pubKey[1];
+ whKeyId keyId = WH_KEYID_ERASED;
+ /* Non-zero hash: wolfCrypt rejects all-zero hashes for ECDSA sign/verify
+ * (returns ECC_BAD_ARG_E) unless WC_ALLOW_ECC_ZERO_HASH is defined. */
+ uint8_t hash[TEST_ECC_KEYSIZE] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+ uint8_t sig[ECC_MAX_SIG_SIZE];
+ word32 sigLen = sizeof(sig);
+ int verify = 0;
+ uint8_t denyBuf[256];
+ uint16_t denyLen = sizeof(denyBuf);
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wh_Client_EccMakeCacheKey(
+ ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, &keyId,
+ WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY |
+ WH_NVM_FLAGS_NONEXPORTABLE,
+ 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached ECC key %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ /* Full export must be denied by the NONEXPORTABLE policy. */
+ {
+ int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf,
+ &denyLen);
+ if (denyRet != WH_ERROR_ACCESS) {
+ WH_ERROR_PRINT(
+ "NONEXPORTABLE ECC full export was not denied: %d\n", denyRet);
+ ret = -1;
+ }
+ }
+
+ /* Sign on the server using the cached private key. */
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(hsmKey, NULL, devId);
+ if (ret == 0) {
+ ret = wh_Client_EccSetKeyId(hsmKey, keyId);
+ }
+ if (ret == 0) {
+ ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng,
+ hsmKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("HSM ECC sign failed %d\n", ret);
+ }
+ }
+ wc_ecc_free(hsmKey);
+ }
+
+ /* Public-only export must succeed and verify the signature client-side. */
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wh_Client_EccExportPublicKey(ctx, keyId, pubKey, 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("wh_Client_EccExportPublicKey failed %d\n", ret);
+ }
+ else if (pubKey->type != ECC_PUBLICKEY) {
+ WH_ERROR_PRINT(
+ "Exported ECC key is not public-only (type=%d)\n",
+ pubKey->type);
+ ret = -1;
+ }
+ else {
+ ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash),
+ &verify, pubKey);
+ if (ret != 0 || verify != 1) {
+ WH_ERROR_PRINT(
+ "Client-side ECC verify failed ret=%d verify=%d\n",
+ ret, verify);
+ if (ret == 0) {
+ ret = -1;
+ }
+ }
+ }
+ wc_ecc_free(pubKey);
+ }
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ECC EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+#if !defined(WOLF_CRYPTO_CB_ONLY_ECC)
+
+/* Curve sizes used by the cross-verify and async test families. */
+#define WH_TEST_ECC_P256_KEY_SIZE 32
+#define WH_TEST_ECC_P384_KEY_SIZE 48
+#define WH_TEST_ECC_P521_KEY_SIZE 66
+/* Use maximum digest size for all curves to test hash truncation edge cases.
+ * ECDSA implementations must properly truncate hashes larger than the curve
+ * order. */
+#define WH_TEST_ECC_HASH_SIZE WC_MAX_DIGEST_SIZE
+
+static int whTest_CryptoEccCrossVerify_OneCurve(whClientContext* ctx,
+ WC_RNG* rng, int keySize,
+ int curveId, const char* name)
+{
+ ecc_key hsmKey[1] = {0};
+ ecc_key swKey[1] = {0};
+ uint8_t hash[WH_TEST_ECC_HASH_SIZE] = {0};
+ uint8_t sig[ECC_MAX_SIG_SIZE] = {0};
+ uint8_t pubX[ECC_MAXSIZE] = {0};
+ uint8_t pubY[ECC_MAXSIZE] = {0};
+ word32 pubXLen = 0;
+ word32 pubYLen = 0;
+ word32 sigLen = 0;
+ int res = 0;
+ whKeyId keyId = WH_KEYID_ERASED;
+ int hsmKeyInit = 0;
+ int swKeyInit = 0;
+ int ret = WH_ERROR_OK;
+ int i;
+
+ /* Use non-repeating pattern to detect hash truncation bugs */
+ for (i = 0; i < WH_TEST_ECC_HASH_SIZE; i++) {
+ hash[i] = (uint8_t)i;
+ }
+
+ WH_TEST_PRINT(" Testing %s curve...\n", name);
+
+ pubXLen = keySize;
+ pubYLen = keySize;
+
+ /* Test 1: HSM sign + Software verify */
+ ret = wc_ecc_init_ex(hsmKey, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to init HSM key: %d\n", name, ret);
+ }
+ else {
+ hsmKeyInit = 1;
+ }
+ if (ret == 0) {
+ ret = wc_ecc_make_key(rng, keySize, hsmKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to generate HSM key: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_export_public_raw(hsmKey, pubX, &pubXLen, pubY, &pubYLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to export HSM public key: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ sigLen = sizeof(sig);
+ ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng, hsmKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: HSM sign failed: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(swKey, NULL, INVALID_DEVID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to init SW key: %d\n", name, ret);
+ }
+ else {
+ swKeyInit = 1;
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_import_unsigned(swKey, pubX, pubY, NULL, curveId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to import public to SW: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ res = 0;
+ ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &res, swKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: SW verify failed: %d\n", name, ret);
+ }
+ else if (res != 1) {
+ WH_ERROR_PRINT("%s: HSM sign + SW verify: signature invalid\n",
+ name);
+ ret = -1;
+ }
+ else {
+ WH_TEST_PRINT(" HSM sign + SW verify: PASS\n");
+ }
+ }
+ if (swKeyInit) {
+ wc_ecc_free(swKey);
+ swKeyInit = 0;
+ }
+ if (hsmKeyInit) {
+ if (wh_Client_EccGetKeyId(hsmKey, &keyId) == 0 &&
+ !WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ wc_ecc_free(hsmKey);
+ hsmKeyInit = 0;
+ }
+
+ /* Test 2: Software sign + HSM verify */
+ if (ret == 0) {
+ memset(sig, 0, sizeof(sig));
+ memset(pubX, 0, sizeof(pubX));
+ memset(pubY, 0, sizeof(pubY));
+ pubXLen = keySize;
+ pubYLen = keySize;
+ keyId = WH_KEYID_ERASED;
+
+ ret = wc_ecc_init_ex(swKey, NULL, INVALID_DEVID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to init SW key: %d\n", name, ret);
+ }
+ else {
+ swKeyInit = 1;
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_make_key(rng, keySize, swKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to generate SW key: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_export_public_raw(swKey, pubX, &pubXLen, pubY, &pubYLen);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to export SW public key: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ sigLen = sizeof(sig);
+ ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng, swKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: SW sign failed: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(hsmKey, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to init HSM key: %d\n", name, ret);
+ }
+ else {
+ hsmKeyInit = 1;
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_import_unsigned(hsmKey, pubX, pubY, NULL, curveId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: Failed to import public to HSM: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ res = 0;
+ ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &res, hsmKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: HSM verify failed: %d\n", name, ret);
+ }
+ else if (res != 1) {
+ WH_ERROR_PRINT("%s: SW sign + HSM verify: signature invalid\n",
+ name);
+ ret = -1;
+ }
+ else {
+ WH_TEST_PRINT(" SW sign + HSM verify: PASS\n");
+ }
+ }
+ if (hsmKeyInit) {
+ if (wh_Client_EccGetKeyId(hsmKey, &keyId) == 0 &&
+ !WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ wc_ecc_free(hsmKey);
+ }
+ if (swKeyInit) {
+ wc_ecc_free(swKey);
+ }
+ return ret;
+}
+
+static int _whTest_CryptoEccCrossVerify(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ WH_TEST_PRINT("Testing ECDSA cross-verification (HSM<->SW)...\n");
+
+#if !defined(NO_ECC256)
+ if (ret == 0) {
+ ret = whTest_CryptoEccCrossVerify_OneCurve(
+ ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256");
+ }
+#endif
+
+#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384
+ if (ret == 0) {
+ ret = whTest_CryptoEccCrossVerify_OneCurve(
+ ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384");
+ }
+#endif
+
+#if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521
+ if (ret == 0) {
+ ret = whTest_CryptoEccCrossVerify_OneCurve(
+ ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521");
+ }
+#endif
+
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ECDSA cross-verification SUCCESS\n");
+ }
+ return ret;
+}
+
+/* Async sign/verify per curve. Generates a server-cached signing key, signs
+ * via wh_Client_EccSign{Request,Response}, software-verifies, then imports
+ * the public key into a separate cache slot and async-verifies. Also covers
+ * BADARGS and stuck-ctx invariants. */
+static int whTest_CryptoEccSignVerifyAsync_OneCurve(whClientContext* ctx,
+ WC_RNG* rng, int keySize,
+ int curveId,
+ const char* name)
+{
+ ecc_key hsmKey[1] = {0};
+ ecc_key swKey[1] = {0};
+ uint8_t hash[WH_TEST_ECC_HASH_SIZE] = {0};
+ uint8_t sig[ECC_MAX_SIG_SIZE] = {0};
+ uint8_t pubX[ECC_MAXSIZE] = {0};
+ uint8_t pubY[ECC_MAXSIZE] = {0};
+ word32 pubXLen = 0;
+ word32 pubYLen = 0;
+ uint16_t sigLen = 0;
+ int res = 0;
+ whKeyId signKeyId = WH_KEYID_ERASED;
+ whKeyId verifyKeyId = WH_KEYID_ERASED;
+ int hsmKeyInit = 0;
+ int swKeyInit = 0;
+ int ret = WH_ERROR_OK;
+ int i;
+
+ for (i = 0; i < WH_TEST_ECC_HASH_SIZE; i++) {
+ hash[i] = (uint8_t)i;
+ }
+
+ WH_TEST_PRINT(" Testing async Sign/Verify %s curve...\n", name);
+
+ pubXLen = keySize;
+ pubYLen = keySize;
+
+ ret = wc_ecc_init_ex(hsmKey, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ hsmKeyInit = 1;
+ ret = wc_ecc_make_key(rng, keySize, hsmKey);
+ }
+ if (ret == 0) {
+ uint8_t signLabel[] = "TestEccAsyncSign";
+ signKeyId = WH_KEYID_ERASED;
+ ret = wh_Client_EccImportKey(
+ ctx, hsmKey, &signKeyId, WH_NVM_FLAGS_USAGE_SIGN,
+ sizeof(signLabel), signLabel);
+ }
+
+ if (ret == 0) {
+ sigLen = sizeof(sig);
+ ret = wh_Client_EccSignRequest(ctx, signKeyId, hash, sizeof(hash));
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccSignRequest failed: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_EccSignResponse(ctx, sig, &sigLen);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccSignResponse failed: %d\n", name, ret);
+ }
+ }
+
+ if (ret == 0) {
+ int badret =
+ wh_Client_EccSignRequest(ctx, WH_KEYID_ERASED, hash, sizeof(hash));
+ if (badret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("%s: EccSignRequest with erased keyId returned %d "
+ "(want BADARGS)\n",
+ name, badret);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_ecc_export_public_raw(hsmKey, pubX, &pubXLen, pubY, &pubYLen);
+ }
+
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(swKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ swKeyInit = 1;
+ ret = wc_ecc_import_unsigned(swKey, pubX, pubY, NULL, curveId);
+ }
+ }
+ if (ret == 0) {
+ res = 0;
+ ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &res, swKey);
+ if (ret == 0 && res != 1) {
+ WH_ERROR_PRINT("%s: async sign produced invalid signature\n", name);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ ecc_key pubOnly[1] = {0};
+ uint8_t label[] = "TestEccAsyncVerify";
+
+ ret = wc_ecc_init_ex(pubOnly, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_ecc_import_unsigned(pubOnly, pubX, pubY, NULL, curveId);
+ }
+ if (ret == 0) {
+ verifyKeyId = WH_KEYID_ERASED;
+ ret = wh_Client_EccImportKey(
+ ctx, pubOnly, &verifyKeyId, WH_NVM_FLAGS_USAGE_VERIFY,
+ sizeof(label), label);
+ }
+ wc_ecc_free(pubOnly);
+ }
+ if (ret == 0) {
+ ret = wh_Client_EccVerifyRequest(ctx, verifyKeyId, sig, sigLen, hash,
+ sizeof(hash));
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccVerifyRequest failed: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ res = 0;
+ do {
+ ret = wh_Client_EccVerifyResponse(ctx, NULL, &res);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccVerifyResponse failed: %d\n", name, ret);
+ }
+ else if (res != 1) {
+ WH_ERROR_PRINT("%s: async verify returned res=%d (want 1)\n", name,
+ res);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ int badret = wh_Client_EccVerifyRequest(ctx, WH_KEYID_ERASED, sig,
+ sigLen, hash, sizeof(hash));
+ if (badret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: EccVerifyRequest with erased keyId returned %d "
+ "(want BADARGS)\n",
+ name, badret);
+ ret = -1;
+ }
+ }
+
+ /* NULL ctx must be rejected by every async half. */
+ if (ret == 0) {
+ int rc1 = wh_Client_EccSignRequest(NULL, signKeyId, hash, sizeof(hash));
+ int rc2 = wh_Client_EccSignResponse(NULL, sig, &sigLen);
+ int rc3 = wh_Client_EccVerifyRequest(NULL, verifyKeyId, sig, sigLen,
+ hash, sizeof(hash));
+ int rc4 = wh_Client_EccVerifyResponse(NULL, NULL, &res);
+ if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS ||
+ rc3 != WH_ERROR_BADARGS || rc4 != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("%s: NULL ctx async API rc=(%d,%d,%d,%d) want all "
+ "BADARGS\n",
+ name, rc1, rc2, rc3, rc4);
+ ret = -1;
+ }
+ }
+
+ /* Mismatched output-arg shape on Response must BADARGS pre-Recv. */
+ if (ret == 0) {
+ int rc1 = wh_Client_EccSignResponse(ctx, sig, NULL);
+ int rc2 = wh_Client_EccVerifyResponse(ctx, NULL, NULL);
+ if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: bad-arg Response rc=(%d,%d) want both BADARGS\n", name,
+ rc1, rc2);
+ ret = -1;
+ }
+ }
+
+ /* Wrapper-level: response-side bad args must be caught before SendRequest
+ * so the caller's ctx is not left stuck-pending. */
+ if (ret == 0) {
+ int badret = wh_Client_EccVerify(ctx, hsmKey, sig, sigLen, hash,
+ sizeof(hash), NULL);
+ if (badret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("%s: EccVerify with NULL out_res returned %d "
+ "(want BADARGS)\n",
+ name, badret);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int rc;
+ sigLen = sizeof(sig);
+ rc = wh_Client_EccSignRequest(ctx, signKeyId, hash, sizeof(hash));
+ if (rc == WH_ERROR_OK) {
+ do {
+ rc = wh_Client_EccSignResponse(ctx, sig, &sigLen);
+ } while (rc == WH_ERROR_NOTREADY);
+ }
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: ctx stuck after wrapper BADARGS (rc=%d)\n",
+ name, rc);
+ ret = -1;
+ }
+ }
+
+ /* Too-small sig buffer: must return BUFFER_SIZE with required size in
+ * *inout_sig_len, and must not leak partial signature bytes. */
+ if (ret == 0) {
+ uint8_t small_buf[1] = {0xAA};
+ uint16_t small_len = sizeof(small_buf);
+ int rc;
+ rc = wh_Client_EccSignRequest(ctx, signKeyId, hash, sizeof(hash));
+ if (rc == WH_ERROR_OK) {
+ do {
+ rc = wh_Client_EccSignResponse(ctx, small_buf, &small_len);
+ } while (rc == WH_ERROR_NOTREADY);
+ }
+ if (rc != WH_ERROR_BUFFER_SIZE) {
+ WH_ERROR_PRINT(
+ "%s: too-small buffer Sign Response rc=%d (want BUFFER_SIZE)\n",
+ name, rc);
+ ret = -1;
+ }
+ else if (small_len <= 1 || small_len > ECC_MAX_SIG_SIZE) {
+ WH_ERROR_PRINT("%s: too-small buffer Sign required size=%u "
+ "(want > 1 and <= ECC_MAX_SIG_SIZE)\n",
+ name, (unsigned)small_len);
+ ret = -1;
+ }
+ else if (small_buf[0] != 0xAA) {
+ WH_ERROR_PRINT(
+ "%s: partial signature leaked into too-small buffer\n", name);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT(" async Sign/Verify %s: PASS\n", name);
+ }
+
+ if (!WH_KEYID_ISERASED(verifyKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, verifyKeyId);
+ }
+ if (!WH_KEYID_ISERASED(signKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, signKeyId);
+ }
+ if (swKeyInit) {
+ wc_ecc_free(swKey);
+ }
+ if (hsmKeyInit) {
+ wc_ecc_free(hsmKey);
+ }
+ return ret;
+}
+
+#ifdef HAVE_ECC_DHE
+/* Async ECDH per curve: generate two cached private keys, import each side's
+ * public into a separate cache slot, run async SharedSecret both directions,
+ * compare. Plus BUFFER_SIZE and BADARGS contracts on both wrapper and async
+ * halves. */
+static int whTest_CryptoEccSharedSecretAsync_OneCurve(whClientContext* ctx,
+ WC_RNG* rng, int keySize,
+ int curveId,
+ const char* name)
+{
+ ecc_key keyA[1] = {0};
+ ecc_key keyB[1] = {0};
+ ecc_key pubA[1] = {0};
+ ecc_key pubB[1] = {0};
+ uint8_t pubAx[ECC_MAXSIZE] = {0};
+ uint8_t pubAy[ECC_MAXSIZE] = {0};
+ uint8_t pubBx[ECC_MAXSIZE] = {0};
+ uint8_t pubBy[ECC_MAXSIZE] = {0};
+ word32 pubAxLen = 0;
+ word32 pubAyLen = 0;
+ word32 pubBxLen = 0;
+ word32 pubByLen = 0;
+ uint8_t secret_AB[ECC_MAXSIZE] = {0};
+ uint8_t secret_BA[ECC_MAXSIZE] = {0};
+ uint16_t secret_AB_len = sizeof(secret_AB);
+ uint16_t secret_BA_len = sizeof(secret_BA);
+ whKeyId privAId = WH_KEYID_ERASED;
+ whKeyId privBId = WH_KEYID_ERASED;
+ whKeyId pubAId = WH_KEYID_ERASED;
+ whKeyId pubBId = WH_KEYID_ERASED;
+ int keyAInit = 0;
+ int keyBInit = 0;
+ int pubAInit = 0;
+ int pubBInit = 0;
+ uint8_t labelA[] = "TestEccDhAsyncA";
+ uint8_t labelB[] = "TestEccDhAsyncB";
+ int ret = WH_ERROR_OK;
+
+ WH_TEST_PRINT(" Testing async ECDH %s curve...\n", name);
+
+ pubAxLen = pubAyLen = pubBxLen = pubByLen = keySize;
+
+ ret = wc_ecc_init_ex(keyA, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ keyAInit = 1;
+ ret = wc_ecc_make_key(rng, keySize, keyA);
+ }
+ if (ret == 0) {
+ uint8_t privLabelA[] = "TestEccDhAsyncPrivA";
+ privAId = WH_KEYID_ERASED;
+ ret = wh_Client_EccImportKey(
+ ctx, keyA, &privAId, WH_NVM_FLAGS_USAGE_DERIVE,
+ sizeof(privLabelA), privLabelA);
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(keyB, NULL, WH_DEV_ID);
+ }
+ if (ret == 0) {
+ keyBInit = 1;
+ ret = wc_ecc_make_key(rng, keySize, keyB);
+ }
+ if (ret == 0) {
+ uint8_t privLabelB[] = "TestEccDhAsyncPrivB";
+ privBId = WH_KEYID_ERASED;
+ ret = wh_Client_EccImportKey(
+ ctx, keyB, &privBId, WH_NVM_FLAGS_USAGE_DERIVE,
+ sizeof(privLabelB), privLabelB);
+ }
+
+ if (ret == 0) {
+ ret =
+ wc_ecc_export_public_raw(keyA, pubAx, &pubAxLen, pubAy, &pubAyLen);
+ }
+ if (ret == 0) {
+ ret =
+ wc_ecc_export_public_raw(keyB, pubBx, &pubBxLen, pubBy, &pubByLen);
+ }
+
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(pubA, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ pubAInit = 1;
+ ret = wc_ecc_import_unsigned(pubA, pubAx, pubAy, NULL, curveId);
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_EccImportKey(ctx, pubA, &pubAId,
+ WH_NVM_FLAGS_USAGE_DERIVE, sizeof(labelA),
+ labelA);
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(pubB, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ pubBInit = 1;
+ ret = wc_ecc_import_unsigned(pubB, pubBx, pubBy, NULL, curveId);
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_EccImportKey(ctx, pubB, &pubBId,
+ WH_NVM_FLAGS_USAGE_DERIVE, sizeof(labelB),
+ labelB);
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_EccSharedSecretResponse(ctx, secret_AB,
+ &secret_AB_len);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_EccSharedSecretRequest(ctx, privBId, pubAId);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_EccSharedSecretResponse(ctx, secret_BA,
+ &secret_BA_len);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+
+ if (ret == 0) {
+ if (secret_AB_len != secret_BA_len ||
+ memcmp(secret_AB, secret_BA, secret_AB_len) != 0) {
+ WH_ERROR_PRINT("%s: async ECDH secrets differ across sides\n",
+ name);
+ ret = -1;
+ }
+ }
+
+ /* Too-small output buffer: BUFFER_SIZE + required size, no leak. */
+ if (ret == 0) {
+ uint8_t small_buf[1] = {0xAA};
+ uint16_t small_len = sizeof(small_buf);
+ int rc;
+ rc = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId);
+ if (rc == WH_ERROR_OK) {
+ do {
+ rc = wh_Client_EccSharedSecretResponse(ctx, small_buf,
+ &small_len);
+ } while (rc == WH_ERROR_NOTREADY);
+ }
+ if (rc != WH_ERROR_BUFFER_SIZE) {
+ WH_ERROR_PRINT(
+ "%s: too-small buffer ECDH Response rc=%d (want BUFFER_SIZE)\n",
+ name, rc);
+ ret = -1;
+ }
+ else if (small_len != secret_AB_len) {
+ WH_ERROR_PRINT("%s: too-small buffer required size=%u (want %u)\n",
+ name, (unsigned)small_len,
+ (unsigned)secret_AB_len);
+ ret = -1;
+ }
+ else if (small_buf[0] != 0xAA) {
+ WH_ERROR_PRINT("%s: partial secret leaked into too-small buffer\n",
+ name);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ uint8_t small_buf[1] = {0xAA};
+ uint16_t small_len = sizeof(small_buf);
+ int rc =
+ wh_Client_EccSharedSecret(ctx, keyA, pubB, small_buf, &small_len);
+ if (rc != WH_ERROR_BUFFER_SIZE) {
+ WH_ERROR_PRINT(
+ "%s: too-small buffer ECDH wrapper rc=%d (want BUFFER_SIZE)\n",
+ name, rc);
+ ret = -1;
+ }
+ else if (small_len != secret_AB_len) {
+ WH_ERROR_PRINT(
+ "%s: wrapper too-small required size=%u (want %u)\n", name,
+ (unsigned)small_len, (unsigned)secret_AB_len);
+ ret = -1;
+ }
+ else if (small_buf[0] != 0xAA) {
+ WH_ERROR_PRINT(
+ "%s: wrapper leaked partial secret into too-small buffer\n",
+ name);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int badret =
+ wh_Client_EccSharedSecretRequest(ctx, WH_KEYID_ERASED, pubBId);
+ if (badret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: ECDH Request with erased priv keyId returned %d\n", name,
+ badret);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ int badret =
+ wh_Client_EccSharedSecretRequest(ctx, privAId, WH_KEYID_ERASED);
+ if (badret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: ECDH Request with erased pub keyId returned %d\n", name,
+ badret);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int rc1 = wh_Client_EccSharedSecretRequest(NULL, privAId, pubBId);
+ int rc2 = wh_Client_EccSharedSecretResponse(NULL, secret_AB,
+ &secret_AB_len);
+ if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: NULL ctx async ECDH rc=(%d,%d) want BADARGS\n", name, rc1,
+ rc2);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int rc = wh_Client_EccSharedSecretResponse(ctx, secret_AB, NULL);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("%s: SharedSecretResponse(out, NULL) returned %d "
+ "(want BADARGS)\n",
+ name, rc);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int badret =
+ wh_Client_EccSharedSecret(ctx, keyA, pubB, secret_AB, NULL);
+ if (badret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: EccSharedSecret with NULL inout_size returned %d "
+ "(want BADARGS)\n",
+ name, badret);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int rc;
+ secret_AB_len = sizeof(secret_AB);
+ rc = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId);
+ if (rc == WH_ERROR_OK) {
+ do {
+ rc = wh_Client_EccSharedSecretResponse(ctx, secret_AB,
+ &secret_AB_len);
+ } while (rc == WH_ERROR_NOTREADY);
+ }
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT(
+ "%s: ctx stuck after ECDH wrapper BADARGS (rc=%d)\n", name, rc);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT(" async ECDH %s: PASS\n", name);
+ }
+
+ if (!WH_KEYID_ISERASED(pubBId)) {
+ (void)wh_Client_KeyEvict(ctx, pubBId);
+ }
+ if (!WH_KEYID_ISERASED(pubAId)) {
+ (void)wh_Client_KeyEvict(ctx, pubAId);
+ }
+ if (!WH_KEYID_ISERASED(privBId)) {
+ (void)wh_Client_KeyEvict(ctx, privBId);
+ }
+ if (!WH_KEYID_ISERASED(privAId)) {
+ (void)wh_Client_KeyEvict(ctx, privAId);
+ }
+ if (pubBInit) {
+ wc_ecc_free(pubB);
+ }
+ if (pubAInit) {
+ wc_ecc_free(pubA);
+ }
+ if (keyBInit) {
+ wc_ecc_free(keyB);
+ }
+ if (keyAInit) {
+ wc_ecc_free(keyA);
+ }
+ return ret;
+}
+#endif /* HAVE_ECC_DHE */
+
+/* Async server-side keygen per curve: MakeCacheKey async (then sign/verify
+ * proves the cached key is usable), MakeExportKey async (then local
+ * sign/verify proves the exported struct is well-formed), plus arg-shape
+ * contracts on every async half. */
+static int whTest_CryptoEccMakeKeyAsync_OneCurve(whClientContext* ctx,
+ WC_RNG* rng, int keySize,
+ int curveId, const char* name)
+{
+ ecc_key exportKey[1] = {0};
+ ecc_key swKey[1] = {0};
+ uint8_t hash[WH_TEST_ECC_HASH_SIZE] = {0};
+ uint8_t sig[ECC_MAX_SIG_SIZE] = {0};
+ uint8_t pubX[ECC_MAXSIZE] = {0};
+ uint8_t pubY[ECC_MAXSIZE] = {0};
+ word32 pubXLen = 0;
+ word32 pubYLen = 0;
+ uint16_t sigLen = 0;
+ int res = 0;
+ whKeyId cacheKeyId = WH_KEYID_ERASED;
+ int exportKeyInit = 0;
+ int swKeyInit = 0;
+ uint8_t cacheLabel[] = "TestEccAsyncCacheGen";
+ int ret = WH_ERROR_OK;
+ int i;
+
+ for (i = 0; i < WH_TEST_ECC_HASH_SIZE; i++) {
+ hash[i] = (uint8_t)i;
+ }
+
+ pubXLen = keySize;
+ pubYLen = keySize;
+
+ WH_TEST_PRINT(" Testing async MakeKey %s curve...\n", name);
+
+ if (ret == 0) {
+ ret = wh_Client_EccMakeCacheKeyRequest(
+ ctx, keySize, curveId, WH_KEYID_ERASED, WH_NVM_FLAGS_USAGE_SIGN,
+ sizeof(cacheLabel), cacheLabel);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccMakeCacheKeyRequest failed: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_EccMakeCacheKeyResponse(ctx, &cacheKeyId);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccMakeCacheKeyResponse failed: %d\n", name,
+ ret);
+ }
+ else if (WH_KEYID_ISERASED(cacheKeyId)) {
+ WH_ERROR_PRINT("%s: server returned erased keyId\n", name);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ sigLen = sizeof(sig);
+ ret = wh_Client_EccSignRequest(ctx, cacheKeyId, hash, sizeof(hash));
+ if (ret == WH_ERROR_OK) {
+ do {
+ ret = wh_Client_EccSignResponse(ctx, sig, &sigLen);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: sign with cache-generated keyId failed: %d\n",
+ name, ret);
+ }
+ }
+ if (ret == 0) {
+ ecc_key pubOnly[1] = {0};
+ uint8_t labelBuf[WH_NVM_LABEL_LEN];
+ ret = wc_ecc_init_ex(pubOnly, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wh_Client_EccExportKey(ctx, cacheKeyId, pubOnly,
+ sizeof(labelBuf), labelBuf);
+ if (ret == 0) {
+ ret = wc_ecc_export_public_raw(pubOnly, pubX, &pubXLen, pubY,
+ &pubYLen);
+ }
+ wc_ecc_free(pubOnly);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s: export of cached pub failed: %d\n", name, ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(swKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ swKeyInit = 1;
+ ret = wc_ecc_import_unsigned(swKey, pubX, pubY, NULL, curveId);
+ }
+ if (ret == 0) {
+ res = 0;
+ ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &res,
+ swKey);
+ if (ret == 0 && res != 1) {
+ WH_ERROR_PRINT(
+ "%s: software verify of cache-generated key failed\n",
+ name);
+ ret = -1;
+ }
+ }
+ }
+ if (swKeyInit) {
+ wc_ecc_free(swKey);
+ }
+
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(exportKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ exportKeyInit = 1;
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_EccMakeExportKeyRequest(ctx, keySize, curveId);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccMakeExportKeyRequest failed: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_EccMakeExportKeyResponse(ctx, exportKey);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: EccMakeExportKeyResponse failed: %d\n", name,
+ ret);
+ }
+ }
+ if (ret == 0) {
+ word32 swSigLen = sizeof(sig);
+ memset(sig, 0, sizeof(sig));
+ ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &swSigLen, rng,
+ exportKey);
+ if (ret == 0) {
+ res = 0;
+ ret = wc_ecc_verify_hash(sig, swSigLen, hash, sizeof(hash), &res,
+ exportKey);
+ if (ret == 0 && res != 1) {
+ WH_ERROR_PRINT(
+ "%s: local verify of exported keygen key failed\n", name);
+ ret = -1;
+ }
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "%s: local sign/verify of exported keygen key failed: %d\n",
+ name, ret);
+ }
+ }
+
+ if (ret == 0) {
+ int rc1 = wh_Client_EccMakeCacheKeyRequest(NULL, keySize, curveId,
+ WH_KEYID_ERASED,
+ WH_NVM_FLAGS_NONE, 0, NULL);
+ int rc2 = wh_Client_EccMakeCacheKeyResponse(NULL, &cacheKeyId);
+ int rc3 = wh_Client_EccMakeExportKeyRequest(NULL, keySize, curveId);
+ int rc4 = wh_Client_EccMakeExportKeyResponse(NULL, exportKey);
+ if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS ||
+ rc3 != WH_ERROR_BADARGS || rc4 != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: NULL ctx async MakeKey rc=(%d,%d,%d,%d) want all "
+ "BADARGS\n",
+ name, rc1, rc2, rc3, rc4);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ int rc1 = wh_Client_EccMakeCacheKeyResponse(ctx, NULL);
+ int rc2 = wh_Client_EccMakeExportKeyResponse(ctx, NULL);
+ if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "%s: NULL out arg Response rc=(%d,%d) want both BADARGS\n",
+ name, rc1, rc2);
+ ret = -1;
+ }
+ }
+
+ /* EPHEMERAL flag must be rejected by the cache Request so the export pair
+ * unambiguously owns ephemeral keygen. */
+ if (ret == 0) {
+ int rc = wh_Client_EccMakeCacheKeyRequest(
+ ctx, keySize, curveId, WH_KEYID_ERASED, WH_NVM_FLAGS_EPHEMERAL, 0,
+ NULL);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("%s: cache Request with EPHEMERAL flag returned %d "
+ "(want BADARGS)\n",
+ name, rc);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ whKeyId tmpId = WH_KEYID_ERASED;
+ int rc = wh_Client_EccMakeCacheKeyRequest(
+ ctx, keySize, curveId, WH_KEYID_ERASED, WH_NVM_FLAGS_USAGE_SIGN,
+ sizeof(cacheLabel), cacheLabel);
+ if (rc == WH_ERROR_OK) {
+ do {
+ rc = wh_Client_EccMakeCacheKeyResponse(ctx, &tmpId);
+ } while (rc == WH_ERROR_NOTREADY);
+ }
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s: ctx stuck after MakeKey BADARGS (rc=%d)\n",
+ name, rc);
+ ret = -1;
+ }
+ if (!WH_KEYID_ISERASED(tmpId)) {
+ (void)wh_Client_KeyEvict(ctx, tmpId);
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT(" async MakeKey %s: PASS\n", name);
+ }
+
+ if (!WH_KEYID_ISERASED(cacheKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, cacheKeyId);
+ }
+ if (exportKeyInit) {
+ wc_ecc_free(exportKey);
+ }
+ return ret;
+}
+
+static int _whTest_CryptoEccAsync(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ WH_TEST_PRINT("Testing ECC async API...\n");
+
+#if !defined(NO_ECC256)
+ if (ret == 0) {
+ ret = whTest_CryptoEccSignVerifyAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256");
+ }
+#ifdef HAVE_ECC_DHE
+ if (ret == 0) {
+ ret = whTest_CryptoEccSharedSecretAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256");
+ }
+#endif
+ if (ret == 0) {
+ ret = whTest_CryptoEccMakeKeyAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256");
+ }
+#endif
+
+#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384
+ if (ret == 0) {
+ ret = whTest_CryptoEccSignVerifyAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384");
+ }
+#ifdef HAVE_ECC_DHE
+ if (ret == 0) {
+ ret = whTest_CryptoEccSharedSecretAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384");
+ }
+#endif
+ if (ret == 0) {
+ ret = whTest_CryptoEccMakeKeyAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384");
+ }
+#endif
+
+#if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521
+ if (ret == 0) {
+ ret = whTest_CryptoEccSignVerifyAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521");
+ }
+#ifdef HAVE_ECC_DHE
+ if (ret == 0) {
+ ret = whTest_CryptoEccSharedSecretAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521");
+ }
+#endif
+ if (ret == 0) {
+ ret = whTest_CryptoEccMakeKeyAsync_OneCurve(
+ ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521");
+ }
+#endif
+
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ECC async API SUCCESS\n");
+ }
+ return ret;
+}
+
+#endif /* !WOLF_CRYPTO_CB_ONLY_ECC */
+
+#ifdef WOLFHSM_CFG_DMA
+/*
+ * ECC public-key export over the generic DMA transport
+ * (wh_Client_KeyExportPublicDma). ECC has no DMA cryptocb path, so this
+ * exercises the explicit DMA key-export API directly and runs once rather than
+ * looping over devIds. Mirrors _whTest_CryptoEccExportPublicKey but pulls the
+ * public half out via DMA, then verifies a server-made signature client-side.
+ */
+static int _whTest_CryptoEccExportPublicKeyDma(whClientContext* ctx)
+{
+ int ret = 0;
+ WC_RNG rng[1];
+ whKeyId keyId = WH_KEYID_ERASED;
+ ecc_key pubKey[1];
+ /* Non-zero hash: wolfCrypt rejects all-zero hashes for ECDSA. */
+ uint8_t hash[TEST_ECC_KEYSIZE] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+ uint8_t sig[ECC_MAX_SIG_SIZE];
+ word32 sigLen = sizeof(sig);
+ int verify = 0;
+ uint8_t derBuf[256];
+ uint16_t derSz = sizeof(derBuf);
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wh_Client_EccMakeCacheKey(
+ ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, &keyId,
+ WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY |
+ WH_NVM_FLAGS_NONEXPORTABLE,
+ 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached ECC key (DMA) %d\n",
+ ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ /* Sign on the server with the cached private key. */
+ if (ret == 0) {
+ ecc_key hsmKey[1];
+ ret = wc_ecc_init_ex(hsmKey, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ ret = wh_Client_EccSetKeyId(hsmKey, keyId);
+ }
+ if (ret == 0) {
+ ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng,
+ hsmKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("HSM ECC sign failed (DMA) %d\n", ret);
+ }
+ }
+ wc_ecc_free(hsmKey);
+ }
+
+ /* Pull the public half out of the HSM via the generic DMA transport. */
+ if (ret == 0) {
+ ret = wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_ECC, derBuf,
+ derSz, NULL, 0, &derSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("wh_Client_KeyExportPublicDma(ECC) failed %d\n", ret);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wh_Crypto_EccDeserializeKeyDer(derBuf, derSz, pubKey);
+ }
+ if (ret == 0 && pubKey->type != ECC_PUBLICKEY) {
+ WH_ERROR_PRINT(
+ "Exported ECC key (DMA) is not public-only (type=%d)\n",
+ pubKey->type);
+ ret = -1;
+ }
+ if (ret == 0) {
+ ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &verify,
+ pubKey);
+ if (ret != 0 || verify != 1) {
+ WH_ERROR_PRINT(
+ "Client-side ECC verify (DMA) failed ret=%d verify=%d\n",
+ ret, verify);
+ if (ret == 0) {
+ ret = -1;
+ }
+ }
+ }
+ wc_ecc_free(pubKey);
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ECC EXPORT-PUBLIC DMA SUCCESS\n");
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+int whTest_Crypto_Ecc(whClientContext* ctx)
+{
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEcc(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCacheDuplicate(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccExportPublicKey(ctx));
+#if !defined(WOLF_CRYPTO_CB_ONLY_ECC)
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCrossVerify(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccAsync(ctx));
+#endif
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccExportPublicKeyDma(ctx));
+#endif
+ return 0;
+}
+
+#endif /* HAVE_ECC && HAVE_ECC_SIGN && HAVE_ECC_VERIFY */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
+
diff --git a/test-refactor/client-server/wh_test_crypto_ed25519.c b/test-refactor/client-server/wh_test_crypto_ed25519.c
new file mode 100644
index 000000000..1491f846b
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_ed25519.c
@@ -0,0 +1,651 @@
+/*
+ * 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_ed25519.c
+ *
+ * Ed25519 sign/verify routed through the server via WH_DEV_ID:
+ * _whTest_CryptoEd25519Inline - pure wolfCrypt (sign+verify locally,
+ * plus negative case for tampered sig)
+ * _whTest_CryptoEd25519ServerKey - server-cached sign and verify keyIds
+ * _whTest_CryptoEd25519Dma - same, via the DMA messaging path
+ */
+
+#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/ed25519.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"
+
+#ifdef HAVE_ED25519
+
+/* Imports the supplied private key into the server cache as a sign-only
+ * keyId, and the matching public key as a verify-only keyId. Wipes the
+ * client-side key material afterward and rebinds each ed25519_key struct
+ * to its server-side keyId so the caller can use them as opaque handles.
+ *
+ * Output keyIds are written as soon as each import succeeds, even if a
+ * later step fails. Callers must evict any non-erased keyId on the error
+ * path. */
+static int whTest_Ed25519ImportToServer(whClientContext* ctx, int devId,
+ ed25519_key* key, ed25519_key* pubKey,
+ uint8_t* label, uint16_t labelLen,
+ whKeyId* outSignKeyId,
+ whKeyId* outVerifyKeyId)
+{
+ int ret = 0;
+ byte pubKeyRaw[ED25519_PUB_KEY_SIZE];
+ word32 pubKeySize = sizeof(pubKeyRaw);
+ whKeyId signKeyId = WH_KEYID_ERASED;
+ whKeyId verifyKeyId = WH_KEYID_ERASED;
+
+ ret = wc_ed25519_export_public(key, pubKeyRaw, &pubKeySize);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to export Ed25519 public key: %d\n", ret);
+ }
+ else {
+ ret = wc_ed25519_import_public(pubKeyRaw, pubKeySize, pubKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to import Ed25519 public key: %d\n", ret);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_Ed25519ImportKey(
+ ctx, key, &signKeyId, WH_NVM_FLAGS_USAGE_SIGN, labelLen, label);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to import Ed25519 key to server: %d\n",
+ ret);
+ }
+ else {
+ if (outSignKeyId != NULL) {
+ *outSignKeyId = signKeyId;
+ }
+ wc_ed25519_free(key);
+ ret = wc_ed25519_init_ex(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to re-initialize Ed25519 key: %d\n",
+ ret);
+ }
+ else {
+ wh_Client_Ed25519SetKeyId(key, signKeyId);
+ }
+ }
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_Ed25519ImportKey(ctx, pubKey, &verifyKeyId,
+ WH_NVM_FLAGS_USAGE_VERIFY, labelLen,
+ label);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to import Ed25519 public key to server: %d\n", ret);
+ }
+ else {
+ if (outVerifyKeyId != NULL) {
+ *outVerifyKeyId = verifyKeyId;
+ }
+ wc_ed25519_free(pubKey);
+ ret = wc_ed25519_init_ex(pubKey, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to re-initialize Ed25519 public key: %d\n", ret);
+ }
+ else {
+ wh_Client_Ed25519SetKeyId(pubKey, verifyKeyId);
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int _whTest_CryptoEd25519Inline(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ ed25519_key key[1] = {0};
+ ed25519_key pubKey[1] = {0};
+ byte msg[] = "Test message for Ed25519 signing";
+ byte sig[ED25519_SIG_SIZE];
+ word32 sigSz = sizeof(sig);
+ int verified = 0;
+ const word32 msgSz = (word32)sizeof(msg);
+ byte pubKeyRaw[ED25519_PUB_KEY_SIZE];
+ word32 pubKeySize = sizeof(pubKeyRaw);
+
+ (void)ctx;
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize Ed25519 key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(pubKey, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize Ed25519 public key: %d\n", ret);
+ wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_make_key(rng, ED25519_KEY_SIZE, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate Ed25519 key: %d\n", ret);
+ }
+ else {
+ ret = wc_ed25519_export_public(key, pubKeyRaw, &pubKeySize);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to export Ed25519 public key: %d\n", ret);
+ }
+ else {
+ ret = wc_ed25519_import_public(pubKeyRaw, pubKeySize, pubKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to import Ed25519 public key: %d\n",
+ ret);
+ }
+ }
+ }
+
+ if (ret == 0) {
+ sigSz = sizeof(sig);
+ ret = wc_ed25519_sign_msg(msg, msgSz, sig, &sigSz, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign message with Ed25519: %d\n", ret);
+ }
+ else {
+ ret = wc_ed25519_verify_msg(sig, sigSz, msg, msgSz, &verified,
+ pubKey);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to verify Ed25519 signature: %d\n",
+ ret);
+ }
+ else if (verified != 1) {
+ WH_ERROR_PRINT("Ed25519 signature verification failed\n");
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ /* Tampered signature must fail verification. wolfCrypt may signal
+ * rejection either as ret==0 with verified==0, or as ret==SIG_VERIFY_E
+ * (path-dependent inside wolfCrypt). Anything else is a real error. */
+ sig[0] ^= 0xFF;
+ verified = 0;
+ ret = wc_ed25519_verify_msg(sig, sigSz, msg, msgSz, &verified, pubKey);
+ if (verified != 0) {
+ WH_ERROR_PRINT(
+ "Modified Ed25519 signature unexpectedly verified\n");
+ ret = -1;
+ }
+ else if (ret == 0 || ret == SIG_VERIFY_E) {
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ "wc_ed25519_verify_msg of tampered sig errored: %d\n", ret);
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("Ed25519 INLINE DEVID=0x%X SUCCESS\n", devId);
+ }
+
+ wc_ed25519_free(pubKey);
+ wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoEd25519ServerKey(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ ed25519_key key[1] = {0};
+ ed25519_key pubKey[1] = {0};
+ whKeyId signKeyId = WH_KEYID_ERASED;
+ whKeyId verifyKeyId = WH_KEYID_ERASED;
+ byte msg[] = "Ed25519 server key message";
+ byte sig[ED25519_SIG_SIZE];
+ uint32_t sigSz = sizeof(sig);
+ int verified = 0;
+ uint8_t label[] = "Ed25519 Server Key";
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize Ed25519 key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(pubKey, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize Ed25519 public key: %d\n", ret);
+ wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_make_key(rng, ED25519_KEY_SIZE, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate Ed25519 key: %d\n", ret);
+ }
+ else {
+ ret = whTest_Ed25519ImportToServer(ctx, devId, key, pubKey, label,
+ sizeof(label), &signKeyId,
+ &verifyKeyId);
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_Ed25519Sign(ctx, key, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, sig, &sigSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign with server Ed25519 key: %d\n",
+ ret);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_Ed25519Verify(ctx, pubKey, sig, sigSz, msg,
+ (uint32_t)sizeof(msg), (uint8_t)Ed25519,
+ NULL, 0, &verified);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to verify server Ed25519 signature: %d\n", ret);
+ }
+ else if (verified != 1) {
+ WH_ERROR_PRINT(
+ "Server Ed25519 signature verification failed\n");
+ ret = -1;
+ }
+ }
+
+ /* Sign-only keyId must be rejected by Verify with WH_ERROR_USAGE. */
+ if (ret == 0) {
+ int negVerified = 0;
+ int negRet = wh_Client_Ed25519Verify(
+ ctx, key, sig, sigSz, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, &negVerified);
+ if (negRet != WH_ERROR_USAGE) {
+ WH_ERROR_PRINT(
+ "Sign-only Ed25519 key Verify expected WH_ERROR_USAGE (%d), "
+ "got %d\n",
+ WH_ERROR_USAGE, negRet);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ /* Same shape as the inline tampered-sig case above. */
+ sig[0] ^= 0xAA;
+ verified = 0;
+ ret = wh_Client_Ed25519Verify(
+ ctx, pubKey, sig, sigSz, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, &verified);
+ if (verified != 0) {
+ WH_ERROR_PRINT("Modified server Ed25519 signature unexpectedly "
+ "verified\n");
+ ret = -1;
+ }
+ else if (ret == 0 || ret == SIG_VERIFY_E) {
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ "Server Ed25519 verify of tampered sig errored: %d\n", ret);
+ }
+ }
+
+ if (!WH_KEYID_ISERASED(signKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, signKeyId);
+ }
+ if (!WH_KEYID_ISERASED(verifyKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, verifyKeyId);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("Ed25519 SERVER KEY DEVID=0x%X SUCCESS\n", devId);
+ }
+
+ wc_ed25519_free(pubKey);
+ wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+static int _whTest_CryptoEd25519Dma(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ ed25519_key key[1] = {0};
+ ed25519_key pubKey[1] = {0};
+ whKeyId signKeyId = WH_KEYID_ERASED;
+ whKeyId verifyKeyId = WH_KEYID_ERASED;
+ byte msg[] = "Ed25519 DMA message";
+ byte sig[ED25519_SIG_SIZE];
+ uint32_t sigSz = sizeof(sig);
+ int verified = 0;
+ uint8_t label[] = "Ed25519 DMA Key";
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize Ed25519 key (DMA): %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(pubKey, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize Ed25519 public key (DMA): %d\n",
+ ret);
+ wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_make_key(rng, ED25519_KEY_SIZE, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate Ed25519 key (DMA): %d\n", ret);
+ }
+ else {
+ ret = whTest_Ed25519ImportToServer(ctx, devId, key, pubKey, label,
+ sizeof(label), &signKeyId,
+ &verifyKeyId);
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_Ed25519SignDma(ctx, key, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, sig, &sigSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign via DMA Ed25519 key: %d\n", ret);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_Ed25519VerifyDma(ctx, pubKey, sig, sigSz, msg,
+ (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, &verified);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to verify DMA Ed25519 signature: %d\n",
+ ret);
+ }
+ else if (verified != 1) {
+ WH_ERROR_PRINT("DMA Ed25519 signature verification failed\n");
+ ret = -1;
+ }
+ }
+
+ /* Sign-only keyId must be rejected by VerifyDma with WH_ERROR_USAGE. */
+ if (ret == 0) {
+ int negVerified = 0;
+ int negRet = wh_Client_Ed25519VerifyDma(
+ ctx, key, sig, sigSz, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, &negVerified);
+ if (negRet != WH_ERROR_USAGE) {
+ WH_ERROR_PRINT(
+ "Sign-only Ed25519 key VerifyDma expected WH_ERROR_USAGE (%d), "
+ "got %d\n",
+ WH_ERROR_USAGE, negRet);
+ ret = -1;
+ }
+ }
+
+ if (!WH_KEYID_ISERASED(signKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, signKeyId);
+ }
+ if (!WH_KEYID_ISERASED(verifyKeyId)) {
+ (void)wh_Client_KeyEvict(ctx, verifyKeyId);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("Ed25519 DMA DEVID=0x%X SUCCESS\n", devId);
+ }
+
+ wc_ed25519_free(pubKey);
+ wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+/* Cache a NONEXPORTABLE Ed25519 keypair on the server, sign a message there,
+ * then export only the public half via wh_Client_Ed25519ExportPublicKey and
+ * verify the signature client-side. */
+static int _whTest_CryptoEd25519ExportPublicKey(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ ed25519_key hsmKey[1] = {0};
+ ed25519_key pubKey[1] = {0};
+ whKeyId keyId = WH_KEYID_ERASED;
+ byte msg[] = "Ed25519 export-public message";
+ byte sig[ED25519_SIG_SIZE];
+ uint32_t sigSz = sizeof(sig);
+ int verified = 0;
+ uint8_t denyBuf[256];
+ uint16_t denyLen = sizeof(denyBuf);
+
+ ret = wh_Client_Ed25519MakeCacheKey(ctx, &keyId,
+ WH_NVM_FLAGS_USAGE_SIGN |
+ WH_NVM_FLAGS_USAGE_VERIFY |
+ WH_NVM_FLAGS_NONEXPORTABLE,
+ 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached Ed25519 key %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Full export must be denied by the NONEXPORTABLE policy. */
+ {
+ int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf,
+ &denyLen);
+ if (denyRet != WH_ERROR_ACCESS) {
+ WH_ERROR_PRINT(
+ "NONEXPORTABLE Ed25519 full export was not denied: %d\n",
+ denyRet);
+ ret = -1;
+ }
+ }
+
+ /* Sign on the server using the cached private key. */
+ if (ret == 0) {
+ ret = wc_ed25519_init_ex(hsmKey, NULL, devId);
+ if (ret == 0) {
+ ret = wh_Client_Ed25519SetKeyId(hsmKey, keyId);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Ed25519Sign(ctx, hsmKey, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, sig, &sigSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("HSM Ed25519 sign failed %d\n", ret);
+ }
+ }
+ wc_ed25519_free(hsmKey);
+ }
+
+ /* Public-only export must succeed and verify the signature client-side. */
+ if (ret == 0) {
+ ret = wc_ed25519_init_ex(pubKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wh_Client_Ed25519ExportPublicKey(ctx, keyId, pubKey, 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("wh_Client_Ed25519ExportPublicKey failed %d\n",
+ ret);
+ }
+ else if (pubKey->pubKeySet != 1 || pubKey->privKeySet != 0) {
+ WH_ERROR_PRINT(
+ "Exported Ed25519 key flags wrong: pub=%d priv=%d\n",
+ (int)pubKey->pubKeySet, (int)pubKey->privKeySet);
+ ret = -1;
+ }
+ else {
+ ret = wc_ed25519_verify_msg(sig, sigSz, msg,
+ (word32)sizeof(msg), &verified,
+ pubKey);
+ if (ret != 0 || verified != 1) {
+ WH_ERROR_PRINT(
+ "Client-side Ed25519 verify failed ret=%d verify=%d\n",
+ ret, verified);
+ if (ret == 0) {
+ ret = -1;
+ }
+ }
+ }
+ wc_ed25519_free(pubKey);
+ }
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("Ed25519 EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+
+/* Exercises wh_Client_Ed25519Sign with an undersized output buffer: must
+ * return WH_ERROR_BUFFER_SIZE and report the required signature length. */
+static int _whTest_CryptoEd25519BufferTooSmall(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret;
+ WC_RNG rng[1];
+ ed25519_key key[1];
+ const byte msg[] = "ed25519 buf size test";
+ uint8_t small_sig[ED25519_SIG_SIZE - 1] = {0};
+ uint8_t full_sig[ED25519_SIG_SIZE] = {0};
+ uint32_t sig_len;
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_ed25519_init_ex(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ed25519_init_ex %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_ed25519_make_key(rng, ED25519_KEY_SIZE, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_ed25519_make_key %d\n", ret);
+ goto done;
+ }
+
+ sig_len = (uint32_t)sizeof(small_sig);
+ ret = wh_Client_Ed25519Sign(ctx, key, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, small_sig,
+ &sig_len);
+ if (ret != WH_ERROR_BUFFER_SIZE) {
+ WH_ERROR_PRINT(
+ "Ed25519Sign small buf expected WH_ERROR_BUFFER_SIZE, got %d\n",
+ ret);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+ if (sig_len != ED25519_SIG_SIZE) {
+ WH_ERROR_PRINT("Ed25519Sign small buf reported size %u, expected %u\n",
+ (unsigned)sig_len, (unsigned)ED25519_SIG_SIZE);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+
+ sig_len = (uint32_t)sizeof(full_sig);
+ ret = wh_Client_Ed25519Sign(ctx, key, msg, (uint32_t)sizeof(msg),
+ (uint8_t)Ed25519, NULL, 0, full_sig,
+ &sig_len);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Ed25519Sign full buf failed: %d\n", ret);
+ goto done;
+ }
+ if (sig_len != ED25519_SIG_SIZE) {
+ WH_ERROR_PRINT("Ed25519Sign full buf size %u, expected %u\n",
+ (unsigned)sig_len, (unsigned)ED25519_SIG_SIZE);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+
+ WH_TEST_PRINT("Ed25519 buffer-size DEVID=0x%X SUCCESS\n", devId);
+ ret = 0;
+
+done:
+ (void)wc_ed25519_free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+int whTest_Crypto_Ed25519(whClientContext* ctx)
+{
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519Inline(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519ServerKey(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519Dma(ctx));
+#endif
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519ExportPublicKey(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519BufferTooSmall(ctx));
+ return 0;
+}
+#endif /* HAVE_ED25519 */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_kdf.c b/test-refactor/client-server/wh_test_crypto_kdf.c
new file mode 100644
index 000000000..0fbe5db9e
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_kdf.c
@@ -0,0 +1,394 @@
+/*
+ * 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_kdf.c
+ *
+ * HKDF (RFC 5869) and CMAC-KDF (NIST SP 800-108 / SP 800-56C two-step) routed
+ * through the server. Each test exercises three paths: the wolfCrypt API
+ * dispatched via the cryptocb, wh_Client_*MakeExportKey returning the derived
+ * key to the client, and wh_Client_*MakeCacheKey caching the derived key on
+ * the server (verified by exporting and comparing). Each KDF also has a
+ * cached-input variant where the raw key material is first cached and then
+ * referenced by key id.
+ */
+
+#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/kdf.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"
+
+#ifdef HAVE_HKDF
+#define WH_TEST_HKDF_IKM_SIZE 22
+#define WH_TEST_HKDF_SALT_SIZE 13
+#define WH_TEST_HKDF_INFO_SIZE 10
+#define WH_TEST_HKDF_OKM_SIZE 42
+
+static int _whTest_CryptoHkdf(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ whKeyId keyId = WH_KEYID_ERASED;
+
+ /* RFC 5869 Test Case 1 */
+ const uint8_t ikm[WH_TEST_HKDF_IKM_SIZE] = {
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b};
+ const uint8_t salt[WH_TEST_HKDF_SALT_SIZE] = {0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0a, 0x0b, 0x0c};
+ const uint8_t info[WH_TEST_HKDF_INFO_SIZE] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4,
+ 0xf5, 0xf6, 0xf7, 0xf8, 0xf9};
+ const uint8_t expected[WH_TEST_HKDF_OKM_SIZE] = {
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f,
+ 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a,
+ 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34,
+ 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65};
+
+ uint8_t okm[WH_TEST_HKDF_OKM_SIZE];
+ uint8_t okm2[WH_TEST_HKDF_OKM_SIZE];
+ uint8_t label[] = "HKDF Test Label";
+
+ /* 1. wc_HKDF dispatched through the cryptocb */
+ memset(okm, 0, sizeof(okm));
+ ret = wc_HKDF_ex(WC_SHA256, ikm, WH_TEST_HKDF_IKM_SIZE, salt,
+ WH_TEST_HKDF_SALT_SIZE, info, WH_TEST_HKDF_INFO_SIZE, okm,
+ WH_TEST_HKDF_OKM_SIZE, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_HKDF_ex: %d\n", ret);
+ return ret;
+ }
+ if (memcmp(okm, expected, WH_TEST_HKDF_OKM_SIZE) != 0) {
+ WH_ERROR_PRINT("HKDF output mismatch (wc_HKDF_ex)\n");
+ return -1;
+ }
+
+ /* 2. wc_HKDF without salt -- no expected vector, just no error */
+ memset(okm, 0, sizeof(okm));
+ ret = wc_HKDF_ex(WC_SHA256, ikm, WH_TEST_HKDF_IKM_SIZE, NULL, 0, info,
+ WH_TEST_HKDF_INFO_SIZE, okm, WH_TEST_HKDF_OKM_SIZE, NULL,
+ devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_HKDF_ex (no salt): %d\n", ret);
+ return ret;
+ }
+
+ /* 3. wc_HKDF without info */
+ memset(okm, 0, sizeof(okm));
+ ret = wc_HKDF_ex(WC_SHA256, ikm, WH_TEST_HKDF_IKM_SIZE, salt,
+ WH_TEST_HKDF_SALT_SIZE, NULL, 0, okm, WH_TEST_HKDF_OKM_SIZE,
+ NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_HKDF_ex (no info): %d\n", ret);
+ return ret;
+ }
+
+ /* 4. wh_Client_HkdfMakeExportKey */
+ memset(okm, 0, sizeof(okm));
+ ret = wh_Client_HkdfMakeExportKey(
+ ctx, WC_SHA256, WH_KEYID_ERASED, ikm, WH_TEST_HKDF_IKM_SIZE, salt,
+ WH_TEST_HKDF_SALT_SIZE, info, WH_TEST_HKDF_INFO_SIZE, okm,
+ WH_TEST_HKDF_OKM_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wh_Client_HkdfMakeExportKey: %d\n", ret);
+ return ret;
+ }
+ if (memcmp(okm, expected, WH_TEST_HKDF_OKM_SIZE) != 0) {
+ WH_ERROR_PRINT("HKDF output mismatch (MakeExportKey)\n");
+ return -1;
+ }
+
+ /* 5. wh_Client_HkdfMakeCacheKey -- derive into a cached key, then export
+ * it back to verify the server-side derivation. */
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_HkdfMakeCacheKey(
+ ctx, WC_SHA256, WH_KEYID_ERASED, ikm, WH_TEST_HKDF_IKM_SIZE, salt,
+ WH_TEST_HKDF_SALT_SIZE, info, WH_TEST_HKDF_INFO_SIZE, &keyId,
+ WH_NVM_FLAGS_NONE, label, sizeof(label), WH_TEST_HKDF_OKM_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wh_Client_HkdfMakeCacheKey: %d\n", ret);
+ return ret;
+ }
+ if (keyId == WH_KEYID_ERASED) {
+ WH_ERROR_PRINT("HKDF cache did not return a key id\n");
+ return -1;
+ }
+ {
+ uint8_t exportLabel[sizeof(label)] = {0};
+ uint16_t exportLen = WH_TEST_HKDF_OKM_SIZE;
+ memset(okm2, 0, sizeof(okm2));
+ ret = wh_Client_KeyExport(ctx, keyId, exportLabel, sizeof(exportLabel),
+ okm2, &exportLen);
+ if (ret == 0 && exportLen != WH_TEST_HKDF_OKM_SIZE) {
+ WH_ERROR_PRINT("HKDF exported length mismatch: %u != %u\n",
+ exportLen, WH_TEST_HKDF_OKM_SIZE);
+ ret = -1;
+ }
+ if (ret == 0 && memcmp(okm2, expected, WH_TEST_HKDF_OKM_SIZE) != 0) {
+ WH_ERROR_PRINT("HKDF output mismatch (MakeCacheKey)\n");
+ ret = -1;
+ }
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* 6. HKDF with a cached input key id -- derives the same OKM as direct
+ * buffers when the IKM lives in the keystore. */
+ {
+ whKeyId keyIdIn = WH_KEYID_ERASED;
+ uint8_t label_in[] = "input-key";
+ const uint8_t ikm2[] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D,
+ 0x3E, 0x3F};
+ const uint8_t salt2[] = {0xB0, 0xB1, 0xB2, 0xB3};
+ const uint8_t info2[] = {0xC0, 0xC1, 0xC2};
+ uint8_t okmCached[WH_TEST_HKDF_OKM_SIZE];
+ uint8_t okmDirect[WH_TEST_HKDF_OKM_SIZE];
+
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_DERIVE, label_in,
+ sizeof(label_in), (uint8_t*)ikm2, sizeof(ikm2),
+ &keyIdIn);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache HKDF input key: %d\n", ret);
+ return ret;
+ }
+
+ memset(okmCached, 0, sizeof(okmCached));
+ ret = wh_Client_HkdfMakeExportKey(
+ ctx, WC_SHA256, keyIdIn, NULL, 0, salt2, sizeof(salt2), info2,
+ sizeof(info2), okmCached, sizeof(okmCached));
+ if (ret == 0) {
+ memset(okmDirect, 0, sizeof(okmDirect));
+ ret = wh_Client_HkdfMakeExportKey(
+ ctx, WC_SHA256, WH_KEYID_ERASED, ikm2, sizeof(ikm2), salt2,
+ sizeof(salt2), info2, sizeof(info2), okmDirect,
+ sizeof(okmDirect));
+ }
+ if (ret == 0 &&
+ memcmp(okmCached, okmDirect, sizeof(okmCached)) != 0) {
+ WH_ERROR_PRINT("HKDF mismatch (cached vs direct input key)\n");
+ ret = -1;
+ }
+ (void)wh_Client_KeyEvict(ctx, keyIdIn);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ WH_TEST_PRINT("HKDF DEVID=0x%X SUCCESS\n", devId);
+ return 0;
+}
+#endif /* HAVE_HKDF */
+
+#ifdef HAVE_CMAC_KDF
+#define WH_TEST_CMAC_KDF_SALT_SIZE 24
+#define WH_TEST_CMAC_KDF_Z_SIZE 32
+#define WH_TEST_CMAC_KDF_FIXED_INFO_SIZE 60
+#define WH_TEST_CMAC_KDF_OUT_SIZE 40
+
+static int _whTest_CryptoCmacKdf(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ whKeyId keyId = WH_KEYID_ERASED;
+
+ /* NIST SP 800-108 KDF in Counter Mode using CMAC -- vectors from the
+ * wolfSSL CMAC KDF implementation tests. */
+ static const uint8_t salt[WH_TEST_CMAC_KDF_SALT_SIZE] = {
+ 0x20, 0x51, 0xaf, 0x34, 0x76, 0x2e, 0xbe, 0x55, 0x6f, 0x72, 0xa5, 0xc6,
+ 0xed, 0xc7, 0x77, 0x1e, 0xb9, 0x24, 0x5f, 0xad, 0x76, 0xf0, 0x34, 0xbe};
+ static const uint8_t z[WH_TEST_CMAC_KDF_Z_SIZE] = {
+ 0xae, 0x8e, 0x93, 0xc9, 0xc9, 0x91, 0xcf, 0x89, 0x6a, 0x49, 0x1a,
+ 0x89, 0x07, 0xdf, 0x4e, 0x4b, 0xe5, 0x18, 0x6a, 0xe4, 0x96, 0xcd,
+ 0x34, 0x0d, 0xc1, 0x9b, 0x23, 0x78, 0x21, 0xdb, 0x7b, 0x60};
+ static const uint8_t fixedInfo[WH_TEST_CMAC_KDF_FIXED_INFO_SIZE] = {
+ 0xa2, 0x59, 0xca, 0xe2, 0xc4, 0xa3, 0x6b, 0x89, 0x56, 0x3c, 0xb1, 0x48,
+ 0xc7, 0x82, 0x51, 0x34, 0x3b, 0xbf, 0xab, 0xdc, 0x13, 0xca, 0x7a, 0xc2,
+ 0x17, 0x1c, 0x2e, 0xb6, 0x02, 0x1f, 0x44, 0x77, 0xfe, 0xa3, 0x3b, 0x28,
+ 0x72, 0x4d, 0xa7, 0x21, 0xee, 0x08, 0x7b, 0xff, 0xd7, 0x94, 0xa1, 0x56,
+ 0x37, 0x54, 0xb4, 0x25, 0xa8, 0xd0, 0x9b, 0x3e, 0x0d, 0xa5, 0xff, 0xed};
+ static const uint8_t expected[WH_TEST_CMAC_KDF_OUT_SIZE] = {
+ 0xb4, 0x0c, 0x32, 0xbe, 0x01, 0x27, 0x93, 0xba, 0xfd, 0xf7,
+ 0x78, 0xc5, 0xf4, 0x54, 0x43, 0xf4, 0xc9, 0x71, 0x23, 0x93,
+ 0x17, 0x63, 0xd8, 0x3a, 0x59, 0x27, 0x07, 0xbf, 0xf2, 0xd3,
+ 0x60, 0x59, 0x50, 0x27, 0x29, 0xca, 0xb8, 0x8b, 0x29, 0x38};
+
+ uint8_t out[WH_TEST_CMAC_KDF_OUT_SIZE];
+ uint8_t exported[WH_TEST_CMAC_KDF_OUT_SIZE];
+ uint8_t exportLabel[12] = {0};
+ uint16_t exportLen;
+ uint8_t keyLabel[] = "CMAC KDF Key";
+
+ /* 1. Direct wolfCrypt API dispatched via the cryptocb */
+ memset(out, 0, sizeof(out));
+ ret = wc_KDA_KDF_twostep_cmac(salt, WH_TEST_CMAC_KDF_SALT_SIZE, z,
+ WH_TEST_CMAC_KDF_Z_SIZE, fixedInfo,
+ WH_TEST_CMAC_KDF_FIXED_INFO_SIZE, out,
+ WH_TEST_CMAC_KDF_OUT_SIZE, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_KDA_KDF_twostep_cmac: %d\n", ret);
+ return ret;
+ }
+ if (memcmp(out, expected, sizeof(out)) != 0) {
+ WH_ERROR_PRINT("CMAC KDF mismatch (direct wolfCrypt)\n");
+ return -1;
+ }
+
+ /* 2. Client export with direct salt and Z */
+ memset(out, 0, sizeof(out));
+ ret = wh_Client_CmacKdfMakeExportKey(
+ ctx, WH_KEYID_ERASED, salt, WH_TEST_CMAC_KDF_SALT_SIZE, WH_KEYID_ERASED,
+ z, WH_TEST_CMAC_KDF_Z_SIZE, fixedInfo, WH_TEST_CMAC_KDF_FIXED_INFO_SIZE,
+ out, sizeof(out));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wh_Client_CmacKdfMakeExportKey: %d\n", ret);
+ return ret;
+ }
+ if (memcmp(out, expected, sizeof(out)) != 0) {
+ WH_ERROR_PRINT("CMAC KDF mismatch (export key)\n");
+ return -1;
+ }
+
+ /* 3. Client cache with direct salt and Z, then export to compare */
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ ctx, WH_KEYID_ERASED, salt, WH_TEST_CMAC_KDF_SALT_SIZE, WH_KEYID_ERASED,
+ z, WH_TEST_CMAC_KDF_Z_SIZE, fixedInfo, WH_TEST_CMAC_KDF_FIXED_INFO_SIZE,
+ &keyId, WH_NVM_FLAGS_NONE, keyLabel, sizeof(keyLabel),
+ WH_TEST_CMAC_KDF_OUT_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wh_Client_CmacKdfMakeCacheKey: %d\n", ret);
+ return ret;
+ }
+ if (keyId == WH_KEYID_ERASED) {
+ WH_ERROR_PRINT("CMAC KDF cache did not return a key id\n");
+ return -1;
+ }
+ memset(exported, 0, sizeof(exported));
+ exportLen = (uint16_t)sizeof(exported);
+ ret = wh_Client_KeyExport(ctx, keyId, exportLabel, sizeof(exportLabel),
+ exported, &exportLen);
+ if (ret == 0 &&
+ (exportLen != WH_TEST_CMAC_KDF_OUT_SIZE ||
+ memcmp(exported, expected, sizeof(exported)) != 0)) {
+ WH_ERROR_PRINT("Exported CMAC KDF key mismatch\n");
+ ret = -1;
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* 4. Cached salt and cached Z. Caches both inputs first, then derives by
+ * key id with NULL/0 raw buffers. Derives via both export and cache. */
+ {
+ whKeyId saltKeyId = WH_KEYID_ERASED;
+ whKeyId zKeyId = WH_KEYID_ERASED;
+
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0,
+ (uint8_t*)salt, WH_TEST_CMAC_KDF_SALT_SIZE,
+ &saltKeyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache CMAC KDF salt: %d\n", ret);
+ return ret;
+ }
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0,
+ (uint8_t*)z, WH_TEST_CMAC_KDF_Z_SIZE, &zKeyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache CMAC KDF Z: %d\n", ret);
+ (void)wh_Client_KeyEvict(ctx, saltKeyId);
+ return ret;
+ }
+
+ memset(out, 0, sizeof(out));
+ ret = wh_Client_CmacKdfMakeExportKey(
+ ctx, saltKeyId, NULL, 0, zKeyId, NULL, 0, fixedInfo,
+ WH_TEST_CMAC_KDF_FIXED_INFO_SIZE, out, sizeof(out));
+ if (ret == 0 && memcmp(out, expected, sizeof(out)) != 0) {
+ WH_ERROR_PRINT("CMAC KDF mismatch (cached inputs export)\n");
+ ret = -1;
+ }
+
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ ctx, saltKeyId, NULL, 0, zKeyId, NULL, 0, fixedInfo,
+ WH_TEST_CMAC_KDF_FIXED_INFO_SIZE, &keyId, WH_NVM_FLAGS_NONE,
+ keyLabel, sizeof(keyLabel), WH_TEST_CMAC_KDF_OUT_SIZE);
+ if (ret == 0 && keyId == WH_KEYID_ERASED) {
+ WH_ERROR_PRINT("CMAC KDF cache (cached inputs) returned no "
+ "key id\n");
+ ret = -1;
+ }
+ if (ret == 0) {
+ memset(exported, 0, sizeof(exported));
+ exportLen = (uint16_t)sizeof(exported);
+ ret = wh_Client_KeyExport(ctx, keyId, exportLabel,
+ sizeof(exportLabel), exported,
+ &exportLen);
+ if (ret == 0 &&
+ (exportLen != WH_TEST_CMAC_KDF_OUT_SIZE ||
+ memcmp(exported, expected, sizeof(exported)) != 0)) {
+ WH_ERROR_PRINT("CMAC KDF mismatch (cached inputs cache)\n");
+ ret = -1;
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ }
+
+ (void)wh_Client_KeyEvict(ctx, saltKeyId);
+ (void)wh_Client_KeyEvict(ctx, zKeyId);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ WH_TEST_PRINT("CMAC KDF DEVID=0x%X SUCCESS\n", devId);
+ return 0;
+}
+#endif /* HAVE_CMAC_KDF */
+
+int whTest_Crypto_Kdf(whClientContext* ctx)
+{
+#ifdef HAVE_HKDF
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoHkdf(ctx));
+#endif
+#ifdef HAVE_CMAC_KDF
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCmacKdf(ctx));
+#endif
+ (void)ctx;
+ return 0;
+}
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_keypolicy.c b/test-refactor/client-server/wh_test_crypto_keypolicy.c
new file mode 100644
index 000000000..9602cd41d
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_keypolicy.c
@@ -0,0 +1,703 @@
+/*
+ * 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_keypolicy.c
+ *
+ * Per-cached-key usage policy enforcement and revocation lifecycle:
+ * _whTest_CryptoKeyUsagePolicies - cache keys with restricted
+ * WH_NVM_FLAGS_USAGE_* bits, confirm
+ * operations outside that policy fail
+ * with WH_ERROR_USAGE
+ * _whTest_CryptoKeyRevocationAesCbc - revoke a cached AES key, confirm
+ * it cannot be used or erased; commit
+ * + revoke an NVM-backed key with the
+ * same expectation
+ */
+
+#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/aes.h"
+#include "wolfssl/wolfcrypt/cmac.h"
+#include "wolfssl/wolfcrypt/ecc.h"
+#include "wolfssl/wolfcrypt/random.h"
+#include "wolfssl/wolfcrypt/sha256.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"
+
+static int _whTest_CryptoKeyUsagePolicies(whClientContext* client)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ uint8_t plaintext[16] = {0};
+ uint8_t ciphertext[16] = {0};
+ uint8_t key[32] = {0};
+ uint32_t keyLen = sizeof(key);
+ whKeyId keyId = WH_KEYID_ERASED;
+
+ (void)ciphertext;
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ WH_TEST_PRINT("Testing Key Usage Policies...\n");
+
+ ret = wc_RNG_GenerateBlock(rng, plaintext, sizeof(plaintext));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate random data: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wc_RNG_GenerateBlock(rng, key, sizeof(key));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate random key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+#ifndef NO_AES
+#ifdef HAVE_AES_CBC
+ WH_TEST_PRINT(" Testing AES CBC encrypt without ENCRYPT flag...\n");
+ {
+ Aes aes[1];
+ uint8_t iv[AES_BLOCK_SIZE] = {0};
+
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE,
+ (uint8_t*)"aes-no-enc", strlen("aes-no-enc"),
+ key, keyLen, &keyId);
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ if (ret == 0) {
+ ret = wc_AesCbcEncrypt(aes, ciphertext, plaintext,
+ sizeof(plaintext));
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(
+ " PASS: Correctly denied encryption\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ " FAIL: Expected WH_ERROR_USAGE, got %d\n",
+ ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ }
+ wc_AesFree(aes);
+ }
+ wh_Client_KeyEvict(client, keyId);
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ WH_TEST_PRINT(" Testing AES CBC decrypt without DECRYPT flag...\n");
+ {
+ Aes aes[1];
+ uint8_t iv[AES_BLOCK_SIZE] = {0};
+ uint8_t decrypted[16] = {0};
+ uint8_t tempCipher[16] = {0};
+
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ENCRYPT,
+ (uint8_t*)"aes-enc-only",
+ strlen("aes-enc-only"), key, keyLen, &keyId);
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ if (ret == 0) {
+ ret = wc_AesCbcEncrypt(aes, tempCipher, plaintext,
+ sizeof(plaintext));
+ }
+ }
+ wc_AesFree(aes);
+ }
+ wh_Client_KeyEvict(client, keyId);
+ }
+
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(
+ client, WH_NVM_FLAGS_USAGE_ENCRYPT, (uint8_t*)"aes-no-dec",
+ strlen("aes-no-dec"), key, keyLen, &keyId);
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ if (ret == 0) {
+ ret = wc_AesCbcDecrypt(aes, decrypted, tempCipher,
+ sizeof(tempCipher));
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(
+ " PASS: Correctly denied decryption\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(" FAIL: Expected "
+ "WH_ERROR_USAGE, got %d\n",
+ ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ }
+ wc_AesFree(aes);
+ }
+ wh_Client_KeyEvict(client, keyId);
+ }
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+#endif /* HAVE_AES_CBC */
+#endif /* !NO_AES */
+
+#ifdef HAVE_ECC
+#ifdef HAVE_ECC_SIGN
+ WH_TEST_PRINT(" Testing ECDSA sign without SIGN flag...\n");
+ {
+ ecc_key eccKey[1];
+ uint8_t sig[ECC_MAX_SIG_SIZE] = {0};
+ word32 sigLen = sizeof(sig);
+ uint8_t hash[WC_SHA256_DIGEST_SIZE] = {0};
+
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_EccMakeCacheKey(
+ client, 32, ECC_SECP256R1, &keyId, WH_NVM_FLAGS_NONE,
+ strlen("ecc-no-sign"), (uint8_t*)"ecc-no-sign");
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(eccKey, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ ret = wc_ecc_set_curve(eccKey, 32, ECC_SECP256R1);
+ if (ret == 0) {
+ ret = wh_Client_EccSetKeyId(eccKey, keyId);
+ if (ret == 0) {
+ ret = wc_RNG_GenerateBlock(rng, hash, sizeof(hash));
+ if (ret == 0) {
+ ret = wc_ecc_sign_hash(hash, sizeof(hash), sig,
+ &sigLen, rng, eccKey);
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(
+ " PASS: Correctly denied signing\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(" FAIL: Expected "
+ "WH_ERROR_USAGE, got %d\n",
+ ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ }
+ }
+ wc_ecc_free(eccKey);
+ }
+ wh_Client_KeyEvict(client, keyId);
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+#endif /* HAVE_ECC_SIGN */
+
+#ifdef HAVE_ECC_DHE
+ WH_TEST_PRINT(" Testing ECDH without DERIVE flag...\n");
+ {
+ ecc_key privKey[1];
+ ecc_key pubKey[1];
+ uint8_t sharedSecret[ECC_MAXSIZE] = {0};
+ word32 secretLen = sizeof(sharedSecret);
+
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_EccMakeCacheKey(
+ client, 32, ECC_SECP256R1, &keyId, WH_NVM_FLAGS_NONE,
+ strlen("ecc-no-derive"), (uint8_t*)"ecc-no-derive");
+ if (ret == 0) {
+ ret = wc_ecc_init_ex(privKey, NULL, WH_DEV_ID);
+ if (ret == 0) {
+ ret = wc_ecc_set_curve(privKey, 32, ECC_SECP256R1);
+ if (ret == 0) {
+ ret = wh_Client_EccSetKeyId(privKey, keyId);
+ }
+ if (ret == 0) {
+ const byte qx[] = {
+ 0xbb, 0x33, 0xac, 0x4c, 0x27, 0x50, 0x4a, 0xc6,
+ 0x4a, 0xa5, 0x04, 0xc3, 0x3c, 0xde, 0x9f, 0x36,
+ 0xdb, 0x72, 0x2d, 0xce, 0x94, 0xea, 0x2b, 0xfa,
+ 0xcb, 0x20, 0x09, 0x39, 0x2c, 0x16, 0xe8, 0x61};
+ const byte qy[] = {
+ 0x02, 0xe9, 0xaf, 0x4d, 0xd3, 0x02, 0x93, 0x9a,
+ 0x31, 0x5b, 0x97, 0x92, 0x21, 0x7f, 0xf0, 0xcf,
+ 0x18, 0xda, 0x91, 0x11, 0x02, 0x34, 0x86, 0xe8,
+ 0x20, 0x58, 0x33, 0x0b, 0x80, 0x34, 0x89, 0xd8};
+ int curveId = ECC_SECP256R1;
+
+ ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_ecc_import_unsigned(pubKey, qx, qy, NULL,
+ curveId);
+ if (ret == 0) {
+ ret = wc_ecc_shared_secret(privKey, pubKey,
+ sharedSecret,
+ &secretLen);
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(" PASS: Correctly denied key "
+ "derivation\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(" FAIL: Expected "
+ "WH_ERROR_USAGE, got %d\n",
+ ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ wc_ecc_free(pubKey);
+ }
+ }
+ wc_ecc_free(privKey);
+ }
+ wh_Client_KeyEvict(client, keyId);
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+#endif /* HAVE_ECC_DHE */
+#endif /* HAVE_ECC */
+
+#ifdef HAVE_HKDF
+ WH_TEST_PRINT(" Testing HKDF without DERIVE flag...\n");
+ {
+ uint8_t ikm[32] = {0};
+ whKeyId outKeyId = WH_KEYID_ERASED;
+
+ ret = wc_RNG_GenerateBlock(rng, ikm, sizeof(ikm));
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(
+ client, WH_NVM_FLAGS_NONE, (uint8_t*)"hkdf-no-derive",
+ strlen("hkdf-no-derive"), ikm, sizeof(ikm), &keyId);
+ if (ret == 0) {
+ ret = wh_Client_HkdfMakeCacheKey(
+ client, WC_SHA256, keyId, NULL, 0, NULL, 0, NULL, 0,
+ &outKeyId, WH_NVM_FLAGS_EPHEMERAL, (uint8_t*)"hkdf-out",
+ strlen("hkdf-out"), 32);
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(
+ " PASS: Correctly denied HKDF derivation\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ " FAIL: Expected WH_ERROR_USAGE, got %d\n", ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ wh_Client_KeyEvict(client, keyId);
+ if (!WH_KEYID_ISERASED(outKeyId)) {
+ wh_Client_KeyEvict(client, outKeyId);
+ }
+ }
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+#endif /* HAVE_HKDF */
+
+#if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT)
+ WH_TEST_PRINT(" Testing CMAC generate without SIGN flag...\n");
+ {
+ Cmac cmac;
+ whKeyId cmacKeyId = WH_KEYID_ERASED;
+ uint8_t message[64];
+ uint8_t tag[AES_BLOCK_SIZE];
+ word32 tagLen = sizeof(tag);
+
+ ret = wc_RNG_GenerateBlock(rng, message, sizeof(message));
+ if (ret == 0) {
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE,
+ (uint8_t*)"cmac-no-sign",
+ strlen("cmac-no-sign"), key,
+ AES_128_KEY_SIZE, &cmacKeyId);
+ }
+ if (ret == 0) {
+ ret = wc_InitCmac_ex(&cmac, NULL, 0, WC_CMAC_AES, NULL, NULL,
+ WH_DEV_ID);
+ if (ret == 0) {
+ ret = wh_Client_CmacSetKeyId(&cmac, cmacKeyId);
+ if (ret == 0) {
+ ret = wc_AesCmacGenerate_ex(&cmac, tag, &tagLen, message,
+ sizeof(message), NULL, 0, NULL,
+ WH_DEV_ID);
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(
+ " PASS: Correctly denied CMAC generate\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ " FAIL: Expected WH_ERROR_USAGE, got %d\n",
+ ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ wc_CmacFree(&cmac);
+ }
+ wh_Client_KeyEvict(client, cmacKeyId);
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ WH_TEST_PRINT(" Testing CMAC verify without VERIFY flag...\n");
+ {
+ Cmac cmac;
+ whKeyId cmacKeyId = WH_KEYID_ERASED;
+ uint8_t message[64];
+ uint8_t tag[AES_BLOCK_SIZE];
+ word32 tagLen = sizeof(tag);
+
+ ret = wc_RNG_GenerateBlock(rng, message, sizeof(message));
+ if (ret == 0) {
+ ret = wc_RNG_GenerateBlock(rng, tag, sizeof(tag));
+ }
+ if (ret == 0) {
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE,
+ (uint8_t*)"cmac-no-verify",
+ strlen("cmac-no-verify"), key,
+ AES_128_KEY_SIZE, &cmacKeyId);
+ }
+ if (ret == 0) {
+ ret = wc_InitCmac_ex(&cmac, NULL, 0, WC_CMAC_AES, NULL, NULL,
+ WH_DEV_ID);
+ if (ret == 0) {
+ ret = wh_Client_CmacSetKeyId(&cmac, cmacKeyId);
+ if (ret == 0) {
+ ret = wc_AesCmacVerify_ex(&cmac, tag, tagLen, message,
+ sizeof(message), NULL, 0, NULL,
+ WH_DEV_ID);
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(
+ " PASS: Correctly denied CMAC verify\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ " FAIL: Expected WH_ERROR_USAGE, got %d\n",
+ ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ wc_CmacFree(&cmac);
+ }
+ wh_Client_KeyEvict(client, cmacKeyId);
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+#endif /* WOLFSSL_CMAC && !NO_AES && WOLFSSL_AES_DIRECT */
+
+#ifdef WOLFHSM_CFG_KEYWRAP
+ WH_TEST_PRINT(" Testing key wrap without WRAP flag...\n");
+ {
+ uint8_t kek[32] = {0};
+ uint8_t dataKey[32] = {0};
+ uint8_t wrappedKey[256] = {0};
+ uint16_t wrappedKeySz = sizeof(wrappedKey);
+ whKeyId kekId = WH_KEYID_ERASED;
+ const whKeyId wrappedId = 1;
+ whNvmMetadata meta = {0};
+
+ ret = wc_RNG_GenerateBlock(rng, kek, sizeof(kek));
+ if (ret == 0) {
+ ret = wc_RNG_GenerateBlock(rng, dataKey, sizeof(dataKey));
+ }
+ if (ret == 0) {
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_NONE,
+ (uint8_t*)"kek-no-wrap",
+ strlen("kek-no-wrap"), kek, sizeof(kek),
+ &kekId);
+ if (ret == 0) {
+ meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(
+ client->comm->client_id, wrappedId);
+ meta.flags = WH_NVM_FLAGS_NONE;
+ meta.len = sizeof(dataKey);
+
+ ret = wh_Client_KeyWrap(client, WC_CIPHER_AES_GCM, kekId,
+ dataKey, sizeof(dataKey), &meta,
+ wrappedKey, &wrappedKeySz);
+ if (ret == WH_ERROR_USAGE) {
+ WH_TEST_PRINT(" PASS: Correctly denied key wrapping\n");
+ ret = 0;
+ }
+ else {
+ WH_ERROR_PRINT(
+ " FAIL: Expected WH_ERROR_USAGE, got %d\n", ret);
+ ret = WH_ERROR_ABORTED;
+ }
+ wh_Client_KeyEvict(client, kekId);
+ }
+ }
+ }
+ if (ret != 0) {
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+#endif /* WOLFHSM_CFG_KEYWRAP */
+
+ (void)wc_FreeRng(rng);
+ WH_TEST_PRINT("Key Usage Policy Tests PASSED\n");
+ return 0;
+}
+
+#if !defined(NO_AES) && defined(HAVE_AES_CBC) && \
+ defined(WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS)
+static int whTest_RevocationTryAESEncrypt(whKeyId keyId, WC_RNG* rng,
+ int* encryptRes)
+{
+ int ret;
+ Aes aes[1];
+ uint8_t iv[AES_BLOCK_SIZE];
+ uint8_t plaintext[16];
+ uint8_t ciphertext[16] = {0};
+
+ ret = wc_RNG_GenerateBlock(rng, iv, sizeof(iv));
+ if (ret == 0) {
+ ret = wc_RNG_GenerateBlock(rng, plaintext, sizeof(plaintext));
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate AES revocation test inputs: %d\n",
+ ret);
+ return ret;
+ }
+ ret = wc_AesInit(aes, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to init AES for revoked key test: %d\n", ret);
+ return ret;
+ }
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to set AES keyId for revoked key test: %d\n",
+ ret);
+ wc_AesFree(aes);
+ return ret;
+ }
+ ret = wc_AesSetIV(aes, iv);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to set AES IV for revoked key test: %d\n", ret);
+ wc_AesFree(aes);
+ return ret;
+ }
+ ret = wc_AesCbcEncrypt(aes, ciphertext, plaintext,
+ (word32)sizeof(plaintext));
+ wc_AesFree(aes);
+ *encryptRes = ret;
+ return WH_ERROR_OK;
+}
+
+static int _whTest_CryptoKeyRevocationAesCbc(whClientContext* client)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ WC_RNG rng[1];
+ uint8_t key[32] = {0};
+ const uint8_t label[] = "revocation-aes-cbc";
+ whKeyId keyId = WH_KEYID_ERASED;
+ const int expectedEraseErr = WH_ERROR_ACCESS;
+ int encryptRes = 0;
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ WH_TEST_PRINT("Testing Key Revocation...\n");
+ WH_TEST_PRINT(" AES-CBC key revoke flow...\n");
+
+ ret = wc_RNG_GenerateBlock(rng, key, sizeof(key));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate AES revocation inputs: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ANY, (uint8_t*)label,
+ sizeof(label), key, sizeof(key), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache AES key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = whTest_RevocationTryAESEncrypt(keyId, rng, &encryptRes);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to encrypt with unrevoked AES key: %d\n", ret);
+ (void)wh_Client_KeyEvict(client, keyId);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+ if (encryptRes != 0) {
+ WH_ERROR_PRINT("Encrypt with unrevoked AES key failed: %d\n",
+ encryptRes);
+ (void)wc_FreeRng(rng);
+ return encryptRes;
+ }
+
+ ret = wh_Client_KeyRevoke(client, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to revoke AES key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = whTest_RevocationTryAESEncrypt(keyId, rng, &encryptRes);
+ if (ret != 0 || encryptRes != WH_ERROR_USAGE) {
+ WH_ERROR_PRINT(
+ "Encrypt with revoked AES key should fail (%d), got %d\n",
+ WH_ERROR_USAGE, encryptRes);
+ (void)wc_FreeRng(rng);
+ return WH_ERROR_ABORTED;
+ }
+
+ ret = wh_Client_KeyCommit(client, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to commit revoked AES key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ ret = whTest_RevocationTryAESEncrypt(keyId, rng, &encryptRes);
+ if (ret != 0 || encryptRes != WH_ERROR_USAGE) {
+ WH_ERROR_PRINT(
+ "Encrypt with revoked AES key should fail (%d), got %d\n",
+ WH_ERROR_USAGE, encryptRes);
+ (void)wc_FreeRng(rng);
+ return WH_ERROR_ABORTED;
+ }
+
+ ret = wh_Client_KeyErase(client, keyId);
+ if (ret != expectedEraseErr) {
+ WH_ERROR_PRINT("Revoked key erase should fail (%d), got %d\n",
+ expectedEraseErr, ret);
+ (void)wc_FreeRng(rng);
+ return WH_ERROR_ABORTED;
+ }
+
+ /* Slightly different flow: cache + commit + evict + revoke */
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(client, WH_NVM_FLAGS_USAGE_ANY, (uint8_t*)label,
+ sizeof(label), key, sizeof(key), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache AES key (2nd time): %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+ ret = wh_Client_KeyCommit(client, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to commit AES key (2nd time): %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+ ret = whTest_RevocationTryAESEncrypt(keyId, rng, &encryptRes);
+ if (ret != 0 || encryptRes != 0) {
+ WH_ERROR_PRINT(
+ "Failed to encrypt with unrevoked AES key (2nd time): %d\n", ret);
+ (void)wh_Client_KeyEvict(client, keyId);
+ (void)wc_FreeRng(rng);
+ return ret != 0 ? ret : encryptRes;
+ }
+ ret = wh_Client_KeyEvict(client, keyId);
+ if (ret != 0 && ret != WH_ERROR_NOTFOUND) {
+ WH_ERROR_PRINT("Failed to evict AES key (2nd time): %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+ ret = wh_Client_KeyRevoke(client, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to revoke AES key (2nd time): %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+ ret = whTest_RevocationTryAESEncrypt(keyId, rng, &encryptRes);
+ if (ret != 0 || encryptRes != WH_ERROR_USAGE) {
+ WH_ERROR_PRINT(
+ "Encrypt with revoked AES key should fail (%d), got %d\n",
+ WH_ERROR_USAGE, encryptRes);
+ (void)wh_Client_KeyEvict(client, keyId);
+ (void)wc_FreeRng(rng);
+ return WH_ERROR_ABORTED;
+ }
+
+ WH_TEST_PRINT(" AES-CBC revocation enforcement: PASS\n");
+ (void)wc_FreeRng(rng);
+ return 0;
+}
+#endif /* !NO_AES && HAVE_AES_CBC && \
+ WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS */
+
+int whTest_Crypto_KeyPolicy(whClientContext* ctx)
+{
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoKeyUsagePolicies(ctx));
+#if !defined(NO_AES) && defined(HAVE_AES_CBC) && \
+ defined(WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS)
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoKeyRevocationAesCbc(ctx));
+#endif
+ return 0;
+}
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_mldsa.c b/test-refactor/client-server/wh_test_crypto_mldsa.c
new file mode 100644
index 000000000..2cb88baf4
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_mldsa.c
@@ -0,0 +1,1155 @@
+/*
+ * 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_mldsa.c
+ *
+ * ML-DSA (Dilithium) tests routed through the server:
+ * _whTest_CryptoMlDsaClient - non-DMA generate / sign / verify, with
+ * and without FIPS 204 context strings
+ * _whTest_CryptoMlDsaDmaClient - DMA generate / import / export / sign /
+ * verify, with key round-trip equality
+ * _whTest_CryptoMlDsaVerifyOnlyDma - import a known public key + signature,
+ * verify against fixed message vector
+ */
+
+#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 "wolfssl/wolfcrypt/dilithium.h"
+#include "wolfssl/wolfcrypt/asn.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"
+
+#ifdef HAVE_DILITHIUM
+
+#if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
+ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \
+ !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && !defined(WOLFSSL_NO_ML_DSA_44)
+
+static int _whTest_CryptoMlDsaClient(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ MlDsaKey key[1];
+
+ ret = wc_MlDsaKey_Init(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize ML-DSA key: %d\n", ret);
+ return ret;
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_MlDsaMakeExportKey(ctx, WC_ML_DSA_44, 0, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate ML-DSA key: %d\n", ret);
+ }
+ }
+
+ if (ret == 0) {
+ byte msg[] = "Test message for non-DMA ML-DSA";
+ byte sig[DILITHIUM_MAX_SIG_SIZE];
+ word32 sigLen = sizeof(sig);
+ int verified = 0;
+
+ ret = wh_Client_MlDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key,
+ NULL, 0, WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign using ML-DSA non-DMA: %d\n", ret);
+ }
+ else {
+ ret = wh_Client_MlDsaVerify(ctx, sig, sigLen, msg, sizeof(msg),
+ &verified, key, NULL, 0,
+ WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to verify ML-DSA non-DMA: %d\n", ret);
+ }
+ else if (!verified) {
+ WH_ERROR_PRINT("ML-DSA non-DMA verification failed\n");
+ ret = -1;
+ }
+ else {
+ int vret;
+ sig[0] ^= 0xFF;
+ vret = wh_Client_MlDsaVerify(ctx, sig, sigLen, msg, sizeof(msg),
+ &verified, key, NULL, 0,
+ WC_HASH_TYPE_NONE);
+ if (vret != 0) {
+ WH_ERROR_PRINT("Failed to call verify with modified sig: "
+ "%d\n",
+ vret);
+ ret = vret;
+ }
+ else if (verified) {
+ WH_ERROR_PRINT("ML-DSA non-DMA verified bad signature\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+
+ if (ret == 0) {
+ byte msg[] = "Context test message non-DMA";
+ byte sig[DILITHIUM_MAX_SIG_SIZE];
+ word32 sigLen = sizeof(sig);
+ int verified = 0;
+ const byte ctx_str[] = "test-context";
+
+ ret = wh_Client_MlDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key,
+ ctx_str, sizeof(ctx_str), WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign with context non-DMA: %d\n", ret);
+ }
+ else {
+ ret = wh_Client_MlDsaVerify(ctx, sig, sigLen, msg, sizeof(msg),
+ &verified, key, ctx_str,
+ sizeof(ctx_str), WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to verify with context non-DMA: %d\n", ret);
+ }
+ else if (!verified) {
+ WH_ERROR_PRINT("Context verification failed non-DMA\n");
+ ret = -1;
+ }
+ else {
+ const byte wrong_ctx[] = "wrong-context";
+ int wrong_verified = 0;
+ int vret = wh_Client_MlDsaVerify(
+ ctx, sig, sigLen, msg, sizeof(msg), &wrong_verified, key,
+ wrong_ctx, sizeof(wrong_ctx), WC_HASH_TYPE_NONE);
+ if (vret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to call verify with wrong context "
+ "non-DMA: %d\n",
+ vret);
+ ret = vret;
+ }
+ else if (wrong_verified) {
+ WH_ERROR_PRINT(
+ "Verification succeeded with wrong context "
+ "non-DMA\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ML-DSA Client Non-DMA API SUCCESS\n");
+ }
+
+ wc_MlDsaKey_Free(key);
+ return ret;
+}
+
+/* Cache a NONEXPORTABLE ML-DSA-44 keypair on the server, then verify that
+ * wh_Client_MlDsaExportPublicKey returns a public-only key struct. */
+static int _whTest_CryptoMlDsaExportPublicKey(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ MlDsaKey pub[1] = {0};
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t denyBuf[DILITHIUM_MAX_BOTH_KEY_DER_SIZE];
+ uint16_t denyLen = sizeof(denyBuf);
+
+ ret = wh_Client_MlDsaMakeCacheKey(
+ ctx, 0, WC_ML_DSA_44, &keyId,
+ WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY |
+ WH_NVM_FLAGS_NONEXPORTABLE,
+ 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached ML-DSA key %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Full export must be denied by the NONEXPORTABLE policy. */
+ {
+ int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf,
+ &denyLen);
+ if (denyRet != WH_ERROR_ACCESS) {
+ WH_ERROR_PRINT(
+ "NONEXPORTABLE ML-DSA full export was not denied: %d\n",
+ denyRet);
+ ret = -1;
+ }
+ }
+
+ /* Public-only export must succeed and yield a public-only key struct. */
+ if (ret == 0) {
+ ret = wc_MlDsaKey_Init(pub, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_MlDsaKey_SetParams(pub, WC_ML_DSA_44);
+ }
+ if (ret == 0) {
+ ret = wh_Client_MlDsaExportPublicKey(ctx, keyId, pub, 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("wh_Client_MlDsaExportPublicKey failed %d\n",
+ ret);
+ }
+ else if (pub->pubKeySet != 1 || pub->prvKeySet != 0) {
+ WH_ERROR_PRINT(
+ "Exported ML-DSA key flags wrong: pub=%d prv=%d\n",
+ (int)pub->pubKeySet, (int)pub->prvKeySet);
+ ret = -1;
+ }
+ }
+ wc_MlDsaKey_Free(pub);
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ML-DSA EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+static int _whTest_CryptoMlDsaDmaClient(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID_DMA;
+ int ret = 0;
+ MlDsaKey key[1];
+ MlDsaKey imported_key[1];
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t label[] = "ML-DSA Test Key";
+ int keyImported = 0;
+
+ /* Buffers for comparing serialized keys */
+ byte key_der1[DILITHIUM_MAX_PRV_KEY_SIZE];
+ byte key_der2[DILITHIUM_MAX_PRV_KEY_SIZE];
+ word32 key_der1_len = sizeof(key_der1);
+ word32 key_der2_len = sizeof(key_der2);
+
+ ret = wc_MlDsaKey_Init(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize ML-DSA key: %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_MlDsaKey_Init(imported_key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize imported ML-DSA key: %d\n", ret);
+ wc_MlDsaKey_Free(key);
+ return ret;
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_MlDsaMakeExportKeyDma(ctx, WC_ML_DSA_44, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate ML-DSA key using DMA: %d\n",
+ ret);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_Dilithium_PrivateKeyToDer(key, key_der1, key_der1_len);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to serialize generated key: %d\n", ret);
+ }
+ else {
+ key_der1_len = ret;
+ ret = 0;
+ }
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_MlDsaImportKeyDma(ctx, key, &keyId, WH_NVM_FLAGS_NONE,
+ sizeof(label), label);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to import ML-DSA key using DMA: %d\n",
+ ret);
+ }
+ keyImported = (ret == 0);
+ }
+
+ if (ret == 0) {
+ ret = wh_Client_MlDsaExportKeyDma(ctx, keyId, imported_key,
+ sizeof(label), label);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to export ML-DSA key using DMA: %d\n",
+ ret);
+ }
+ }
+
+ if (ret == 0) {
+ ret = wc_Dilithium_PrivateKeyToDer(imported_key, key_der2,
+ key_der2_len);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to serialize exported key: %d\n", ret);
+ }
+ else {
+ key_der2_len = ret;
+ ret = 0;
+ }
+ }
+
+ if (ret == 0) {
+ if (key_der1_len != key_der2_len ||
+ memcmp(key_der1, key_der2, key_der1_len) != 0) {
+ WH_ERROR_PRINT("Exported key does not match generated key\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ byte msg[] = "Test message to sign";
+ byte sig[DILITHIUM_MAX_SIG_SIZE];
+ word32 sigLen = sizeof(sig);
+ int verified = 0;
+
+ ret = wh_Client_MlDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, key,
+ NULL, 0, WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign message using ML-DSA: %d\n", ret);
+ }
+ else {
+ ret = wh_Client_MlDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg),
+ &verified, key, NULL, 0,
+ WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to verify signature using ML-DSA: %d\n", ret);
+ }
+ else if (!verified) {
+ WH_ERROR_PRINT("Signature verification failed when it should "
+ "have succeeded\n");
+ ret = -1;
+ }
+ else {
+ sig[0] ^= 0xFF;
+ ret = wh_Client_MlDsaVerifyDma(ctx, sig, sigLen, msg,
+ sizeof(msg), &verified, key,
+ NULL, 0, WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to verify modified signature using "
+ "ML-DSA: %d\n",
+ ret);
+ }
+ else if (verified) {
+ WH_ERROR_PRINT("Signature verification succeeded when it "
+ "should have failed\n");
+ ret = -1;
+ }
+ else {
+ ret = 0;
+ }
+ }
+ }
+ }
+
+ if (ret == 0) {
+ byte msg[] = "Context test message";
+ byte sig[DILITHIUM_MAX_SIG_SIZE];
+ word32 sigLen = sizeof(sig);
+ int verified = 0;
+ const byte ctx_str[] = "test-context";
+
+ ret = wh_Client_MlDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, key,
+ ctx_str, sizeof(ctx_str),
+ WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign with context using ML-DSA: %d\n",
+ ret);
+ }
+ else {
+ ret = wh_Client_MlDsaVerifyDma(
+ ctx, sig, sigLen, msg, sizeof(msg), &verified, key, ctx_str,
+ sizeof(ctx_str), WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to verify with context using ML-DSA: %d\n", ret);
+ }
+ else if (!verified) {
+ WH_ERROR_PRINT("Context verification failed when it should "
+ "have succeeded\n");
+ ret = -1;
+ }
+ else {
+ const byte wrong_ctx[] = "wrong-context";
+ int wrong_verified = 0;
+ int vret = wh_Client_MlDsaVerifyDma(
+ ctx, sig, sigLen, msg, sizeof(msg), &wrong_verified, key,
+ wrong_ctx, sizeof(wrong_ctx), WC_HASH_TYPE_NONE);
+ if (vret != 0) {
+ WH_ERROR_PRINT("Failed to call verify with wrong context: "
+ "%d\n",
+ vret);
+ ret = vret;
+ }
+ else if (wrong_verified) {
+ WH_ERROR_PRINT("Context verification succeeded with wrong "
+ "context\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+
+ if (keyImported) {
+ int evict_ret = wh_Client_KeyEvict(ctx, keyId);
+ if (evict_ret != 0) {
+ WH_ERROR_PRINT("Failed to evict ML-DSA key: %d\n", evict_ret);
+ if (ret == 0) {
+ ret = evict_ret;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ML-DSA Client DMA API SUCCESS\n");
+ }
+
+ wc_MlDsaKey_Free(key);
+ wc_MlDsaKey_Free(imported_key);
+ return ret;
+}
+
+/* DMA counterpart to _whTest_CryptoMlDsaExportPublicKey: cache a NONEXPORTABLE
+ * ML-DSA-44 keypair, then verify wh_Client_MlDsaExportPublicKeyDma yields a
+ * public-only key. */
+static int _whTest_CryptoMlDsaExportPublicKeyDma(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = 0;
+ MlDsaKey pub[1] = {0};
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t denyBuf[DILITHIUM_MAX_BOTH_KEY_DER_SIZE];
+ uint16_t denyLen = sizeof(denyBuf);
+
+ ret = wh_Client_MlDsaMakeCacheKey(
+ ctx, 0, WC_ML_DSA_44, &keyId,
+ WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY |
+ WH_NVM_FLAGS_NONEXPORTABLE,
+ 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "Failed to make NONEXPORTABLE cached ML-DSA key (DMA test) %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Full DMA export must be denied by the NONEXPORTABLE policy. */
+ {
+ int denyRet = wh_Client_KeyExportDma(ctx, keyId, denyBuf,
+ (uint16_t)sizeof(denyBuf), NULL, 0,
+ &denyLen);
+ if (denyRet != WH_ERROR_ACCESS) {
+ WH_ERROR_PRINT(
+ "NONEXPORTABLE ML-DSA full DMA export was not denied: %d\n",
+ denyRet);
+ ret = -1;
+ }
+ }
+
+ /* Public-only DMA export must succeed and yield a public-only key. */
+ if (ret == 0) {
+ ret = wc_MlDsaKey_Init(pub, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_MlDsaKey_SetParams(pub, WC_ML_DSA_44);
+ }
+ if (ret == 0) {
+ ret = wh_Client_MlDsaExportPublicKeyDma(ctx, keyId, pub, 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT(
+ "wh_Client_MlDsaExportPublicKeyDma failed %d\n", ret);
+ }
+ else if (pub->pubKeySet != 1 || pub->prvKeySet != 0) {
+ WH_ERROR_PRINT(
+ "Exported ML-DSA key (DMA) flags wrong: pub=%d prv=%d\n",
+ (int)pub->pubKeySet, (int)pub->prvKeySet);
+ ret = -1;
+ }
+ }
+ wc_MlDsaKey_Free(pub);
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ML-DSA EXPORT-PUBLIC DMA DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+#endif /* !WOLFSSL_DILITHIUM_NO_VERIFY && !WOLFSSL_DILITHIUM_NO_SIGN && \
+ !WOLFSSL_DILITHIUM_NO_MAKE_KEY && !WOLFSSL_NO_ML_DSA_44 */
+
+#if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
+ !defined(WOLFSSL_NO_ML_DSA_44) && \
+ defined(WOLFHSM_CFG_DMA)
+static int _whTest_CryptoMlDsaVerifyOnlyDma(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID_DMA;
+
+ /* Vectors from wolfCrypt test vectors, but decoupled for isolated usage */
+ const byte ml_dsa_44_pub_key[] = {
+ 0xd8, 0xac, 0xaf, 0xd8, 0x2e, 0x14, 0x23, 0x78, 0xf7, 0x0d, 0x9a, 0x04,
+ 0x2b, 0x92, 0x48, 0x67, 0x60, 0x55, 0x34, 0xd9, 0xac, 0x0b, 0xc4, 0x1f,
+ 0x46, 0xe8, 0x85, 0xb9, 0x2e, 0x1b, 0x10, 0x3a, 0x75, 0x7a, 0xc2, 0xbc,
+ 0x76, 0xf0, 0x6d, 0x05, 0xa4, 0x78, 0x48, 0x84, 0x26, 0x69, 0xbd, 0x26,
+ 0x1d, 0x73, 0x60, 0xaa, 0x57, 0x9d, 0x8c, 0x66, 0xb1, 0x19, 0xea, 0x11,
+ 0xff, 0xbb, 0xf6, 0xeb, 0x26, 0x26, 0xac, 0x78, 0x74, 0x46, 0x6d, 0x51,
+ 0x6e, 0x92, 0xdf, 0x6a, 0x98, 0x41, 0xe9, 0x10, 0xf2, 0xcc, 0xa8, 0x7a,
+ 0x50, 0xdb, 0x1f, 0x4c, 0x42, 0x19, 0xd5, 0xbc, 0x76, 0x20, 0x6f, 0x2f,
+ 0xbf, 0xc2, 0xc9, 0x1b, 0x02, 0xb5, 0xb1, 0x09, 0x46, 0x06, 0x87, 0x02,
+ 0xac, 0x3d, 0xcf, 0xc3, 0xa5, 0x1b, 0xf0, 0xce, 0xd4, 0x9e, 0x84, 0x34,
+ 0x3c, 0x24, 0x7d, 0x89, 0xf3, 0xbf, 0x9c, 0x18, 0x9d, 0x1b, 0x1d, 0xd4,
+ 0xf6, 0xda, 0xc9, 0xa4, 0x14, 0xc4, 0x6b, 0xd7, 0x05, 0x6d, 0xed, 0x54,
+ 0x42, 0x6b, 0x5f, 0x6d, 0x1e, 0xda, 0x6b, 0x47, 0x70, 0xe5, 0x4e, 0xe7,
+ 0x25, 0x06, 0xf8, 0x28, 0x24, 0x34, 0xd6, 0xe5, 0xbe, 0xc5, 0x4f, 0x9e,
+ 0x5d, 0x33, 0xfc, 0xef, 0xe4, 0xe9, 0x55, 0x67, 0x93, 0x1f, 0x2e, 0x11,
+ 0x3a, 0x2e, 0xf2, 0xbb, 0x82, 0x09, 0x8d, 0xb2, 0x09, 0xf3, 0x2f, 0xef,
+ 0x6f, 0x38, 0xc6, 0x56, 0xf2, 0x23, 0x08, 0x63, 0x99, 0x7f, 0x4e, 0xc0,
+ 0x9d, 0x08, 0x9d, 0xa1, 0x59, 0x6e, 0xe1, 0x00, 0x2c, 0x99, 0xec, 0x83,
+ 0x2f, 0x12, 0x97, 0x2f, 0x75, 0x04, 0x67, 0x44, 0xb5, 0x95, 0xce, 0xc6,
+ 0x3e, 0x7a, 0x10, 0x77, 0x5e, 0xbe, 0x9c, 0x0f, 0xb3, 0xc7, 0x38, 0xbf,
+ 0x9e, 0x35, 0x8f, 0xe4, 0x8d, 0x19, 0xc3, 0x41, 0xb1, 0x0b, 0x8c, 0x10,
+ 0x9a, 0x58, 0xec, 0x4f, 0xb3, 0xe9, 0x5b, 0x72, 0x4b, 0xb8, 0x99, 0x34,
+ 0x9a, 0xcd, 0xb0, 0x69, 0xd0, 0x67, 0xef, 0x96, 0xb9, 0xe5, 0x54, 0x92,
+ 0xb7, 0x1a, 0x52, 0xf6, 0x0a, 0xc2, 0x23, 0x8d, 0x4f, 0xad, 0x00, 0xae,
+ 0x0f, 0x97, 0xfa, 0xce, 0x96, 0xba, 0xe7, 0x74, 0x55, 0xd4, 0xaf, 0xbf,
+ 0xa1, 0x32, 0x91, 0x2d, 0x03, 0x9f, 0xe3, 0x10, 0x8c, 0x77, 0x5d, 0x26,
+ 0x76, 0xf1, 0x87, 0x90, 0xf0, 0x20, 0xd1, 0xea, 0xf7, 0xa4, 0xe8, 0x2c,
+ 0x32, 0x1c, 0x55, 0xc0, 0x5d, 0xc9, 0xcd, 0x4e, 0x8f, 0x0d, 0xef, 0x0a,
+ 0x27, 0xb6, 0x4f, 0xa4, 0xd3, 0xa4, 0xed, 0x33, 0x22, 0xa1, 0xd3, 0x15,
+ 0xac, 0x1a, 0x20, 0x4e, 0x28, 0x8c, 0x8c, 0xd0, 0x71, 0xd1, 0xf2, 0xdb,
+ 0x33, 0x63, 0xb6, 0xa4, 0xf2, 0x17, 0x3c, 0x12, 0xb0, 0xad, 0xef, 0x31,
+ 0x91, 0xfe, 0xe5, 0x53, 0x99, 0xb6, 0x85, 0x63, 0xfa, 0xe6, 0xcd, 0xf6,
+ 0xb9, 0xce, 0x4a, 0x7d, 0x4a, 0x49, 0x29, 0xd2, 0xd9, 0xc9, 0x47, 0x4a,
+ 0x8a, 0x5c, 0x14, 0x5e, 0x0f, 0x7c, 0xc3, 0x91, 0xb0, 0xab, 0x37, 0xf5,
+ 0x26, 0x8d, 0x46, 0x74, 0x49, 0xad, 0x51, 0xc3, 0x11, 0xfa, 0x85, 0x15,
+ 0xa5, 0x84, 0xc1, 0xe0, 0x3c, 0x13, 0x6d, 0x13, 0xa3, 0xe6, 0xa8, 0x3c,
+ 0x22, 0xac, 0x17, 0x48, 0x57, 0x7c, 0x81, 0xe2, 0x4e, 0xd8, 0x33, 0x5d,
+ 0x4d, 0x65, 0xf7, 0xe1, 0xb8, 0x00, 0x78, 0x09, 0x16, 0xb0, 0x0b, 0xca,
+ 0x15, 0x0d, 0xcd, 0x9a, 0xd8, 0x47, 0x4c, 0x9b, 0x69, 0xb2, 0xa0, 0x9d,
+ 0x96, 0x96, 0x52, 0x6d, 0x89, 0xad, 0xff, 0x55, 0xde, 0x7b, 0xd6, 0x3d,
+ 0x1d, 0x5e, 0x8d, 0xf1, 0xfc, 0x48, 0x1c, 0x50, 0x59, 0x55, 0xb9, 0x07,
+ 0xfd, 0x6b, 0xcb, 0x95, 0xa6, 0x14, 0x73, 0xdb, 0x40, 0x40, 0x1c, 0x44,
+ 0xe6, 0x79, 0x30, 0x88, 0xbd, 0xa0, 0xde, 0x9b, 0xb8, 0x76, 0xf8, 0x98,
+ 0x56, 0x4b, 0xb9, 0x7a, 0xf6, 0xd4, 0x73, 0x89, 0x6b, 0xf7, 0x7d, 0x05,
+ 0x33, 0xbe, 0xb6, 0x1c, 0x4d, 0xa7, 0x12, 0x3b, 0x3f, 0xed, 0x4a, 0x0f,
+ 0xae, 0xa7, 0x6a, 0x26, 0x0d, 0x01, 0x84, 0x84, 0xa8, 0x0e, 0xc1, 0xc1,
+ 0xfd, 0xe4, 0xa9, 0xe2, 0x3f, 0xab, 0xce, 0x20, 0x90, 0x86, 0x79, 0xa2,
+ 0x40, 0xd0, 0xef, 0x79, 0x34, 0x2b, 0xe8, 0xc9, 0x54, 0xa7, 0x19, 0x62,
+ 0xcc, 0x20, 0x79, 0x3f, 0x5b, 0x9c, 0x61, 0xc2, 0xc1, 0xd2, 0x36, 0x7c,
+ 0x8e, 0xe3, 0x01, 0xbe, 0xc4, 0xb2, 0xb8, 0x07, 0x51, 0x23, 0x5b, 0x5d,
+ 0x00, 0xe6, 0x7f, 0xd6, 0xbb, 0x32, 0xa9, 0x7e, 0xb4, 0x30, 0xeb, 0x5e,
+ 0x6d, 0xed, 0xb2, 0xc3, 0x88, 0x81, 0xa3, 0x3b, 0x1f, 0x1e, 0xf9, 0x48,
+ 0x10, 0xd6, 0x01, 0x65, 0x5f, 0x6d, 0xc5, 0xeb, 0x76, 0x5f, 0x10, 0x79,
+ 0xaa, 0xc0, 0x86, 0xe7, 0x44, 0x95, 0x44, 0x4b, 0x54, 0x0c, 0x46, 0x2a,
+ 0x98, 0x01, 0x6e, 0xc0, 0xb9, 0x59, 0x2a, 0xff, 0x8f, 0xb3, 0x80, 0x15,
+ 0xec, 0xcd, 0x39, 0x36, 0xd7, 0x2f, 0x20, 0x9e, 0x3a, 0xc1, 0x90, 0xe5,
+ 0x99, 0x27, 0x16, 0xd7, 0x6c, 0x30, 0x10, 0x12, 0x03, 0x3e, 0xdc, 0xb9,
+ 0x03, 0x25, 0xb0, 0x8a, 0x27, 0x4d, 0x1a, 0x32, 0x36, 0x54, 0xc0, 0xba,
+ 0x22, 0xb2, 0xe2, 0xf6, 0x39, 0x23, 0x03, 0xc4, 0xc9, 0xe4, 0x0d, 0x99,
+ 0xfb, 0x98, 0xa5, 0x9b, 0x12, 0x9b, 0x58, 0x44, 0x74, 0x9f, 0x65, 0x61,
+ 0x51, 0xba, 0x31, 0x60, 0x9c, 0xec, 0xf8, 0x4d, 0x36, 0x61, 0xd1, 0x33,
+ 0x6d, 0xa6, 0x28, 0x75, 0xba, 0x7c, 0x82, 0xcb, 0x7e, 0xbe, 0x8f, 0x2d,
+ 0x21, 0x84, 0xb9, 0xf2, 0x4e, 0x7b, 0x95, 0x99, 0x11, 0xf3, 0xe1, 0xc0,
+ 0x6a, 0x44, 0xae, 0x11, 0xcb, 0x04, 0xa0, 0xf2, 0x3e, 0x17, 0xdf, 0xb2,
+ 0x6a, 0xdf, 0x5c, 0xf3, 0x8a, 0xf8, 0x90, 0x86, 0x64, 0xea, 0x0a, 0x32,
+ 0x7f, 0x9f, 0x90, 0xa8, 0x9d, 0x33, 0x12, 0xa6, 0xa4, 0xe7, 0x74, 0xa0,
+ 0x75, 0xa9, 0x65, 0xf8, 0x39, 0xae, 0x14, 0x32, 0x79, 0xcc, 0xaa, 0x34,
+ 0x86, 0x55, 0xcc, 0x99, 0xb7, 0x00, 0x05, 0x8b, 0xe3, 0x76, 0x28, 0x12,
+ 0xb6, 0x2a, 0x3e, 0x44, 0x8d, 0xf4, 0xba, 0xef, 0xf6, 0xdc, 0x29, 0x08,
+ 0x29, 0x7d, 0xd1, 0x1d, 0x17, 0x15, 0xb6, 0xb6, 0x58, 0x67, 0xd5, 0xd3,
+ 0x12, 0x05, 0x4e, 0xb0, 0xc3, 0x83, 0xe0, 0x35, 0x30, 0x60, 0x59, 0xa0,
+ 0xc5, 0x97, 0x5b, 0x81, 0xd3, 0x68, 0x6c, 0x8c, 0x17, 0x28, 0xa9, 0x24,
+ 0x4f, 0x80, 0x20, 0xa5, 0x21, 0x9f, 0x8f, 0x15, 0x89, 0x2d, 0x87, 0xae,
+ 0x2e, 0xcc, 0x73, 0x3e, 0x06, 0x43, 0xbc, 0xb3, 0x1b, 0xa6, 0x72, 0xaa,
+ 0xa3, 0xaa, 0xbb, 0x6f, 0x2d, 0x68, 0x60, 0xcf, 0x05, 0x94, 0x25, 0x3e,
+ 0x59, 0xf3, 0x64, 0x61, 0x5e, 0x78, 0x9a, 0x7e, 0x0d, 0x50, 0x45, 0x78,
+ 0x51, 0xab, 0x11, 0xb1, 0xc6, 0x95, 0xfc, 0x29, 0x28, 0x10, 0x9c, 0x1a,
+ 0x8c, 0x37, 0xb5, 0x4f, 0x0e, 0xed, 0x4a, 0x28, 0x6c, 0xaa, 0xb7, 0x0d,
+ 0x12, 0xfa, 0x87, 0x5d, 0xd4, 0x9a, 0xb7, 0x2b, 0x46, 0x90, 0x58, 0x4e,
+ 0xd7, 0x8b, 0x41, 0x1b, 0xf8, 0xc4, 0xc2, 0xde, 0xda, 0xec, 0x61, 0xe7,
+ 0xbf, 0x11, 0xdd, 0x6e, 0x4e, 0x6a, 0xd4, 0x87, 0x01, 0xe4, 0xac, 0xe8,
+ 0xaf, 0x2b, 0x01, 0xe1, 0x09, 0x20, 0xe0, 0xbd, 0x7d, 0x03, 0x73, 0x23,
+ 0xdf, 0x77, 0x71, 0xa4, 0x25, 0x8b, 0x0a, 0x93, 0x49, 0x32, 0x45, 0x1a,
+ 0xa4, 0x94, 0x31, 0x61, 0x2e, 0x17, 0x39, 0x8a, 0x66, 0xc9, 0xf9, 0x20,
+ 0x2d, 0x6a, 0x97, 0x2f, 0xe7, 0x26, 0xd8, 0x01, 0x42, 0x65, 0xcf, 0xce,
+ 0xd4, 0x24, 0x41, 0xfb, 0x9b, 0x6f, 0xf1, 0xc2, 0x9e, 0xd5, 0x08, 0x0c,
+ 0xdc, 0x4d, 0x8e, 0xae, 0xcb, 0x5f, 0xd4, 0xcd, 0x7c, 0xf6, 0x82, 0xc6,
+ 0xee, 0xf9, 0x88, 0x3a, 0x34, 0x07, 0x04, 0xb4, 0x84, 0x69, 0xb3, 0xa4,
+ 0x67, 0xab, 0x09, 0xc0, 0x83, 0xfe, 0x59, 0xaf, 0x18, 0x2c, 0xc8, 0x09,
+ 0xc1, 0xbb, 0x13, 0x7c, 0xce, 0x01, 0x5d, 0x85, 0xaa, 0x10, 0x28, 0xa2,
+ 0x96, 0x98, 0x69, 0x23, 0xa3, 0xe7, 0x67, 0xbc, 0x7c, 0x7e, 0xde, 0x4b,
+ 0x36, 0xab, 0x94, 0xd2, 0xb8, 0xf9, 0xdf, 0xee, 0xa1, 0x69, 0xa1, 0xc8,
+ 0xe9, 0x83, 0x21, 0xac, 0x1b, 0x39, 0xf7, 0x6d, 0xbf, 0x8c, 0xdb, 0xd6,
+ 0x2f, 0xc9, 0x3c, 0x3d, 0x50, 0xcf, 0x7f, 0xbe, 0x4a, 0x8d, 0xd8, 0x14,
+ 0xad, 0x69, 0xb0, 0x3e, 0x8a, 0xaf, 0xeb, 0xd9, 0x1a, 0x15, 0x4a, 0xe4,
+ 0xdd, 0xd9, 0xb2, 0xf8, 0x6b, 0xe2, 0x42, 0x9e, 0x29, 0x16, 0xfc, 0x85,
+ 0x9c, 0x47, 0x4b, 0x1f, 0x3d, 0x7b, 0x8c, 0xe1, 0x6d, 0xa3, 0xb8, 0x0a,
+ 0xe6, 0xfa, 0x27, 0xfe, 0x52, 0x72, 0xab, 0x3a, 0xa6, 0x58, 0xd7, 0x53,
+ 0xaf, 0x9f, 0xee, 0x03, 0x85, 0xfc, 0xa4, 0x7a, 0x72, 0x29, 0x7e, 0x62,
+ 0x28, 0x08, 0x79, 0xa8, 0xb8, 0xc7, 0x51, 0x8d, 0xaa, 0x40, 0x2d, 0x4a,
+ 0xd9, 0x47, 0xb4, 0xa8, 0xa2, 0x0a, 0x43, 0xd0, 0xe0, 0x4a, 0x39, 0xa3,
+ 0x06, 0x08, 0x9a, 0xe2, 0xf3, 0xf2, 0xf8, 0xb9, 0x9f, 0x63, 0x32, 0xa0,
+ 0x65, 0x0b, 0xb0, 0x50, 0x96, 0xa6, 0xa8, 0x7a, 0x18, 0xdd, 0x6c, 0xd1,
+ 0x9b, 0xd9, 0x4e, 0x76, 0x8f, 0xfb, 0x22, 0xa6, 0x1d, 0x29, 0xfc, 0xb8,
+ 0x47, 0x29, 0xb6, 0xd1, 0xb1, 0x63, 0x4a, 0x36, 0x1b, 0x10, 0xe6, 0x4c,
+ 0x65, 0x68, 0x1f, 0xad, 0x4f, 0x7d, 0x6b, 0x01, 0x41, 0x18, 0x5f, 0xba,
+ 0x3d, 0xa6, 0x54, 0x28, 0x58, 0xd5, 0x81, 0x60, 0xdf, 0x84, 0x76, 0x00,
+ 0x21, 0x53, 0xeb, 0xd3, 0xa6, 0xec, 0x7d, 0x3c, 0xb8, 0xcd, 0x91, 0x4c,
+ 0x2f, 0x4b, 0x2e, 0x23, 0x4c, 0x0f, 0x0f, 0xe0, 0x14, 0xa5, 0xe7, 0xe5,
+ 0x70, 0x8d, 0x8b, 0x9c};
+ const byte ml_dsa_44_sig[] = {
+ 0x27, 0x3b, 0x58, 0xa0, 0xcf, 0x00, 0x29, 0x5e, 0x1a, 0x63, 0xbf, 0xb4,
+ 0x97, 0x16, 0xa1, 0x9c, 0x78, 0xd1, 0x33, 0xdc, 0x72, 0xde, 0xa3, 0xfc,
+ 0xf4, 0x09, 0xb1, 0x09, 0x16, 0x3f, 0x80, 0x72, 0x22, 0x68, 0x65, 0x68,
+ 0xb9, 0x80, 0x5a, 0x4a, 0x0d, 0x73, 0x49, 0xe1, 0xc6, 0xde, 0xca, 0x08,
+ 0x4f, 0xca, 0xf8, 0xb2, 0xf8, 0x45, 0x3b, 0x6b, 0x8c, 0x6c, 0xfd, 0x3a,
+ 0xf4, 0xde, 0xde, 0x82, 0xd8, 0x04, 0xbe, 0x4f, 0x4a, 0xdb, 0x92, 0x47,
+ 0x83, 0x2d, 0xc4, 0x55, 0xed, 0x20, 0x4f, 0x71, 0xb1, 0x58, 0xd9, 0x70,
+ 0x73, 0xbd, 0xb0, 0x3a, 0xb4, 0x8f, 0xd6, 0x9e, 0x32, 0x98, 0x2b, 0x9e,
+ 0xff, 0x2a, 0x7c, 0xcb, 0x05, 0x1b, 0x8e, 0xe6, 0x3a, 0x45, 0xc6, 0x7a,
+ 0xc8, 0xaf, 0x62, 0xd3, 0x04, 0xfa, 0x69, 0x4f, 0xda, 0x1b, 0x74, 0x16,
+ 0x0d, 0xb3, 0x1a, 0xee, 0x71, 0xd7, 0xb0, 0xef, 0x69, 0xf5, 0xe2, 0xe9,
+ 0xc2, 0xcc, 0x15, 0x66, 0x28, 0x0a, 0xac, 0xe2, 0x63, 0x06, 0xb7, 0x21,
+ 0x0d, 0xd8, 0x5c, 0x94, 0x63, 0xfd, 0x51, 0x18, 0x9f, 0x07, 0x19, 0x3d,
+ 0xa2, 0x50, 0x40, 0xd3, 0xe9, 0x05, 0xd4, 0x11, 0x13, 0x15, 0xaa, 0x46,
+ 0xda, 0x3e, 0x5f, 0xcd, 0x3c, 0xfa, 0x42, 0xba, 0x79, 0x4a, 0xb7, 0x43,
+ 0x91, 0xa5, 0xcb, 0xbc, 0xeb, 0x37, 0x94, 0xf1, 0x9c, 0xb9, 0xdb, 0x41,
+ 0x06, 0xd8, 0x7b, 0x5e, 0x90, 0xe3, 0x3c, 0x8a, 0x10, 0x62, 0x9a, 0x15,
+ 0x27, 0x78, 0xed, 0x69, 0x11, 0x2c, 0xb5, 0xb4, 0xdb, 0xc8, 0x70, 0x50,
+ 0x62, 0x47, 0x96, 0xcb, 0xd9, 0xb2, 0x3e, 0x59, 0x2f, 0x1c, 0xac, 0xcb,
+ 0xcf, 0x22, 0xc2, 0x9b, 0xc7, 0x92, 0xe9, 0x4d, 0x8d, 0x5d, 0xcf, 0x06,
+ 0x53, 0x7e, 0xf4, 0x4e, 0xfe, 0x9e, 0x41, 0x5d, 0x00, 0x8c, 0x08, 0xf4,
+ 0x02, 0x79, 0x33, 0x1c, 0x27, 0x1d, 0xe3, 0x94, 0xac, 0xe6, 0x87, 0xa0,
+ 0x08, 0xb4, 0x60, 0x0c, 0xff, 0x47, 0xdc, 0x16, 0x3a, 0x1d, 0x89, 0xc0,
+ 0x6a, 0xa4, 0x3d, 0x71, 0x33, 0xdd, 0x1e, 0x70, 0xfe, 0xd4, 0x8b, 0xed,
+ 0x7c, 0x91, 0xe4, 0xe2, 0x15, 0x06, 0xc1, 0x83, 0x24, 0x55, 0xa7, 0x2a,
+ 0x9f, 0x4e, 0xd9, 0x56, 0x7a, 0x95, 0xa8, 0xdd, 0xc4, 0xf0, 0x71, 0x3a,
+ 0x99, 0x65, 0x31, 0x4b, 0xb7, 0x96, 0x2c, 0x53, 0x54, 0x83, 0xec, 0xc9,
+ 0x97, 0x2f, 0x0c, 0xa4, 0x8f, 0xbb, 0x93, 0x9d, 0xea, 0xae, 0xf9, 0xcb,
+ 0xb2, 0xb9, 0xa3, 0x61, 0x5f, 0x77, 0x8c, 0xb6, 0x5a, 0x56, 0xbe, 0x5f,
+ 0x85, 0xd1, 0xb5, 0x0a, 0x53, 0xe2, 0xc7, 0xbf, 0x76, 0x8b, 0x97, 0x6f,
+ 0x10, 0xdd, 0x1f, 0x44, 0x69, 0x66, 0x03, 0xc4, 0x6b, 0x59, 0xf7, 0xb4,
+ 0xc1, 0x12, 0xcc, 0x00, 0x70, 0xe8, 0xbd, 0x44, 0x28, 0xf5, 0xfa, 0x96,
+ 0xf3, 0x59, 0xed, 0x81, 0x67, 0xe0, 0xbe, 0x47, 0x75, 0xb3, 0xa8, 0x9f,
+ 0x21, 0x70, 0x2e, 0x6f, 0xef, 0x54, 0x11, 0x3f, 0x34, 0xaf, 0x0d, 0x73,
+ 0x5b, 0x9e, 0x6d, 0x86, 0x58, 0xb7, 0x34, 0xc2, 0xc2, 0xb3, 0x64, 0xd5,
+ 0x9b, 0x6e, 0xb9, 0x99, 0x6a, 0xe4, 0xfd, 0xc3, 0x17, 0xf3, 0x10, 0xfc,
+ 0x6e, 0xf5, 0x65, 0xe1, 0x9c, 0x59, 0x15, 0x11, 0x00, 0xea, 0x96, 0x81,
+ 0x69, 0x9b, 0x05, 0x4d, 0xf3, 0xce, 0xf3, 0xf0, 0xa9, 0x01, 0x3f, 0x13,
+ 0xbb, 0xb0, 0xac, 0xc3, 0x92, 0x1c, 0x2b, 0x61, 0xe3, 0x01, 0x22, 0x45,
+ 0x4a, 0x23, 0x19, 0x80, 0xca, 0xb9, 0xef, 0x4e, 0x76, 0x52, 0xc5, 0x9d,
+ 0x91, 0x33, 0x17, 0xc4, 0x28, 0x83, 0x55, 0x61, 0x49, 0x72, 0x04, 0xaa,
+ 0xf8, 0xe3, 0x4b, 0x20, 0xf7, 0x6a, 0x74, 0x56, 0x64, 0xf9, 0xb3, 0xc9,
+ 0x67, 0x5b, 0x55, 0x29, 0x9a, 0x89, 0xa5, 0x14, 0x67, 0xea, 0x6d, 0x6a,
+ 0xde, 0x98, 0x58, 0x73, 0x25, 0xa3, 0xdb, 0xed, 0x3d, 0x62, 0xaa, 0xe0,
+ 0x79, 0x7f, 0xa3, 0xd9, 0xb5, 0x4c, 0xe9, 0xa8, 0xdf, 0xfd, 0x59, 0x31,
+ 0x42, 0x81, 0x9e, 0xb7, 0x81, 0x3f, 0x0e, 0xfb, 0xef, 0x80, 0x71, 0x9d,
+ 0xb7, 0xa5, 0xfc, 0xb1, 0x80, 0xc9, 0x7e, 0x31, 0xd9, 0x47, 0xe2, 0xca,
+ 0x10, 0x7b, 0xd1, 0xa1, 0x1c, 0x28, 0xc7, 0x7f, 0x51, 0x26, 0xb1, 0x4e,
+ 0x57, 0xdd, 0x7d, 0x76, 0x5c, 0x5a, 0x85, 0xa7, 0x7b, 0x8c, 0xc5, 0x6e,
+ 0xac, 0x20, 0xf8, 0x49, 0x16, 0xd6, 0x64, 0xf5, 0xf4, 0x2c, 0x32, 0xa1,
+ 0x5d, 0xfb, 0x87, 0xb6, 0x14, 0xfe, 0x68, 0x7c, 0x4d, 0xce, 0xd7, 0x94,
+ 0xf9, 0x8b, 0xf0, 0x61, 0xfd, 0xe0, 0x83, 0x7f, 0x13, 0xec, 0x7a, 0xb7,
+ 0x41, 0x04, 0x51, 0x6e, 0x30, 0xa2, 0x01, 0xf7, 0x30, 0x12, 0xec, 0xd2,
+ 0x8f, 0x73, 0xe7, 0x8e, 0x12, 0xb4, 0xe5, 0xc1, 0xff, 0xdf, 0x67, 0x14,
+ 0xb1, 0xe9, 0xba, 0x36, 0x19, 0x18, 0xf4, 0xaa, 0xe0, 0xe4, 0x9d, 0xcd,
+ 0xe8, 0xe7, 0x2b, 0x33, 0xb3, 0xdc, 0xb9, 0x19, 0xd7, 0xad, 0xa4, 0x68,
+ 0xcd, 0x83, 0x77, 0x98, 0x36, 0x49, 0xd9, 0x32, 0x20, 0xfd, 0xfc, 0x34,
+ 0xe7, 0x54, 0xd9, 0xb5, 0x05, 0xab, 0x0e, 0x08, 0x0e, 0x16, 0x8a, 0x7d,
+ 0x91, 0x4c, 0xaa, 0x19, 0x04, 0x37, 0x35, 0xa5, 0xab, 0x6c, 0xee, 0xc4,
+ 0x90, 0xf0, 0x5f, 0xc7, 0xae, 0x82, 0xfd, 0x59, 0x53, 0xe5, 0x36, 0x5a,
+ 0x56, 0x37, 0x61, 0x69, 0xda, 0xe5, 0x8f, 0xfd, 0x2e, 0xd4, 0x9c, 0x7f,
+ 0xb6, 0x39, 0xa4, 0x8d, 0x0a, 0xab, 0x82, 0x0f, 0xfe, 0x84, 0x69, 0x44,
+ 0x8a, 0xa6, 0xd0, 0x39, 0xf9, 0x72, 0x68, 0xe7, 0x97, 0xd8, 0x6c, 0x7b,
+ 0xec, 0x85, 0x8c, 0x52, 0xc9, 0x97, 0xbb, 0xc4, 0x7a, 0x67, 0x22, 0x60,
+ 0x46, 0x9f, 0x16, 0xf1, 0x67, 0x0e, 0x1b, 0x50, 0x7c, 0xc4, 0x29, 0x15,
+ 0xbc, 0x55, 0x6a, 0x67, 0xf6, 0xa8, 0x85, 0x66, 0x89, 0x9f, 0xff, 0x38,
+ 0x28, 0xaa, 0x87, 0x91, 0xce, 0xde, 0x8d, 0x45, 0x5c, 0xa1, 0x25, 0x95,
+ 0xe2, 0x86, 0xdd, 0xa1, 0x87, 0x6a, 0x0a, 0xa8, 0x3e, 0x63, 0x0e, 0x21,
+ 0xa5, 0x6e, 0x08, 0x4d, 0x07, 0xb6, 0x26, 0xa8, 0x92, 0xdb, 0xed, 0x13,
+ 0x01, 0xc3, 0xba, 0xcf, 0xad, 0x01, 0xbc, 0xe5, 0xc0, 0xba, 0xbe, 0x7c,
+ 0x75, 0xf1, 0xb9, 0xfe, 0xd3, 0xf0, 0xa5, 0x2c, 0x8e, 0x10, 0xff, 0x99,
+ 0xcb, 0xe2, 0x2d, 0xdc, 0x2f, 0x76, 0x00, 0xf8, 0x51, 0x7c, 0xcc, 0x52,
+ 0x16, 0x0f, 0x18, 0x98, 0xea, 0x34, 0x06, 0x7f, 0xb7, 0x2e, 0xe9, 0x40,
+ 0xf0, 0x2d, 0x30, 0x3d, 0xc0, 0x67, 0x4c, 0xe6, 0x63, 0x40, 0x41, 0x42,
+ 0x96, 0xbb, 0x0b, 0xd6, 0xc9, 0x1c, 0x22, 0x7a, 0xa9, 0x4d, 0xcc, 0x5b,
+ 0xaa, 0x03, 0xc6, 0x3b, 0x1e, 0x2f, 0x11, 0xae, 0x34, 0x6f, 0x0c, 0xe9,
+ 0x16, 0x9c, 0x82, 0x3b, 0x90, 0x4c, 0x0e, 0xf0, 0xf9, 0x7f, 0x02, 0xca,
+ 0xb9, 0xa9, 0x49, 0x6d, 0x27, 0x73, 0xd0, 0xbf, 0x15, 0x61, 0x52, 0xbc,
+ 0xd6, 0x31, 0x59, 0x2b, 0x52, 0x5b, 0xaf, 0x3c, 0xc0, 0x8f, 0xdc, 0xd5,
+ 0x2c, 0x1d, 0xe4, 0xe9, 0x41, 0xe8, 0xd3, 0x35, 0xd6, 0xb1, 0xf3, 0x32,
+ 0xe0, 0x52, 0x08, 0x73, 0x99, 0xb6, 0x6b, 0xbc, 0x26, 0xfb, 0x2e, 0xa7,
+ 0xb7, 0xcd, 0x14, 0xf0, 0xf9, 0xe5, 0x3a, 0xd0, 0x05, 0x5b, 0x2b, 0x38,
+ 0xbd, 0x7c, 0xda, 0xd4, 0x15, 0x45, 0xfa, 0x3b, 0x6f, 0x94, 0x8e, 0x22,
+ 0xce, 0xfa, 0x53, 0xe0, 0x5f, 0xa6, 0x9d, 0x1c, 0x26, 0x91, 0x8a, 0xab,
+ 0x72, 0x5b, 0x18, 0x78, 0x69, 0x98, 0x3f, 0x8d, 0x33, 0x7c, 0x21, 0x93,
+ 0x9e, 0xf0, 0xaf, 0xb7, 0x30, 0xc8, 0xac, 0xbc, 0xdb, 0x9c, 0x29, 0x17,
+ 0x6b, 0x9d, 0x0f, 0x16, 0xd6, 0xc0, 0xcc, 0x3b, 0xce, 0x11, 0xe9, 0x64,
+ 0xc8, 0xd4, 0x4c, 0x98, 0x7c, 0x8f, 0xf1, 0x5e, 0x84, 0xe4, 0x72, 0xf9,
+ 0x69, 0xf5, 0x9d, 0xad, 0x95, 0x3b, 0xfb, 0x6d, 0x30, 0x7e, 0x0a, 0x47,
+ 0x5b, 0x26, 0xb2, 0x4e, 0xeb, 0x1a, 0xc3, 0x37, 0x16, 0x28, 0x79, 0x62,
+ 0xb4, 0x36, 0x85, 0x4a, 0x15, 0x5a, 0xc3, 0x6e, 0xbe, 0x7e, 0x00, 0xe9,
+ 0x4a, 0xa5, 0xd7, 0x90, 0xcf, 0x59, 0x63, 0x2d, 0x2b, 0xc2, 0xc6, 0x47,
+ 0xe6, 0x77, 0xb7, 0x6e, 0x9b, 0xc8, 0x0d, 0x18, 0x2b, 0x45, 0x2b, 0xc9,
+ 0x5a, 0x6e, 0xb4, 0x50, 0xa5, 0x23, 0x7d, 0x17, 0xcc, 0x49, 0xe2, 0xb3,
+ 0xf4, 0x6d, 0xb4, 0xb7, 0xbb, 0x9e, 0xdd, 0x20, 0x99, 0x19, 0xf5, 0x53,
+ 0x1f, 0xd0, 0xff, 0x67, 0xf3, 0x8e, 0x6a, 0xcd, 0x2a, 0x6e, 0x2b, 0x0a,
+ 0x90, 0xd7, 0xdb, 0xe1, 0xff, 0x1c, 0x40, 0xa1, 0xb0, 0x5d, 0x94, 0x4d,
+ 0x20, 0x14, 0x01, 0xa1, 0xa8, 0xd1, 0x15, 0xd2, 0xd9, 0x1b, 0xbf, 0xc2,
+ 0x8a, 0xd0, 0x02, 0xf6, 0x16, 0xa1, 0xb7, 0x40, 0xe0, 0x36, 0x88, 0xc8,
+ 0x17, 0x0a, 0xf0, 0xb6, 0x0d, 0x3c, 0x53, 0xb9, 0x51, 0xed, 0xef, 0x20,
+ 0x6f, 0xf3, 0x0c, 0xb5, 0xce, 0x0e, 0x9e, 0xfd, 0x0f, 0x5e, 0x3f, 0x8f,
+ 0x3c, 0xb7, 0x2a, 0xdb, 0xc6, 0xa7, 0xf2, 0x11, 0x6e, 0xdc, 0x05, 0x33,
+ 0xd4, 0xd8, 0xb0, 0x2d, 0x8a, 0xe5, 0x39, 0x82, 0x00, 0x49, 0x7d, 0xfd,
+ 0x32, 0x29, 0xbb, 0x79, 0x5d, 0xcb, 0x21, 0x7b, 0x2d, 0x36, 0x58, 0x73,
+ 0x52, 0x57, 0x52, 0x96, 0x4d, 0x89, 0x61, 0xf4, 0xad, 0x1f, 0x48, 0xd5,
+ 0x7a, 0x4a, 0xaa, 0x1c, 0xa1, 0xf4, 0xb4, 0x9c, 0x43, 0x3b, 0x95, 0x72,
+ 0xd0, 0x0e, 0x35, 0x82, 0x26, 0xd4, 0x2e, 0xe3, 0x83, 0x96, 0x97, 0x5a,
+ 0x7b, 0xfc, 0x48, 0x17, 0x3c, 0xba, 0x9e, 0x5f, 0x46, 0x1a, 0x53, 0xe3,
+ 0x2e, 0x78, 0x79, 0x80, 0xf6, 0x2d, 0x24, 0xcf, 0x62, 0xb6, 0x86, 0xeb,
+ 0xee, 0xec, 0xf2, 0x1d, 0x00, 0xc8, 0x28, 0x9d, 0x93, 0x16, 0xa7, 0xd9,
+ 0x11, 0x47, 0xe3, 0xc4, 0xb6, 0xc4, 0xa0, 0x99, 0x83, 0xc1, 0x17, 0xd8,
+ 0x8e, 0xde, 0x69, 0x1d, 0xcb, 0xdd, 0xe7, 0x86, 0x6f, 0xf2, 0x36, 0x07,
+ 0x23, 0x86, 0x0d, 0xe9, 0xad, 0x87, 0xae, 0x76, 0x98, 0x95, 0x51, 0xf2,
+ 0xb3, 0x11, 0xc5, 0x34, 0xf0, 0x0c, 0xf8, 0x29, 0x9c, 0x84, 0x4f, 0x81,
+ 0x49, 0x85, 0x63, 0x25, 0x16, 0xb0, 0xc3, 0xaa, 0xd7, 0x8a, 0x2e, 0x4b,
+ 0x97, 0x60, 0x74, 0xf8, 0xa7, 0x39, 0xec, 0x6c, 0x2c, 0x9b, 0x33, 0x3a,
+ 0x11, 0xbd, 0xa6, 0x90, 0x48, 0x65, 0xb1, 0xe7, 0x38, 0x53, 0x47, 0x1b,
+ 0x62, 0xd5, 0xb7, 0xa8, 0xd4, 0xae, 0xf5, 0x12, 0x06, 0x12, 0x54, 0xa2,
+ 0xce, 0xf1, 0x6b, 0x3a, 0xda, 0x63, 0x2e, 0x37, 0x2a, 0x25, 0x89, 0x30,
+ 0x98, 0x77, 0x1d, 0x4b, 0x5a, 0x1e, 0xb7, 0x3d, 0xed, 0x19, 0xec, 0x9f,
+ 0x64, 0x46, 0xa8, 0x2a, 0x79, 0xf3, 0x70, 0x39, 0x9f, 0x8c, 0xc3, 0x28,
+ 0xcc, 0x2a, 0xc0, 0xd0, 0xe6, 0x80, 0xf5, 0x01, 0x78, 0x72, 0x7f, 0xe7,
+ 0x2e, 0x7b, 0x5f, 0x05, 0xc3, 0x41, 0x33, 0x07, 0xdb, 0x9c, 0xa8, 0x96,
+ 0xa7, 0x21, 0x20, 0x23, 0xd0, 0x59, 0x39, 0x06, 0x19, 0xa4, 0x29, 0xe5,
+ 0x72, 0x39, 0x69, 0x23, 0xe3, 0xfa, 0x28, 0x63, 0xf5, 0x42, 0x3b, 0xca,
+ 0x88, 0x5d, 0x7e, 0x47, 0x93, 0xa8, 0x8c, 0x75, 0xf2, 0x19, 0x44, 0x43,
+ 0x15, 0x39, 0x03, 0x42, 0xd8, 0x1d, 0x81, 0x30, 0x8e, 0x84, 0x31, 0x24,
+ 0x75, 0x67, 0x4e, 0xbe, 0xfe, 0x0a, 0xd8, 0xc3, 0xe7, 0x5b, 0xe1, 0xd5,
+ 0x12, 0x6a, 0x69, 0x99, 0xcd, 0x35, 0xca, 0x22, 0x02, 0x65, 0xb3, 0x0f,
+ 0x50, 0xb6, 0xaa, 0xc6, 0x91, 0x5c, 0x4d, 0xd4, 0x07, 0x93, 0x46, 0xf0,
+ 0xcc, 0xe1, 0x92, 0x14, 0x91, 0x21, 0x43, 0xc4, 0xba, 0x45, 0x1c, 0x47,
+ 0x29, 0xdf, 0xff, 0x89, 0x60, 0xee, 0x89, 0x1e, 0xc3, 0xb4, 0xb9, 0x0b,
+ 0xc9, 0x7e, 0xd9, 0x15, 0xb0, 0x80, 0x91, 0xbe, 0xb9, 0x43, 0x48, 0x12,
+ 0x86, 0x8e, 0x79, 0x38, 0x4d, 0xce, 0x36, 0x7f, 0xc3, 0xe8, 0xb7, 0xb9,
+ 0x92, 0xbf, 0x27, 0x20, 0x54, 0xc8, 0x05, 0x63, 0x3b, 0xf5, 0x48, 0x1a,
+ 0xa9, 0x04, 0x6c, 0xb6, 0x0e, 0x11, 0xea, 0xf3, 0x59, 0xb9, 0xa6, 0xf6,
+ 0xf8, 0x0b, 0x15, 0xed, 0x30, 0xf9, 0xe4, 0xe5, 0x26, 0x2d, 0xbb, 0xc6,
+ 0x5b, 0x36, 0xbb, 0x73, 0xa6, 0x4f, 0xf5, 0x43, 0x9f, 0xd7, 0xb9, 0x0f,
+ 0xbc, 0x4f, 0x8d, 0xb8, 0xec, 0x1d, 0x42, 0x19, 0x56, 0x37, 0xc4, 0xcb,
+ 0xd0, 0x16, 0x85, 0xff, 0xd3, 0x9b, 0xef, 0xc8, 0x75, 0x37, 0xd1, 0x92,
+ 0xad, 0x21, 0x94, 0x1e, 0x9a, 0xf6, 0x2f, 0x6d, 0x30, 0xba, 0x37, 0xc3,
+ 0xdc, 0x11, 0xe0, 0x79, 0xa4, 0x92, 0x1f, 0xe4, 0xaa, 0x7a, 0x6b, 0x2a,
+ 0xe4, 0x04, 0xb7, 0xf9, 0x86, 0x95, 0xdb, 0xa8, 0xfc, 0x8a, 0x53, 0x21,
+ 0x31, 0x14, 0xf7, 0x40, 0x01, 0x78, 0x4e, 0x73, 0x18, 0xb3, 0x54, 0xd7,
+ 0xa6, 0x93, 0xf0, 0x70, 0x04, 0x1c, 0xe0, 0x2b, 0xef, 0xee, 0xd4, 0x64,
+ 0xa7, 0xd9, 0x9f, 0x81, 0x4f, 0xe5, 0x1e, 0xbe, 0x6e, 0xd2, 0xf6, 0x3a,
+ 0xba, 0xcf, 0x8c, 0x96, 0x2a, 0x3d, 0xf7, 0xe5, 0x5c, 0x59, 0x40, 0x9c,
+ 0xe3, 0xf9, 0x2b, 0x6d, 0x3d, 0xf2, 0x6f, 0x81, 0xd6, 0xab, 0x9c, 0xab,
+ 0xc6, 0xf7, 0x8f, 0xaa, 0xe5, 0x71, 0xe3, 0xc9, 0x8c, 0x1a, 0xeb, 0xc5,
+ 0x87, 0xe7, 0xb0, 0xde, 0x18, 0xba, 0xaa, 0x1e, 0xda, 0x12, 0x32, 0x16,
+ 0x94, 0x3a, 0x6e, 0x4f, 0x84, 0x06, 0x8e, 0x33, 0xf7, 0xfa, 0x35, 0xb8,
+ 0x45, 0xe4, 0x5e, 0x9e, 0x46, 0x05, 0x7a, 0xf7, 0xf4, 0x99, 0xad, 0xb9,
+ 0xdd, 0x55, 0xd9, 0x52, 0x3b, 0x93, 0xe3, 0x9b, 0x54, 0x1b, 0xe6, 0xa9,
+ 0x70, 0xd3, 0x48, 0xf9, 0x3d, 0xdb, 0x88, 0x63, 0x66, 0xa0, 0xab, 0x72,
+ 0x83, 0x6e, 0x8f, 0x78, 0x9d, 0x55, 0x46, 0x21, 0xca, 0x7c, 0xb7, 0x5d,
+ 0x16, 0xe8, 0x66, 0x3b, 0x7b, 0xaa, 0xfe, 0x9c, 0x9c, 0x33, 0xc9, 0xc2,
+ 0xa4, 0x3c, 0x78, 0x97, 0xf3, 0x5b, 0xc2, 0x29, 0x36, 0x98, 0x68, 0x28,
+ 0xfe, 0x0a, 0xae, 0x6f, 0xe5, 0xf7, 0xfb, 0x9d, 0xf8, 0x8c, 0xd9, 0xd0,
+ 0x4d, 0xfe, 0xc7, 0xd0, 0xb0, 0xe3, 0x9c, 0xdb, 0xac, 0x9e, 0x1b, 0x55,
+ 0x7e, 0x24, 0xfe, 0xc4, 0x12, 0xcb, 0xc2, 0xdd, 0x0a, 0xda, 0x31, 0x40,
+ 0x41, 0xb7, 0xfc, 0x3f, 0x6d, 0xe2, 0xd3, 0x8a, 0x0f, 0x21, 0x33, 0x3a,
+ 0xbc, 0xa7, 0x62, 0x18, 0xb3, 0xaf, 0x48, 0xc6, 0xe2, 0xa3, 0xdd, 0x1d,
+ 0x20, 0x62, 0xe4, 0x4b, 0x81, 0x6b, 0x3a, 0xc5, 0xb1, 0x07, 0xe1, 0xf1,
+ 0xe1, 0xba, 0xf6, 0x01, 0xc6, 0xf2, 0xea, 0xc0, 0x97, 0x73, 0x79, 0x19,
+ 0x06, 0xaa, 0x62, 0x42, 0xcb, 0x21, 0x5f, 0x08, 0x97, 0x7d, 0x72, 0xb5,
+ 0x39, 0x4d, 0x99, 0xe3, 0xa2, 0x3f, 0xb9, 0xb4, 0xed, 0xf4, 0x61, 0x35,
+ 0xe1, 0x50, 0xfb, 0x56, 0x7c, 0x35, 0xfd, 0x44, 0x8a, 0x57, 0x22, 0xed,
+ 0x30, 0x33, 0xc3, 0x0b, 0xf1, 0x88, 0xe4, 0x44, 0x46, 0xf5, 0x73, 0x6d,
+ 0x9b, 0x98, 0x88, 0x92, 0xf5, 0x34, 0x85, 0x18, 0x66, 0xef, 0x70, 0xbe,
+ 0x7b, 0xc1, 0x0f, 0x1c, 0x78, 0x2d, 0x42, 0x13, 0x2d, 0x2f, 0x4d, 0x40,
+ 0x8e, 0xe2, 0x6f, 0xe0, 0x04, 0xdb, 0x58, 0xbc, 0x65, 0x80, 0xba, 0xfc,
+ 0x89, 0xee, 0xf3, 0x78, 0xb2, 0xd9, 0x78, 0x93, 0x6d, 0xbf, 0xd4, 0x74,
+ 0x24, 0xf4, 0x5c, 0x37, 0x89, 0x0c, 0x14, 0xd5, 0xbd, 0xc5, 0xfc, 0x37,
+ 0xe8, 0x8b, 0xe0, 0xc5, 0x89, 0xc9, 0x70, 0xb3, 0x76, 0x46, 0xce, 0x0d,
+ 0x7c, 0x3d, 0xa4, 0x5d, 0x02, 0x95, 0x03, 0xba, 0x24, 0xaa, 0xf7, 0xd0,
+ 0x75, 0x35, 0x78, 0x27, 0x9c, 0x6d, 0x2a, 0xef, 0xaa, 0xac, 0x85, 0xef,
+ 0x8d, 0xfc, 0xc0, 0xfc, 0x72, 0x02, 0xf4, 0xa3, 0xd3, 0x87, 0xfc, 0x4d,
+ 0xce, 0x3d, 0xcb, 0xc2, 0x74, 0x5b, 0xb0, 0x83, 0xc5, 0x72, 0x72, 0xd6,
+ 0xa1, 0x67, 0x4d, 0xa1, 0xd6, 0xaa, 0xe7, 0x9b, 0xe7, 0xc0, 0xfd, 0x86,
+ 0x91, 0x08, 0xfa, 0x48, 0x2f, 0x50, 0xce, 0x17, 0xea, 0x1c, 0xe3, 0x90,
+ 0x35, 0xe6, 0x6c, 0xc9, 0x66, 0x7d, 0x51, 0x32, 0x20, 0x0c, 0x2d, 0x4b,
+ 0xa1, 0xbf, 0x78, 0x87, 0xe1, 0x5a, 0x28, 0x0e, 0x9a, 0x85, 0xf6, 0x7e,
+ 0x39, 0x60, 0xbc, 0x64, 0x42, 0x5d, 0xf0, 0x0a, 0xd7, 0x3e, 0xbb, 0xa0,
+ 0x6d, 0x7c, 0xfa, 0x75, 0xee, 0x34, 0x39, 0x23, 0x0e, 0xbd, 0x50, 0x19,
+ 0x7a, 0x2a, 0xb7, 0x17, 0x3a, 0x8b, 0xb7, 0xb6, 0xf4, 0xd8, 0x47, 0x71,
+ 0x6b, 0x21, 0x1b, 0x56, 0xcc, 0xfb, 0x7b, 0x81, 0x99, 0x46, 0x88, 0x23,
+ 0x40, 0x49, 0x66, 0x8b, 0xac, 0x84, 0x16, 0x8a, 0x86, 0xae, 0x38, 0xc4,
+ 0x5b, 0x1f, 0x2b, 0xfa, 0xf2, 0x8b, 0x81, 0xc1, 0x22, 0x61, 0x61, 0x6c,
+ 0x43, 0x16, 0x8c, 0x1d, 0x37, 0xb2, 0xaf, 0x3c, 0x3a, 0x90, 0x33, 0xed,
+ 0xf5, 0x08, 0x78, 0xfd, 0x5a, 0xde, 0xd3, 0x38, 0x6d, 0xd7, 0x1c, 0x23,
+ 0xeb, 0xb4, 0x9b, 0x8e, 0xc2, 0x48, 0x47, 0x8e, 0x84, 0xbb, 0xc4, 0xd0,
+ 0xcc, 0xf9, 0x55, 0x5a, 0x57, 0xb9, 0x99, 0x52, 0x82, 0x21, 0x3b, 0x83,
+ 0xda, 0x8f, 0xa3, 0x88, 0x9c, 0x57, 0xe0, 0x4b, 0xc1, 0xce, 0xbe, 0xd3,
+ 0xea, 0xdd, 0xf2, 0x07, 0xc1, 0x73, 0x6f, 0xc0, 0x5e, 0x8e, 0x85, 0x72,
+ 0xab, 0x2f, 0xa9, 0xac, 0x39, 0xee, 0x05, 0x34, 0x13, 0x16, 0x1b, 0x1c,
+ 0x21, 0x24, 0x41, 0x49, 0x78, 0x87, 0x8b, 0x97, 0x9c, 0x9f, 0xa3, 0xa8,
+ 0xb9, 0xbc, 0xc6, 0xcc, 0xf2, 0xfd, 0x18, 0x2a, 0x46, 0x58, 0x5a, 0x88,
+ 0xa2, 0xb5, 0xcc, 0xd2, 0xda, 0xe1, 0xe3, 0x0d, 0x20, 0x23, 0x2b, 0x2f,
+ 0x47, 0x57, 0x5e, 0x64, 0x87, 0x97, 0x9c, 0xa7, 0xaa, 0xbc, 0xc1, 0xe4,
+ 0xe5, 0xea, 0x0b, 0x16, 0x3b, 0x3c, 0x3e, 0x45, 0x58, 0x63, 0x6a, 0x6f,
+ 0x7c, 0x8c, 0x8d, 0x92, 0x99, 0x9c, 0xad, 0xb5, 0xb7, 0xce, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x16, 0x23, 0x36, 0x4a};
+ static byte test_msg[512] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53,
+ 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+ 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+ 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
+ 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3,
+ 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
+ 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
+ 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
+ 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b,
+ 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3,
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3,
+ 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
+ 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
+
+ int ret;
+ MlDsaKey key[1];
+ whNvmId keyId = WH_KEYID_ERASED;
+ int evictKey = 0;
+
+ /* Initialize keys */
+ ret = wc_MlDsaKey_Init(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize ML-DSA key: %d\n", ret);
+ return ret;
+ }
+ else {
+ ret = wc_dilithium_set_level(key, WC_ML_DSA_44);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to set ML-DSA level: %d\n", ret);
+ }
+ }
+
+ /* make dummy msg */
+ int i = 0;
+ for (i = 0; i < (int)sizeof(test_msg); i++) {
+ test_msg[i] = (byte)i;
+ }
+
+ /* Import the raw public key into the wolfCrypt structure */
+ if (ret == 0) {
+ ret = wc_MlDsaKey_ImportPubRaw(key, ml_dsa_44_pub_key,
+ sizeof(ml_dsa_44_pub_key));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to import ML-DSA public key: %d\n", ret);
+ }
+ }
+ /* Import the key into wolfHSM via the wolfCrypt structure */
+ if (ret == 0) {
+ if (devId == WH_DEV_ID_DMA) {
+ ret = wh_Client_MlDsaImportKeyDma(ctx, key, &keyId, 0, 0, NULL);
+ }
+ else {
+ ret = wh_Client_MlDsaImportKey(ctx, key, &keyId, 0, 0, NULL);
+ }
+ if (ret == WH_ERROR_OK) {
+ evictKey = 1;
+ }
+ else {
+ WH_ERROR_PRINT("Failed to import ML-DSA key: %d\n", ret);
+ }
+ }
+
+ /* Cache the key using DMA and set the key ID */
+ if (ret == 0) {
+ ret = wh_Client_MlDsaSetKeyId(key, keyId);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Failed to set key ID: %d\n", ret);
+ }
+ }
+
+ /* Verify the message signature */
+ if (ret == 0) {
+ int verifyResult;
+ ret = wc_MlDsaKey_VerifyCtx(key, ml_dsa_44_sig, sizeof(ml_dsa_44_sig),
+ NULL, 0, test_msg, sizeof(test_msg),
+ &verifyResult);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Signature did not verify\n");
+ }
+ }
+
+ if (evictKey) {
+ if (WH_ERROR_OK != wh_Client_KeyEvict(ctx, keyId)) {
+ WH_ERROR_PRINT("Failed to evict key\n");
+ }
+ }
+ wc_MlDsaKey_Free(key);
+
+ if (ret == WH_ERROR_OK) {
+ WH_TEST_PRINT("ML-DSA VERIFY ONLY: SUCCESS\n");
+ }
+
+ return ret;
+}
+#endif /* !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
+ !defined(WOLFSSL_NO_ML_DSA_44) && \
+ defined(WOLFHSM_CFG_DMA) */
+
+#if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
+ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \
+ !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && !defined(WOLFSSL_NO_ML_DSA_44)
+/*
+ * ML-DSA exercised through the plain wolfCrypt API (wc_MlDsaKey_MakeKey /
+ * SignCtx / VerifyCtx), which dispatches through the cryptocb. PQC keygen/
+ * sign/verify is handled by both the normal and DMA cryptocbs, so the entry
+ * point loops this over every devId. Complements the wh_Client_MlDsa*
+ * direct-API tests above.
+ */
+static int whTest_CryptoMlDsaWolfCryptImpl(whClientContext* ctx, int devId)
+{
+ int ret = 0;
+ int verified = 0;
+ int sigLenInt = 0;
+ MlDsaKey key[1];
+ WC_RNG rng[1];
+ byte msg[] = "Test message for ML-DSA signing";
+ byte sig[DILITHIUM_MAX_SIG_SIZE];
+ word32 sigSz = sizeof(sig);
+
+ (void)ctx;
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_MlDsaKey_Init(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to initialize ML-DSA key: %d\n", ret);
+ (void)wc_FreeRng(rng);
+ return ret;
+ }
+
+ if (ret == 0) {
+ ret = wc_MlDsaKey_SetParams(key, WC_ML_DSA_44);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to set ML-DSA params: %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_MlDsaKey_MakeKey(key, rng);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to generate ML-DSA key: %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_MlDsaKey_GetSigLen(key, &sigLenInt);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to get ML-DSA signature length: %d\n", ret);
+ }
+ else {
+ sigSz = (word32)sigLenInt;
+ }
+ }
+ if (ret == 0) {
+ ret = wc_MlDsaKey_SignCtx(key, NULL, 0, sig, &sigSz, msg, sizeof(msg),
+ rng);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to sign with ML-DSA: %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_MlDsaKey_VerifyCtx(key, sig, sigSz, NULL, 0, msg, sizeof(msg),
+ &verified);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to verify ML-DSA signature: %d\n", ret);
+ }
+ else if (!verified) {
+ WH_ERROR_PRINT("ML-DSA signature verification failed\n");
+ ret = -1;
+ }
+ }
+ /* Tamper check: a corrupted signature must not verify. */
+ if (ret == 0) {
+ sig[0] ^= 1;
+ ret = wc_MlDsaKey_VerifyCtx(key, sig, sigSz, NULL, 0, msg, sizeof(msg),
+ &verified);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to verify modified ML-DSA signature: %d\n",
+ ret);
+ }
+ else if (verified) {
+ WH_ERROR_PRINT("ML-DSA verified a tampered signature\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("ML-DSA WOLFCRYPT DEVID=0x%X SUCCESS\n", devId);
+ }
+
+ wc_MlDsaKey_Free(key);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+/* Exercises wh_Client_MlDsaSign with an undersized output buffer: must return
+ * WH_ERROR_BUFFER_SIZE and report a required length greater than the buffer. */
+static int _whTest_CryptoMlDsaBufferTooSmall(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret;
+ MlDsaKey key[1];
+ const byte msg[] = "ml-dsa buf size test";
+ uint8_t small_sig[16] = {0};
+ uint8_t full_sig[DILITHIUM_MAX_SIG_SIZE] = {0};
+ word32 small_buf_sz = (word32)sizeof(small_sig);
+ word32 sig_len;
+
+ ret = wc_MlDsaKey_Init(key, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_MlDsaKey_Init %d\n", ret);
+ return ret;
+ }
+
+ ret = wh_Client_MlDsaMakeExportKey(ctx, WC_ML_DSA_44, 0, key);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_MlDsaMakeExportKey %d\n", ret);
+ goto done;
+ }
+
+ sig_len = small_buf_sz;
+ ret = wh_Client_MlDsaSign(ctx, msg, (word32)sizeof(msg), small_sig,
+ &sig_len, key, NULL, 0, WC_HASH_TYPE_NONE);
+ if (ret != WH_ERROR_BUFFER_SIZE) {
+ WH_ERROR_PRINT(
+ "MlDsaSign small buf expected WH_ERROR_BUFFER_SIZE, got %d\n", ret);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+ if (sig_len <= small_buf_sz) {
+ WH_ERROR_PRINT(
+ "MlDsaSign small buf reported size %u not greater than %u\n",
+ (unsigned)sig_len, (unsigned)small_buf_sz);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+
+ sig_len = (word32)sizeof(full_sig);
+ ret = wh_Client_MlDsaSign(ctx, msg, (word32)sizeof(msg), full_sig,
+ &sig_len, key, NULL, 0, WC_HASH_TYPE_NONE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("MlDsaSign full buf failed: %d\n", ret);
+ goto done;
+ }
+
+ WH_TEST_PRINT("ML-DSA buffer-size DEVID=0x%X SUCCESS\n", devId);
+ ret = 0;
+
+done:
+ wc_MlDsaKey_Free(key);
+ return ret;
+}
+#endif /* make/sign/verify && ML_DSA_44 */
+
+int whTest_Crypto_MlDsa(whClientContext* ctx)
+{
+#if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
+ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \
+ !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && !defined(WOLFSSL_NO_ML_DSA_44)
+ int i, devId;
+
+ /* Plain wolfCrypt-API ML-DSA dispatches through the cryptocb; PQC is
+ * handled by both the normal and DMA cryptocbs, so loop over every devId.
+ * The wh_Client_MlDsa* direct-API tests below run on their own devIds. */
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(whTest_CryptoMlDsaWolfCryptImpl(ctx, devId));
+ }
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaClient(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaExportPublicKey(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaBufferTooSmall(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaDmaClient(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaExportPublicKeyDma(ctx));
+#endif
+#endif
+#if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
+ !defined(WOLFSSL_NO_ML_DSA_44) && \
+ defined(WOLFHSM_CFG_DMA)
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaVerifyOnlyDma(ctx));
+#endif
+ (void)ctx;
+ return 0;
+}
+
+#endif /* HAVE_DILITHIUM */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_rng.c b/test-refactor/client-server/wh_test_crypto_rng.c
new file mode 100644
index 000000000..373834dcd
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_rng.c
@@ -0,0 +1,381 @@
+/*
+ * 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_rng.c
+ *
+ * RNG round-trips: synchronous (wolfCrypt API), async non-DMA, and async DMA.
+ */
+
+#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_client.h"
+#include "wolfhsm/wh_client_crypto.h"
+#include "wolfhsm/wh_message_crypto.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+#define WH_TEST_RNG_LIL 7
+#define WH_TEST_RNG_MED 1024
+#define WH_TEST_RNG_BIG (WOLFHSM_CFG_COMM_DATA_LEN * 2)
+
+/* Returns 0 if buf appears to contain non-trivial data (not all zero), -1 on
+ * the all-zero case which would suggest the response was never written. */
+static int whTest_RngBufNonZero(const uint8_t* buf, uint32_t len)
+{
+ uint32_t i;
+ for (i = 0; i < len; i++) {
+ if (buf[i] != 0) {
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int whTest_CryptoRngImpl(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+ uint8_t lil[WH_TEST_RNG_LIL];
+ uint8_t med[WH_TEST_RNG_MED];
+ uint8_t big[WH_TEST_RNG_BIG];
+
+ (void)ctx;
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ }
+ else {
+ int freeRet;
+ ret = wc_RNG_GenerateBlock(rng, lil, sizeof(lil));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret);
+ }
+ else {
+ ret = wc_RNG_GenerateBlock(rng, med, sizeof(med));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret);
+ }
+ else {
+ ret = wc_RNG_GenerateBlock(rng, big, sizeof(big));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret);
+ }
+ else if (memcmp(lil, med, sizeof(lil)) == 0) {
+ /* The prefixes of two successive independent RNG calls
+ * must not match. A collision here indicates a stuck RNG */
+ WH_ERROR_PRINT("RNG: successive calls produced identical "
+ "prefix\n");
+ ret = -1;
+ }
+ }
+ }
+ /* Always free the RNG if InitRng succeeded, regardless of which (if
+ * any) GenerateBlock call failed. */
+ freeRet = wc_FreeRng(rng);
+ if (freeRet != 0) {
+ WH_ERROR_PRINT("Failed to wc_FreeRng %d\n", freeRet);
+ if (ret == 0) {
+ ret = freeRet;
+ }
+ }
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("RNG DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Direct exercise of the async non-DMA RNG primitives. */
+static int _whTest_CryptoRngAsync(whClientContext* ctx)
+{
+ int ret = WH_ERROR_OK;
+ uint8_t small[64];
+ uint8_t big[WOLFHSM_CFG_COMM_DATA_LEN * 2];
+ uint32_t got;
+
+ /* Case A: small Request -> poll Response */
+ if (ret == 0) {
+ memset(small, 0, sizeof(small));
+ ret = wh_Client_RngGenerateRequest(ctx, sizeof(small));
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Async RNG: Request(small) failed %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ got = sizeof(small);
+ do {
+ ret = wh_Client_RngGenerateResponse(ctx, small, &got);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Async RNG: Response(small) failed %d\n", ret);
+ }
+ else if (got != sizeof(small)) {
+ WH_ERROR_PRINT("Async RNG: short read got=%u want=%u\n",
+ (unsigned)got, (unsigned)sizeof(small));
+ ret = -1;
+ }
+ else if (whTest_RngBufNonZero(small, sizeof(small)) != 0) {
+ WH_ERROR_PRINT("Async RNG: small buffer all zeros\n");
+ ret = -1;
+ }
+ }
+
+ /* Case B: max-inline-size Request -> Response in a single round trip */
+ if (ret == 0) {
+ uint32_t cap = (uint32_t)WH_MESSAGE_CRYPTO_RNG_MAX_INLINE_SZ;
+ memset(big, 0, cap);
+ ret = wh_Client_RngGenerateRequest(ctx, cap);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Async RNG: Request(max) failed %d\n", ret);
+ }
+ if (ret == 0) {
+ got = cap;
+ do {
+ ret = wh_Client_RngGenerateResponse(ctx, big, &got);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret == 0 && got != cap) {
+ WH_ERROR_PRINT("Async RNG: max read short got=%u want=%u\n",
+ (unsigned)got, (unsigned)cap);
+ ret = -1;
+ }
+ else if (ret == 0 && whTest_RngBufNonZero(big, cap) != 0) {
+ WH_ERROR_PRINT("Async RNG: max buffer all zeros\n");
+ ret = -1;
+ }
+ }
+ }
+
+ /* Case C: caller-driven chunking to fill a buffer larger than the per-call
+ * inline capacity. */
+ if (ret == 0) {
+ uint32_t cap = (uint32_t)WH_MESSAGE_CRYPTO_RNG_MAX_INLINE_SZ;
+ uint32_t total = (uint32_t)sizeof(big);
+ uint32_t consumed = 0;
+
+ memset(big, 0, total);
+ while (ret == 0 && consumed < total) {
+ uint32_t want = total - consumed;
+ if (want > cap) {
+ want = cap;
+ }
+ ret = wh_Client_RngGenerateRequest(ctx, want);
+ if (ret == 0) {
+ got = want;
+ do {
+ ret = wh_Client_RngGenerateResponse(ctx, big + consumed,
+ &got);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ if (got == 0 || got > want) {
+ WH_ERROR_PRINT(
+ "Async RNG: bad chunk reply got=%u want=%u\n",
+ (unsigned)got, (unsigned)want);
+ ret = -1;
+ }
+ else {
+ consumed += got;
+ }
+ }
+ }
+ if (ret == 0 && whTest_RngBufNonZero(big, total) != 0) {
+ WH_ERROR_PRINT("Async RNG: chunked buffer all zeros\n");
+ ret = -1;
+ }
+ }
+
+ /* Case D: oversize request must be rejected without sending. */
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateRequest(
+ ctx, (uint32_t)WH_MESSAGE_CRYPTO_RNG_MAX_INLINE_SZ + 1u);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG: oversize Request returned %d (want BADARGS)\n", rc);
+ ret = -1;
+ }
+ }
+
+ /* Case E: zero-size request must be rejected. */
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateRequest(ctx, 0);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG: zero-size Request returned %d (want BADARGS)\n",
+ rc);
+ ret = -1;
+ }
+ }
+
+ /* Case F: NULL ctx rejection on both halves. */
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateRequest(NULL, 16);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG: NULL ctx Request returned %d (want BADARGS)\n", rc);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ int rc;
+ got = 16;
+ rc = wh_Client_RngGenerateResponse(NULL, small, &got);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG: NULL ctx Response returned %d (want BADARGS)\n",
+ rc);
+ ret = -1;
+ }
+ }
+
+ /* Case G: NULL inout_size rejection. */
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateResponse(ctx, small, NULL);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Async RNG: NULL inout_size Response returned %d "
+ "(want BADARGS)\n",
+ rc);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("RNG ASYNC SUCCESS\n");
+ }
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+/* Direct exercise of the async DMA RNG primitives. */
+static int _whTest_CryptoRngDmaAsync(whClientContext* ctx)
+{
+ int ret = WH_ERROR_OK;
+ /* DMA bypasses the comm buffer so we can request more than COMM_DATA_LEN
+ * in a single round trip. */
+ uint8_t big[WOLFHSM_CFG_COMM_DATA_LEN * 2];
+
+ /* Case A: basic DMA Request -> Response */
+ if (ret == 0) {
+ memset(big, 0, sizeof(big));
+ ret = wh_Client_RngGenerateDmaRequest(ctx, big, sizeof(big));
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Async RNG DMA: Request failed %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_RngGenerateDmaResponse(ctx);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Async RNG DMA: Response failed %d\n", ret);
+ }
+ else if (whTest_RngBufNonZero(big, sizeof(big)) != 0) {
+ WH_ERROR_PRINT("Async RNG DMA: buffer all zeros\n");
+ ret = -1;
+ }
+ }
+
+ /* Case B: small DMA request still works (no chunking semantics). */
+ if (ret == 0) {
+ uint8_t small[32];
+ memset(small, 0, sizeof(small));
+ ret = wh_Client_RngGenerateDmaRequest(ctx, small, sizeof(small));
+ if (ret == 0) {
+ do {
+ ret = wh_Client_RngGenerateDmaResponse(ctx);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0 && whTest_RngBufNonZero(small, sizeof(small)) != 0) {
+ WH_ERROR_PRINT("Async RNG DMA: small buffer all zeros\n");
+ ret = -1;
+ }
+ }
+
+ /* Case C: input validation. */
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateDmaRequest(NULL, big, sizeof(big));
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG DMA: NULL ctx returned %d (want BADARGS)\n", rc);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateDmaRequest(ctx, NULL, sizeof(big));
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG DMA: NULL out returned %d (want BADARGS)\n", rc);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateDmaRequest(ctx, big, 0);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG DMA: zero size returned %d (want BADARGS)\n", rc);
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ int rc = wh_Client_RngGenerateDmaResponse(NULL);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT(
+ "Async RNG DMA: Response NULL ctx returned %d (want BADARGS)\n",
+ rc);
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("RNG DMA ASYNC SUCCESS\n");
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+int whTest_Crypto_Rng(whClientContext* ctx)
+{
+ int i, devId;
+
+ /* Synchronous wolfCrypt RNG dispatches through the cryptocb, so run it on
+ * every devId to cover both the normal and DMA server transports. */
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(whTest_CryptoRngImpl(ctx, devId));
+ }
+ /* The explicit request/response primitives are transport-specific
+ * (comm-buffer vs DMA), not devId-routed -- run each once. */
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRngAsync(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRngDmaAsync(ctx));
+#endif
+ return 0;
+}
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_rsa.c b/test-refactor/client-server/wh_test_crypto_rsa.c
new file mode 100644
index 000000000..dfcc896a0
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_rsa.c
@@ -0,0 +1,397 @@
+/*
+ * 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_rsa.c
+ *
+ * RSA encrypt/decrypt round-trips routed through the server via WH_DEV_ID:
+ * ephemeral key, server-exported key, and server-cached key 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/rsa.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"
+
+#ifndef NO_RSA
+
+#define RSA_KEY_BITS 2048
+#define RSA_KEY_BYTES (RSA_KEY_BITS / 8)
+#define RSA_EXPONENT WC_RSA_EXPONENT
+#define WH_TEST_RSA_PLAINTEXT "mytextisbigplain"
+
+static int _whTest_CryptoRsa(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ WC_RNG rng[1];
+ RsaKey rsa[1];
+ char plainText[sizeof(WH_TEST_RSA_PLAINTEXT)] = WH_TEST_RSA_PLAINTEXT;
+ char cipherText[RSA_KEY_BYTES];
+ char finalText[RSA_KEY_BYTES];
+ whKeyId keyId = WH_KEYID_ERASED;
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ /* Using ephemeral key */
+ memset(cipherText, 0, sizeof(cipherText));
+ memset(finalText, 0, sizeof(finalText));
+ ret = wc_InitRsaKey_ex(rsa, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRsaKey_ex %d\n", ret);
+ }
+ else {
+ ret = wc_MakeRsaKey(rsa, RSA_KEY_BITS, RSA_EXPONENT, rng);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_MakeRsaKey %d\n", ret);
+ }
+ else {
+ ret = wc_RsaPublicEncrypt((byte*)plainText, sizeof(plainText),
+ (byte*)cipherText, sizeof(cipherText),
+ rsa, rng);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to wc_RsaPublicEncrypt %d\n", ret);
+ }
+ else {
+ ret = wc_RsaPrivateDecrypt((byte*)cipherText, ret,
+ (byte*)finalText, sizeof(finalText),
+ rsa);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to wc_RsaPrivateDecrypt %d\n", ret);
+ }
+ else {
+ ret = 0;
+ if (memcmp(plainText, finalText, sizeof(plainText)) != 0) {
+ WH_ERROR_PRINT("Failed to match\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ (void)wc_FreeRsaKey(rsa);
+ }
+
+ if (ret == 0) {
+ /* Using client export key */
+ memset(cipherText, 0, sizeof(cipherText));
+ memset(finalText, 0, sizeof(finalText));
+ ret = wc_InitRsaKey_ex(rsa, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRsaKey_ex %d\n", ret);
+ }
+ else {
+ ret = wh_Client_RsaMakeExportKey(ctx, RSA_KEY_BITS, RSA_EXPONENT,
+ rsa);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make exported key %d\n", ret);
+ }
+ else {
+ ret = wc_RsaPublicEncrypt((byte*)plainText, sizeof(plainText),
+ (byte*)cipherText, sizeof(cipherText),
+ rsa, rng);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to encrypt %d\n", ret);
+ }
+ else {
+ ret = wc_RsaPrivateDecrypt((byte*)cipherText, ret,
+ (byte*)finalText,
+ sizeof(finalText), rsa);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to decrypt %d\n", ret);
+ }
+ else {
+ ret = 0;
+ if (memcmp(plainText, finalText, sizeof(plainText)) !=
+ 0) {
+ WH_ERROR_PRINT("Failed to match\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ (void)wc_FreeRsaKey(rsa);
+ }
+ }
+
+ if (ret == 0) {
+ /* Using keyCache key */
+ memset(cipherText, 0, sizeof(cipherText));
+ memset(finalText, 0, sizeof(finalText));
+ ret = wh_Client_RsaMakeCacheKey(
+ ctx, RSA_KEY_BITS, RSA_EXPONENT, &keyId,
+ WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make cached key %d\n", ret);
+ }
+ else {
+ ret = wc_InitRsaKey_ex(rsa, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRsaKey_ex %d\n", ret);
+ }
+ else {
+ ret = wh_Client_RsaSetKeyId(rsa, keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_SetKeyIdRsa %d\n", ret);
+ }
+ else {
+ ret = wh_Client_RsaGetKeyId(rsa, &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_GetKeyIdRsa %d\n", ret);
+ }
+ else {
+ ret = wc_RsaPublicEncrypt(
+ (byte*)plainText, sizeof(plainText),
+ (byte*)cipherText, sizeof(cipherText), rsa, rng);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to encrypt %d\n", ret);
+ }
+ else {
+ ret = wc_RsaPrivateDecrypt(
+ (byte*)cipherText, ret, (byte*)finalText,
+ sizeof(finalText), rsa);
+ if (ret < 0) {
+ WH_ERROR_PRINT("Failed to decrypt %d\n", ret);
+ }
+ else {
+ ret = 0;
+ if (memcmp(plainText, finalText,
+ sizeof(plainText)) != 0) {
+ WH_ERROR_PRINT("Failed to match\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ }
+ (void)wc_FreeRsaKey(rsa);
+ }
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("RSA DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Cache a NONEXPORTABLE RSA keypair, export only the public half via
+ * wh_Client_RsaExportPublicKey, then encrypt client-side with the exported
+ * pub and decrypt server-side using the cached private. A successful round
+ * trip proves the exported public key matches the cached private. */
+static int _whTest_CryptoRsaExportPublicKey(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret = WH_ERROR_OK;
+ WC_RNG rng[1];
+ RsaKey rsaPub[1];
+ RsaKey rsaFull[1];
+ char plainText[sizeof(WH_TEST_RSA_PLAINTEXT)] = WH_TEST_RSA_PLAINTEXT;
+ char cipherText[RSA_KEY_BYTES];
+ char finalText[RSA_KEY_BYTES];
+ whKeyId keyId = WH_KEYID_ERASED;
+ uint8_t denyBuf[2048];
+ uint16_t denyLen = sizeof(denyBuf);
+ int encLen;
+ int decLen;
+
+ memset(cipherText, 0, sizeof(cipherText));
+ memset(finalText, 0, sizeof(finalText));
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wh_Client_RsaMakeCacheKey(
+ ctx, RSA_KEY_BITS, RSA_EXPONENT, &keyId,
+ WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT |
+ WH_NVM_FLAGS_NONEXPORTABLE,
+ 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached RSA key %d\n", ret);
+ }
+
+ /* Full export must be denied by the NONEXPORTABLE policy. */
+ if (ret == 0) {
+ int denyRet =
+ wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf, &denyLen);
+ if (denyRet != WH_ERROR_ACCESS) {
+ WH_ERROR_PRINT(
+ "NONEXPORTABLE RSA full export was not denied: %d\n", denyRet);
+ ret = -1;
+ }
+ }
+
+ /* Public-only export must succeed and yield a usable public key. */
+ if (ret == 0) {
+ ret = wc_InitRsaKey_ex(rsaPub, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wh_Client_RsaExportPublicKey(ctx, keyId, rsaPub, 0, NULL);
+ if (ret != 0) {
+ WH_ERROR_PRINT("wh_Client_RsaExportPublicKey failed %d\n", ret);
+ }
+ else if (rsaPub->type != RSA_PUBLIC) {
+ WH_ERROR_PRINT(
+ "Exported RSA key is not public-only (type=%d)\n",
+ rsaPub->type);
+ ret = -1;
+ }
+ else {
+ encLen = wc_RsaPublicEncrypt(
+ (byte*)plainText, sizeof(plainText), (byte*)cipherText,
+ sizeof(cipherText), rsaPub, rng);
+ if (encLen < 0) {
+ WH_ERROR_PRINT("PublicEncrypt with exported pub failed %d\n",
+ encLen);
+ ret = encLen;
+ }
+ }
+ (void)wc_FreeRsaKey(rsaPub);
+ }
+ }
+
+ /* Server-side decrypt with the cached private completes the round-trip. */
+ if (ret == 0) {
+ ret = wc_InitRsaKey_ex(rsaFull, NULL, devId);
+ if (ret == 0) {
+ ret = wh_Client_RsaSetKeyId(rsaFull, keyId);
+ }
+ if (ret == 0) {
+ decLen = wc_RsaPrivateDecrypt((byte*)cipherText, encLen,
+ (byte*)finalText, sizeof(finalText),
+ rsaFull);
+ if (decLen < 0) {
+ WH_ERROR_PRINT("HSM PrivateDecrypt failed %d\n", decLen);
+ ret = decLen;
+ }
+ else if (memcmp(plainText, finalText, sizeof(plainText)) != 0) {
+ WH_ERROR_PRINT("RSA round-trip plaintext mismatch\n");
+ ret = -1;
+ }
+ }
+ (void)wc_FreeRsaKey(rsaFull);
+ }
+
+ if (!WH_KEYID_ISERASED(keyId)) {
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+ (void)wc_FreeRng(rng);
+
+ if (ret == 0) {
+ WH_TEST_PRINT("RSA EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Exercises wh_Client_RsaFunction with an undersized output buffer. */
+static int _whTest_CryptoRsaBufferTooSmall(whClientContext* ctx)
+{
+ const int devId = WH_DEV_ID;
+ const int rsa_key_bits = 2048;
+ const int rsa_key_bytes = rsa_key_bits / 8;
+ int ret;
+ RsaKey rsa[1];
+ const byte plain[] = "rsa buf size test";
+ uint8_t small_out[16] = {0};
+ uint8_t full_out[256 /* RSA-2048 modulus */] = {0};
+ uint16_t out_len;
+
+ ret = wc_InitRsaKey_ex(rsa, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRsaKey_ex %d\n", ret);
+ return ret;
+ }
+
+ ret = wh_Client_RsaMakeExportKey(ctx, rsa_key_bits, WC_RSA_EXPONENT, rsa);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_RsaMakeExportKey %d\n", ret);
+ goto done;
+ }
+
+ out_len = (uint16_t)sizeof(small_out);
+ ret = wh_Client_RsaFunction(ctx, rsa, RSA_PUBLIC_ENCRYPT, plain,
+ (uint16_t)sizeof(plain), small_out,
+ &out_len);
+ /* Server's wc_RsaFunction pre-checks the buffer and returns
+ RSA_BUFFER_E. */
+ if (ret != WH_ERROR_BUFFER_SIZE && ret != RSA_BUFFER_E) {
+ WH_ERROR_PRINT(
+ "RsaFunction small buf expected WH_ERROR_BUFFER_SIZE or "
+ "RSA_BUFFER_E, got %d\n",
+ ret);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+
+ out_len = (uint16_t)sizeof(full_out);
+ ret = wh_Client_RsaFunction(ctx, rsa, RSA_PUBLIC_ENCRYPT, plain,
+ (uint16_t)sizeof(plain), full_out,
+ &out_len);
+ if (ret != 0) {
+ WH_ERROR_PRINT("RsaFunction full buf failed: %d\n", ret);
+ goto done;
+ }
+ if (out_len != (uint16_t)rsa_key_bytes) {
+ WH_ERROR_PRINT("RsaFunction full buf size %u, expected %d\n",
+ (unsigned)out_len, rsa_key_bytes);
+ ret = WH_TEST_FAIL;
+ goto done;
+ }
+
+ WH_TEST_PRINT("RSA buffer-size DEVID=0x%X SUCCESS\n", devId);
+ ret = 0;
+
+done:
+ (void)wc_FreeRsaKey(rsa);
+ return ret;
+}
+
+int whTest_Crypto_Rsa(whClientContext* ctx)
+{
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsa(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsaExportPublicKey(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsaBufferTooSmall(ctx));
+ return 0;
+}
+
+#endif /* !NO_RSA */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/client-server/wh_test_crypto_sha.c b/test-refactor/client-server/wh_test_crypto_sha.c
new file mode 100644
index 000000000..02e257a01
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_sha.c
@@ -0,0 +1,2446 @@
+/*
+ * 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_sha.c
+ *
+ * SHA-224 / 256 / 384 / 512 routed through the server via WH_DEV_ID.
+ * Each hash size has four variants:
+ * whTest_CryptoSha sync wolfCrypt API single+multi block
+ * whTest_CryptoShaLargeInput sync test with input larger than the
+ * server transport buffer (chunked send)
+ * whTest_CryptoShaAsync direct exercise of wh_Client_Sha*Request
+ * / Response primitives
+ * whTest_CryptoShaDmaAsync same, via the DMA messaging path
+ *
+ * The legacy implementations are preserved verbatim as static *Impl
+ * helpers; thin public wrappers above each family own the WC_RNG lifecycle
+ * so the public API matches the rest of the test-refactor suite.
+ */
+
+#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/sha256.h"
+#include "wolfssl/wolfcrypt/sha512.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 "wolfhsm/wh_message_crypto.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+#ifndef NO_SHA256
+static int whTest_CryptoSha256Impl(whClientContext* ctx, int devId, WC_RNG* rng)
+{
+ (void)ctx; (void)rng; /* Not currently used */
+ int ret = WH_ERROR_OK;
+ wc_Sha256 sha256[1];
+ uint8_t out[WC_SHA256_DIGEST_SIZE];
+ /* Vector exactly one block size in length */
+ const char inOne[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ const uint8_t expectedOutOne[WC_SHA256_DIGEST_SIZE] = {
+ 0xff, 0xe0, 0x54, 0xfe, 0x7a, 0xe0, 0xcb, 0x6d, 0xc6, 0x5c, 0x3a,
+ 0xf9, 0xb6, 0x1d, 0x52, 0x09, 0xf4, 0x39, 0x85, 0x1d, 0xb4, 0x3d,
+ 0x0b, 0xa5, 0x99, 0x73, 0x37, 0xdf, 0x15, 0x46, 0x68, 0xeb};
+ /* Vector long enough to span a SHA256 block */
+ const char inMulti[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"
+ "YZ1234567890abcdefghi";
+ const uint8_t expectedOutMulti[WC_SHA256_DIGEST_SIZE] = {
+ 0x7b, 0x54, 0x45, 0x86, 0xb3, 0x51, 0x43, 0x4e, 0xf6, 0x83, 0xdb,
+ 0x78, 0x1d, 0x94, 0xd6, 0xb0, 0x36, 0x9b, 0x36, 0x56, 0x93, 0x0e,
+ 0xf4, 0x47, 0x9b, 0xae, 0xff, 0xfa, 0x1f, 0x36, 0x38, 0x64};
+
+ /* Initialize SHA256 structure */
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha256 on devId 0x%X: %d\n", devId,
+ ret);
+ } else {
+ /* Test SHA256 on a single block worth of data. Should trigger a server
+ * transaction */
+ ret = wc_Sha256Update(sha256,
+ (const byte*)inOne,
+ WC_SHA256_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha256Update %d\n", ret);
+ } else {
+ /* Finalize should trigger a server transaction with empty buffer */
+ ret = wc_Sha256Final(sha256, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha256Final %d\n", ret);
+ } else {
+ /* Compare the computed hash with the expected output */
+ if (memcmp(out, expectedOutOne, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA256 hash does not match expected.\n");
+ ret = -1;
+ }
+ memset(out, 0, WC_SHA256_DIGEST_SIZE);
+ }
+ }
+ /* Reset state for multi block test */
+ (void)wc_Sha256Free(sha256);
+ }
+ if (ret == 0) {
+ /* Multiblock test */
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha256 for devId 0x%X: %d\n",
+ devId, ret);
+ } else {
+ /* Update with a non-block aligned length. Will not trigger server
+ * transaction */
+ ret = wc_Sha256Update(sha256,
+ (const byte*)inMulti,
+ 1);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha256Update (first) %d\n", ret);
+ } else {
+ /* Update with a full block, will trigger block to be sent to
+ * server and one additional byte to be buffered */
+ ret = wc_Sha256Update(sha256,
+ (const byte*)inMulti + 1,
+ WC_SHA256_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha256Update (mid) %d\n", ret);
+ } else {
+ /* Update with the remaining data, should not trigger server
+ * transaction */
+ ret = wc_Sha256Update(sha256,
+ (const byte*)inMulti + 1 + WC_SHA256_BLOCK_SIZE,
+ strlen(inMulti) - 1 - WC_SHA256_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha256Update (last) %d\n",
+ ret);
+ } else {
+ /* Finalize should trigger a server transaction on the
+ * remaining partial buffer */
+ ret = wc_Sha256Final(sha256, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha256Final %d\n",
+ ret);
+ } else {
+ /* Compare the computed hash with the expected
+ * output */
+ if (memcmp(out, expectedOutMulti,
+ WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA256 hash does not match the "
+ "expected output.\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ }
+ (void)wc_Sha256Free(sha256);
+ }
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA256 DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Hash a buffer with a pure-software SHA256 (no devId) so we can compare. */
+static int whTest_Sha256Reference(const uint8_t* in, uint32_t inLen,
+ uint8_t out[WC_SHA256_DIGEST_SIZE])
+{
+ wc_Sha256 sw[1];
+ int ret = wc_InitSha256_ex(sw, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_Sha256Update(sw, in, inLen);
+ }
+ if (ret == 0) {
+ ret = wc_Sha256Final(sw, out);
+ }
+ (void)wc_Sha256Free(sw);
+ return ret;
+}
+
+/* Drive the new multi-block wire format through the blocking wrapper. Tests:
+ * - large multi-request input,
+ * - exact per-call inline capacity boundary,
+ * - one-byte-over-capacity boundary (forces a tail in the client buffer),
+ * - non-aligned chunked update sequence.
+ *
+ * Buffer is sized to comfortably exceed the per-call inline capacity at any
+ * reasonable comm-buffer size. We use a static buffer to keep stack pressure
+ * low under ASAN. */
+static uint8_t
+ whTest_Sha256BigBuf[2 *
+ (WH_MESSAGE_CRYPTO_SHA256_MAX_INLINE_UPDATE_SZ + 64u)];
+
+static int whTest_CryptoSha256LargeInputImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha256 sha256[1];
+ uint8_t out[WC_SHA256_DIGEST_SIZE];
+ uint8_t ref[WC_SHA256_DIGEST_SIZE];
+ uint8_t* buf = whTest_Sha256BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha256BigBuf);
+ uint32_t i;
+
+ (void)ctx;
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)(i & 0xff);
+ }
+
+ /* Test 1: large single-update */
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha256Update(sha256, buf, BUFSZ);
+ }
+ if (ret == 0) {
+ ret = wc_Sha256Final(sha256, out);
+ }
+ (void)wc_Sha256Free(sha256);
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA256 large input mismatch\n");
+ ret = -1;
+ }
+
+ /* Test 2: exactly the per-call inline capacity in one shot */
+ if (ret == 0) {
+ const uint32_t cap = WH_MESSAGE_CRYPTO_SHA256_MAX_INLINE_UPDATE_SZ;
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha256Update(sha256, buf, cap);
+ }
+ if (ret == 0) {
+ ret = wc_Sha256Final(sha256, out);
+ }
+ (void)wc_Sha256Free(sha256);
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, cap, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA256 capacity-boundary mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 3: capacity + 1 byte (one full request, then a tail buffered) */
+ if (ret == 0) {
+ const uint32_t cap1 =
+ WH_MESSAGE_CRYPTO_SHA256_MAX_INLINE_UPDATE_SZ + 1u;
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha256Update(sha256, buf, cap1);
+ }
+ if (ret == 0) {
+ ret = wc_Sha256Final(sha256, out);
+ }
+ (void)wc_Sha256Free(sha256);
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, cap1, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA256 capacity+1 mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 4: non-aligned chunk stress test */
+ if (ret == 0) {
+ const uint32_t chunks[] = {13, 17, 1280, 41, 1};
+ const size_t nChunks = sizeof(chunks) / sizeof(chunks[0]);
+ uint32_t total = 0;
+ size_t k;
+ for (k = 0; k < nChunks; k++) {
+ total += chunks[k];
+ }
+ if (total > BUFSZ) {
+ WH_ERROR_PRINT("test buffer too small for chunked stress test\n");
+ ret = -1;
+ }
+ if (ret == 0) {
+ uint32_t off = 0;
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ for (k = 0; ret == 0 && k < nChunks; k++) {
+ ret = wc_Sha256Update(sha256, buf + off, chunks[k]);
+ off += chunks[k];
+ }
+ if (ret == 0) {
+ ret = wc_Sha256Final(sha256, out);
+ }
+ (void)wc_Sha256Free(sha256);
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, total, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA256 chunked stress mismatch\n");
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA256 LARGE-INPUT DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Direct exercise of the new async non-DMA SHA256 primitives. */
+static int whTest_CryptoSha256AsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha256 sha256[1];
+ uint8_t out[WC_SHA256_DIGEST_SIZE];
+ uint8_t ref[WC_SHA256_DIGEST_SIZE];
+ /* Use the same large static buffer as the LargeInput test. */
+ uint8_t* buf = whTest_Sha256BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha256BigBuf);
+ uint32_t i;
+ bool sent;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 31u + 7u) & 0xff);
+ }
+
+ /* Case A: basic UpdateRequest -> UpdateResponse -> Final */
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret == 0) {
+ sent = false;
+ ret = wh_Client_Sha256UpdateRequest(ctx, sha256, buf, 256, &sent);
+ }
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha256UpdateResponse(ctx, sha256);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha256FinalRequest(ctx, sha256);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha256FinalResponse(ctx, sha256, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, 256, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA256 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: pure-buffer-fill update (sent must be false), then finalize */
+ if (ret == 0) {
+ (void)wc_Sha256Free(sha256);
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ }
+ if (ret == 0) {
+ sent = true; /* expect to be cleared to false */
+ ret = wh_Client_Sha256UpdateRequest(ctx, sha256, buf, 10, &sent);
+ if (ret == 0 && sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA256: expected sent==false on small update\n");
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha256FinalRequest(ctx, sha256);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha256FinalResponse(ctx, sha256, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, 10, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA256 case B mismatch\n");
+ ret = -1;
+ }
+
+ /* Case C: multi-round async updates that span more than the per-call
+ * inline capacity (forces multiple Request/Response pairs). */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ (void)wc_Sha256Free(sha256);
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ /* arbitrary, 70% of max inline */
+ uint32_t chunk =
+ (WH_MESSAGE_CRYPTO_SHA256_MAX_INLINE_UPDATE_SZ * 7 / 10);
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ sent = false;
+ ret = wh_Client_Sha256UpdateRequest(ctx, sha256, buf + consumed,
+ chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha256UpdateResponse(ctx, sha256);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha256FinalRequest(ctx, sha256);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha256FinalResponse(ctx, sha256, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA256 case C mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Case D: oversized-input rejection. UpdateRequest with inLen > capacity
+ * must return BADARGS without mutating sha. */
+ if (ret == 0) {
+ uint8_t savedDigest[WC_SHA256_DIGEST_SIZE];
+ word32 savedBuffLen;
+ uint32_t cap;
+ int rc;
+ (void)wc_Sha256Free(sha256);
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret == 0) {
+ memcpy(savedDigest, sha256->digest, WC_SHA256_DIGEST_SIZE);
+ savedBuffLen = sha256->buffLen;
+ cap = WH_MESSAGE_CRYPTO_SHA256_MAX_INLINE_UPDATE_SZ +
+ (uint32_t)(WC_SHA256_BLOCK_SIZE - 1u - sha256->buffLen);
+ sent = true;
+ rc = wh_Client_Sha256UpdateRequest(ctx, sha256, buf, cap + 1u,
+ &sent);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Async SHA256: expected BADARGS, got %d\n", rc);
+ ret = -1;
+ }
+ else if (sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA256: sent should remain false on err\n");
+ ret = -1;
+ }
+ else if (sha256->buffLen != savedBuffLen ||
+ memcmp(sha256->digest, savedDigest,
+ WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT(
+ "Async SHA256: state mutated on rejected call\n");
+ ret = -1;
+ }
+ }
+ (void)wc_Sha256Free(sha256);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA256 ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+/* Direct exercise of the new async DMA SHA256 primitives. */
+static int whTest_CryptoSha256DmaAsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha256 sha256[1];
+ uint8_t out[WC_SHA256_DIGEST_SIZE];
+ uint8_t ref[WC_SHA256_DIGEST_SIZE];
+ /* DMA bypasses the comm buffer, so any size goes; reuse the shared
+ * static buffer to keep stack pressure low. */
+ uint8_t* buf = whTest_Sha256BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha256BigBuf);
+ uint32_t i;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 17u + 3u) & 0xff);
+ }
+
+ /* Case A: single large DMA Update + Final */
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ if (ret == 0) {
+ bool sent = false;
+ ret = wh_Client_Sha256DmaUpdateRequest(ctx, sha256, buf, BUFSZ, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha256DmaUpdateResponse(ctx, sha256);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha256DmaFinalRequest(ctx, sha256);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha256DmaFinalResponse(ctx, sha256, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha256Free(sha256);
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA256 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: multiple DMA Update round-trips, then Final */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ ret = wc_InitSha256_ex(sha256, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ uint32_t chunk = 1024;
+ bool sent = false;
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ ret = wh_Client_Sha256DmaUpdateRequest(ctx, sha256, buf + consumed,
+ chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha256DmaUpdateResponse(ctx, sha256);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha256DmaFinalRequest(ctx, sha256);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha256DmaFinalResponse(ctx, sha256, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha256Free(sha256);
+ if (ret == 0) {
+ ret = whTest_Sha256Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA256 case B mismatch\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA256 DMA ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+static int _whTest_CryptoSha256(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha256Impl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha256LargeInput(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha256LargeInputImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha256Async(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha256AsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+static int _whTest_CryptoSha256DmaAsync(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID_DMA;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha256DmaAsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+#endif /* !NO_SHA256 */
+
+#ifdef WOLFSSL_SHA224
+static int whTest_CryptoSha224Impl(whClientContext* ctx, int devId, WC_RNG* rng)
+{
+ (void)ctx;
+ (void)rng; /* Not currently used */
+ int ret = WH_ERROR_OK;
+ wc_Sha224 sha224[1];
+ uint8_t out[WC_SHA224_DIGEST_SIZE];
+ /* Vector exactly one block size in length */
+ const char inOne[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ const uint8_t expectedOutOne[WC_SHA224_DIGEST_SIZE] = {
+ 0xa8, 0x8c, 0xd5, 0xcd, 0xe6, 0xd6, 0xfe, 0x91, 0x36, 0xa4,
+ 0xe5, 0x8b, 0x49, 0x16, 0x74, 0x61, 0xea, 0x95, 0xd3, 0x88,
+ 0xca, 0x2b, 0xdb, 0x7a, 0xfd, 0xc3, 0xcb, 0xf4};
+ /* Vector long enough to span a SHA224 block */
+ const char inMulti[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"
+ "YZ1234567890abcdefghi";
+ const uint8_t expectedOutMulti[WC_SHA224_DIGEST_SIZE] = {
+ 0xb4, 0x22, 0xdc, 0xe8, 0xf9, 0x48, 0x8c, 0x4b, 0xc3, 0xef,
+ 0x8e, 0x7d, 0xbe, 0x11, 0xc7, 0x21, 0xba, 0x38, 0xcb, 0x61,
+ 0xf5, 0x6b, 0x7d, 0xc5, 0x30, 0xa7, 0x9c, 0xfd};
+ /* Initialize SHA224 structure */
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha224 on devId 0x%X: %d\n", devId,
+ ret);
+ }
+ else {
+ /* Test SHA224 on a single block worth of data. Should trigger a server
+ * transaction */
+ ret = wc_Sha224Update(sha224, (const byte*)inOne, WC_SHA224_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha224Update %d\n", ret);
+ }
+ else {
+ /* Finalize should trigger a server transaction with empty buffer */
+ ret = wc_Sha224Final(sha224, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha224Final %d\n", ret);
+ }
+ else {
+ /* Compare the computed hash with the expected output */
+ if (memcmp(out, expectedOutOne, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA224 hash does not match expected.\n");
+ ret = -1;
+ }
+ memset(out, 0, WC_SHA224_DIGEST_SIZE);
+ }
+ }
+ /* Reset state for multi block test */
+ (void)wc_Sha224Free(sha224);
+ }
+ if (ret == 0) {
+ /* Multiblock test */
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha224 for devId 0x%X: %d\n",
+ devId, ret);
+ }
+ else {
+ /* Update with a non-block aligned length. Will not trigger server
+ * transaction */
+ ret = wc_Sha224Update(sha224, (const byte*)inMulti, 1);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha224Update (first) %d\n", ret);
+ }
+ else {
+ /* Update with a full block, will trigger block to be sent to
+ * server and one additional byte to be buffered */
+ ret = wc_Sha224Update(sha224, (const byte*)inMulti + 1,
+ WC_SHA224_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha224Update (mid) %d\n", ret);
+ }
+ else {
+ /* Update with the remaining data, should not trigger server
+ * transaction */
+ ret = wc_Sha224Update(
+ sha224, (const byte*)inMulti + 1 + WC_SHA224_BLOCK_SIZE,
+ strlen(inMulti) - 1 - WC_SHA224_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha224Update (last) %d\n",
+ ret);
+ }
+ else {
+ /* Finalize should trigger a server transaction on the
+ * remaining partial buffer */
+ ret = wc_Sha224Final(sha224, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha224Final %d\n",
+ ret);
+ }
+ else {
+ /* Compare the computed hash with the expected
+ * output */
+ if (memcmp(out, expectedOutMulti,
+ WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA224 hash does not match the "
+ "expected output.\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ }
+ (void)wc_Sha224Free(sha224);
+ }
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA224 DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Hash a buffer with a pure-software SHA224 (no devId) so we can compare. */
+static int whTest_Sha224Reference(const uint8_t* in, uint32_t inLen,
+ uint8_t out[WC_SHA224_DIGEST_SIZE])
+{
+ wc_Sha224 sw[1];
+ int ret = wc_InitSha224_ex(sw, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_Sha224Update(sw, in, inLen);
+ }
+ if (ret == 0) {
+ ret = wc_Sha224Final(sw, out);
+ }
+ (void)wc_Sha224Free(sw);
+ return ret;
+}
+
+/* Drive the new multi-block wire format through the blocking wrapper. Tests:
+ * - large multi-request input,
+ * - exact per-call inline capacity boundary,
+ * - one-byte-over-capacity boundary (forces a tail in the client buffer),
+ * - non-aligned chunked update sequence.
+ *
+ * Buffer is sized to comfortably exceed the per-call inline capacity at any
+ * reasonable comm-buffer size. We use a static buffer to keep stack pressure
+ * low under ASAN. */
+static uint8_t
+ whTest_Sha224BigBuf[2 *
+ (WH_MESSAGE_CRYPTO_SHA224_MAX_INLINE_UPDATE_SZ + 64u)];
+
+static int whTest_CryptoSha224LargeInputImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha224 sha224[1];
+ uint8_t out[WC_SHA224_DIGEST_SIZE];
+ uint8_t ref[WC_SHA224_DIGEST_SIZE];
+ uint8_t* buf = whTest_Sha224BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha224BigBuf);
+ uint32_t i;
+
+ (void)ctx;
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)(i & 0xff);
+ }
+
+ /* Test 1: large single-update */
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha224Update(sha224, buf, BUFSZ);
+ }
+ if (ret == 0) {
+ ret = wc_Sha224Final(sha224, out);
+ }
+ (void)wc_Sha224Free(sha224);
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA224 large input mismatch\n");
+ ret = -1;
+ }
+
+ /* Test 2: exactly the per-call inline capacity in one shot */
+ if (ret == 0) {
+ const uint32_t cap = WH_MESSAGE_CRYPTO_SHA224_MAX_INLINE_UPDATE_SZ;
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha224Update(sha224, buf, cap);
+ }
+ if (ret == 0) {
+ ret = wc_Sha224Final(sha224, out);
+ }
+ (void)wc_Sha224Free(sha224);
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, cap, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA224 capacity-boundary mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 3: capacity + 1 byte (one full request, then a tail buffered) */
+ if (ret == 0) {
+ const uint32_t cap1 =
+ WH_MESSAGE_CRYPTO_SHA224_MAX_INLINE_UPDATE_SZ + 1u;
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha224Update(sha224, buf, cap1);
+ }
+ if (ret == 0) {
+ ret = wc_Sha224Final(sha224, out);
+ }
+ (void)wc_Sha224Free(sha224);
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, cap1, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA224 capacity+1 mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 4: non-aligned chunk stress test */
+ if (ret == 0) {
+ const uint32_t chunks[] = {13, 17, 1280, 41, 1};
+ const size_t nChunks = sizeof(chunks) / sizeof(chunks[0]);
+ uint32_t total = 0;
+ size_t k;
+ for (k = 0; k < nChunks; k++) {
+ total += chunks[k];
+ }
+ if (total > BUFSZ) {
+ WH_ERROR_PRINT("test buffer too small for chunked stress test\n");
+ ret = -1;
+ }
+ if (ret == 0) {
+ uint32_t off = 0;
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ for (k = 0; ret == 0 && k < nChunks; k++) {
+ ret = wc_Sha224Update(sha224, buf + off, chunks[k]);
+ off += chunks[k];
+ }
+ if (ret == 0) {
+ ret = wc_Sha224Final(sha224, out);
+ }
+ (void)wc_Sha224Free(sha224);
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, total, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA224 chunked stress mismatch\n");
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA224 LARGE-INPUT DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Direct exercise of the new async non-DMA SHA224 primitives. */
+static int whTest_CryptoSha224AsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha224 sha224[1];
+ uint8_t out[WC_SHA224_DIGEST_SIZE];
+ uint8_t ref[WC_SHA224_DIGEST_SIZE];
+ /* Use the same large static buffer as the LargeInput test. */
+ uint8_t* buf = whTest_Sha224BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha224BigBuf);
+ uint32_t i;
+ bool sent;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 31u + 7u) & 0xff);
+ }
+
+ /* Case A: basic UpdateRequest -> UpdateResponse -> Final */
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret == 0) {
+ sent = false;
+ ret = wh_Client_Sha224UpdateRequest(ctx, sha224, buf, 256, &sent);
+ }
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha224UpdateResponse(ctx, sha224);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha224FinalRequest(ctx, sha224);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha224FinalResponse(ctx, sha224, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, 256, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA224 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: pure-buffer-fill update (sent must be false), then finalize */
+ if (ret == 0) {
+ (void)wc_Sha224Free(sha224);
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ }
+ if (ret == 0) {
+ sent = true; /* expect to be cleared to false */
+ ret = wh_Client_Sha224UpdateRequest(ctx, sha224, buf, 10, &sent);
+ if (ret == 0 && sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA224: expected sent==false on small update\n");
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha224FinalRequest(ctx, sha224);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha224FinalResponse(ctx, sha224, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, 10, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA224 case B mismatch\n");
+ ret = -1;
+ }
+
+ /* Case C: multi-round async updates that span more than the per-call
+ * inline capacity (forces multiple Request/Response pairs). */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ (void)wc_Sha224Free(sha224);
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ /* arbitrary, 70% of max inline */
+ uint32_t chunk =
+ (WH_MESSAGE_CRYPTO_SHA224_MAX_INLINE_UPDATE_SZ * 7 / 10);
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ sent = false;
+ ret = wh_Client_Sha224UpdateRequest(ctx, sha224, buf + consumed,
+ chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha224UpdateResponse(ctx, sha224);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha224FinalRequest(ctx, sha224);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha224FinalResponse(ctx, sha224, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA224 case C mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Case D: oversized-input rejection. UpdateRequest with inLen > capacity
+ * must return BADARGS without mutating sha. */
+ if (ret == 0) {
+ uint8_t savedDigest[WC_SHA256_DIGEST_SIZE];
+ word32 savedBuffLen;
+ uint32_t cap;
+ int rc;
+ (void)wc_Sha224Free(sha224);
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret == 0) {
+ memcpy(savedDigest, sha224->digest, WC_SHA256_DIGEST_SIZE);
+ savedBuffLen = sha224->buffLen;
+ cap = WH_MESSAGE_CRYPTO_SHA224_MAX_INLINE_UPDATE_SZ +
+ (uint32_t)(WC_SHA224_BLOCK_SIZE - 1u - sha224->buffLen);
+ sent = true;
+ rc = wh_Client_Sha224UpdateRequest(ctx, sha224, buf, cap + 1u,
+ &sent);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Async SHA224: expected BADARGS, got %d\n", rc);
+ ret = -1;
+ }
+ else if (sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA224: sent should remain false on err\n");
+ ret = -1;
+ }
+ else if (sha224->buffLen != savedBuffLen ||
+ memcmp(sha224->digest, savedDigest,
+ WC_SHA256_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT(
+ "Async SHA224: state mutated on rejected call\n");
+ ret = -1;
+ }
+ }
+ (void)wc_Sha224Free(sha224);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA224 ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+/* Direct exercise of the new async DMA SHA224 primitives. */
+static int whTest_CryptoSha224DmaAsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha224 sha224[1];
+ uint8_t out[WC_SHA224_DIGEST_SIZE];
+ uint8_t ref[WC_SHA224_DIGEST_SIZE];
+ /* DMA bypasses the comm buffer, so any size goes; reuse the shared
+ * static buffer to keep stack pressure low. */
+ uint8_t* buf = whTest_Sha224BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha224BigBuf);
+ uint32_t i;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 17u + 3u) & 0xff);
+ }
+
+ /* Case A: single large DMA Update + Final */
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ if (ret == 0) {
+ bool sent = false;
+ ret = wh_Client_Sha224DmaUpdateRequest(ctx, sha224, buf, BUFSZ, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha224DmaUpdateResponse(ctx, sha224);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha224DmaFinalRequest(ctx, sha224);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha224DmaFinalResponse(ctx, sha224, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha224Free(sha224);
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA224 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: multiple DMA Update round-trips, then Final */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ ret = wc_InitSha224_ex(sha224, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ uint32_t chunk = 1024;
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ {
+ bool sent = false;
+ ret = wh_Client_Sha224DmaUpdateRequest(
+ ctx, sha224, buf + consumed, chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha224DmaUpdateResponse(ctx, sha224);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha224DmaFinalRequest(ctx, sha224);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha224DmaFinalResponse(ctx, sha224, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha224Free(sha224);
+ if (ret == 0) {
+ ret = whTest_Sha224Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA224_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA224 case B mismatch\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA224 DMA ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+static int _whTest_CryptoSha224(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha224Impl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha224LargeInput(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha224LargeInputImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha224Async(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha224AsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+static int _whTest_CryptoSha224DmaAsync(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID_DMA;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha224DmaAsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+#endif /* WOLFSSL_SHA224 */
+
+#ifdef WOLFSSL_SHA384
+static int whTest_CryptoSha384Impl(whClientContext* ctx, int devId, WC_RNG* rng)
+{
+ (void)ctx;
+ (void)rng; /* Not currently used */
+ int ret = WH_ERROR_OK;
+ wc_Sha384 sha384[1];
+ uint8_t out[WC_SHA384_DIGEST_SIZE];
+ /* Vector exactly one block size in length */
+ const char inOne[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ const uint8_t expectedOutOne[WC_SHA384_DIGEST_SIZE] = {
+ 0xed, 0xb1, 0x27, 0x30, 0xa3, 0x66, 0x09, 0x8b, 0x3b, 0x2b, 0xea, 0xc7,
+ 0x5a, 0x3b, 0xef, 0x1b, 0x09, 0x69, 0xb1, 0x5c, 0x48, 0xe2, 0x16, 0x3c,
+ 0x23, 0xd9, 0x69, 0x94, 0xf8, 0xd1, 0xbe, 0xf7, 0x60, 0xc7, 0xe2, 0x7f,
+ 0x3c, 0x46, 0x4d, 0x38, 0x29, 0xf5, 0x6c, 0x0d, 0x53, 0x80, 0x8b, 0x0b};
+ /* Vector long enough to span a SHA384 block */
+ const char inMulti[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"
+ "YZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"
+ "YZ1234567890abcdefghi";
+ const uint8_t expectedOutMulti[WC_SHA384_DIGEST_SIZE] = {
+ 0xe2, 0x56, 0x2a, 0x4b, 0xe2, 0x0a, 0x40, 0x34, 0xc1, 0x23, 0x8b, 0x1d,
+ 0x68, 0x49, 0x17, 0xdb, 0x8d, 0x3a, 0x78, 0xab, 0x22, 0xf3, 0xa1, 0x51,
+ 0x70, 0xae, 0x26, 0x80, 0x06, 0x25, 0x99, 0xa5, 0x3d, 0x0f, 0xc3, 0x7a,
+ 0xbd, 0xe1, 0xe2, 0xc6, 0x07, 0xdf, 0xd9, 0x6a, 0x89, 0xa8, 0x2b, 0x99};
+ /* Initialize SHA384 structure */
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha384 on devId 0x%X: %d\n", devId,
+ ret);
+ }
+ else {
+ /* Test SHA384on a single block worth of data. Should trigger a server
+ * transaction */
+ ret = wc_Sha384Update(sha384, (const byte*)inOne, WC_SHA384_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha384Update %d\n", ret);
+ }
+ else {
+ /* Finalize should trigger a server transaction with empty buffer */
+ ret = wc_Sha384Final(sha384, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha384Final %d\n", ret);
+ }
+ else {
+ /* Compare the computed hash with the expected output */
+ if (memcmp(out, expectedOutOne, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA384 hash does not match expected.\n");
+ ret = -1;
+ }
+ memset(out, 0, WC_SHA384_DIGEST_SIZE);
+ }
+ }
+ /* Reset state for multi block test */
+ (void)wc_Sha384Free(sha384);
+ }
+ if (ret == 0) {
+ /* Multiblock test */
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha384 for devId 0x%X: %d\n",
+ devId, ret);
+ }
+ else {
+ /* Update with a non-block aligned length. Will not trigger server
+ * transaction */
+ ret = wc_Sha384Update(sha384, (const byte*)inMulti, 1);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha384Update (first) %d\n", ret);
+ }
+ else {
+ /* Update with a full block, will trigger block to be sent to
+ * server and one additional byte to be buffered */
+ ret = wc_Sha384Update(sha384, (const byte*)inMulti + 1,
+ WC_SHA384_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha384Update (mid) %d\n", ret);
+ }
+ else {
+ /* Update with the remaining data, should not trigger server
+ * transaction */
+ ret = wc_Sha384Update(
+ sha384, (const byte*)inMulti + 1 + WC_SHA384_BLOCK_SIZE,
+ strlen(inMulti) - 1 - WC_SHA384_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha384Update (last) %d\n",
+ ret);
+ }
+ else {
+ /* Finalize should trigger a server transaction on the
+ * remaining partial buffer */
+ ret = wc_Sha384Final(sha384, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha384Final %d\n",
+ ret);
+ }
+ else {
+ /* Compare the computed hash with the expected
+ * output */
+ if (memcmp(out, expectedOutMulti,
+ WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA384 hash does not match the "
+ "expected output.\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ }
+ (void)wc_Sha384Free(sha384);
+ }
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA384 DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Hash a buffer with a pure-software SHA384 (no devId) so we can compare. */
+static int whTest_Sha384Reference(const uint8_t* in, uint32_t inLen,
+ uint8_t out[WC_SHA384_DIGEST_SIZE])
+{
+ wc_Sha384 sw[1];
+ int ret = wc_InitSha384_ex(sw, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_Sha384Update(sw, in, inLen);
+ }
+ if (ret == 0) {
+ ret = wc_Sha384Final(sw, out);
+ }
+ (void)wc_Sha384Free(sw);
+ return ret;
+}
+
+/* Drive the new multi-block wire format through the blocking wrapper. Tests:
+ * - large multi-request input,
+ * - exact per-call inline capacity boundary,
+ * - one-byte-over-capacity boundary (forces a tail in the client buffer),
+ * - non-aligned chunked update sequence.
+ *
+ * Buffer is sized to comfortably exceed the per-call inline capacity at any
+ * reasonable comm-buffer size. We use a static buffer to keep stack pressure
+ * low under ASAN. */
+static uint8_t
+ whTest_Sha384BigBuf[2 *
+ (WH_MESSAGE_CRYPTO_SHA384_MAX_INLINE_UPDATE_SZ + 128u)];
+
+static int whTest_CryptoSha384LargeInputImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha384 sha384[1];
+ uint8_t out[WC_SHA384_DIGEST_SIZE];
+ uint8_t ref[WC_SHA384_DIGEST_SIZE];
+ uint8_t* buf = whTest_Sha384BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha384BigBuf);
+ uint32_t i;
+
+ (void)ctx;
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)(i & 0xff);
+ }
+
+ /* Test 1: large single-update */
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha384Update(sha384, buf, BUFSZ);
+ }
+ if (ret == 0) {
+ ret = wc_Sha384Final(sha384, out);
+ }
+ (void)wc_Sha384Free(sha384);
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA384 large input mismatch\n");
+ ret = -1;
+ }
+
+ /* Test 2: exactly the per-call inline capacity in one shot */
+ if (ret == 0) {
+ const uint32_t cap = WH_MESSAGE_CRYPTO_SHA384_MAX_INLINE_UPDATE_SZ;
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha384Update(sha384, buf, cap);
+ }
+ if (ret == 0) {
+ ret = wc_Sha384Final(sha384, out);
+ }
+ (void)wc_Sha384Free(sha384);
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, cap, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA384 capacity-boundary mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 3: capacity + 1 byte (one full request, then a tail buffered) */
+ if (ret == 0) {
+ const uint32_t cap1 =
+ WH_MESSAGE_CRYPTO_SHA384_MAX_INLINE_UPDATE_SZ + 1u;
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha384Update(sha384, buf, cap1);
+ }
+ if (ret == 0) {
+ ret = wc_Sha384Final(sha384, out);
+ }
+ (void)wc_Sha384Free(sha384);
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, cap1, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA384 capacity+1 mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 4: non-aligned chunk stress test */
+ if (ret == 0) {
+ const uint32_t chunks[] = {13, 17, 1280, 41, 1};
+ const size_t nChunks = sizeof(chunks) / sizeof(chunks[0]);
+ uint32_t total = 0;
+ size_t k;
+ for (k = 0; k < nChunks; k++) {
+ total += chunks[k];
+ }
+ if (total > BUFSZ) {
+ WH_ERROR_PRINT("test buffer too small for chunked stress test\n");
+ ret = -1;
+ }
+ if (ret == 0) {
+ uint32_t off = 0;
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ for (k = 0; ret == 0 && k < nChunks; k++) {
+ ret = wc_Sha384Update(sha384, buf + off, chunks[k]);
+ off += chunks[k];
+ }
+ if (ret == 0) {
+ ret = wc_Sha384Final(sha384, out);
+ }
+ (void)wc_Sha384Free(sha384);
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, total, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA384 chunked stress mismatch\n");
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA384 LARGE-INPUT DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Direct exercise of the new async non-DMA SHA384 primitives. */
+static int whTest_CryptoSha384AsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha384 sha384[1];
+ uint8_t out[WC_SHA384_DIGEST_SIZE];
+ uint8_t ref[WC_SHA384_DIGEST_SIZE];
+ /* Use the same large static buffer as the LargeInput test. */
+ uint8_t* buf = whTest_Sha384BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha384BigBuf);
+ uint32_t i;
+ bool sent;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 31u + 7u) & 0xff);
+ }
+
+ /* Case A: basic UpdateRequest -> UpdateResponse -> Final */
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret == 0) {
+ sent = false;
+ ret = wh_Client_Sha384UpdateRequest(ctx, sha384, buf, 256, &sent);
+ }
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha384UpdateResponse(ctx, sha384);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha384FinalRequest(ctx, sha384);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha384FinalResponse(ctx, sha384, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, 256, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA384 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: pure-buffer-fill update (sent must be false), then finalize */
+ if (ret == 0) {
+ (void)wc_Sha384Free(sha384);
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ }
+ if (ret == 0) {
+ sent = true; /* expect to be cleared to false */
+ ret = wh_Client_Sha384UpdateRequest(ctx, sha384, buf, 10, &sent);
+ if (ret == 0 && sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA384: expected sent==false on small update\n");
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha384FinalRequest(ctx, sha384);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha384FinalResponse(ctx, sha384, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, 10, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA384 case B mismatch\n");
+ ret = -1;
+ }
+
+ /* Case C: multi-round async updates that span more than the per-call
+ * inline capacity (forces multiple Request/Response pairs). */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ (void)wc_Sha384Free(sha384);
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ /* arbitrary, 70% of max inline */
+ uint32_t chunk =
+ (WH_MESSAGE_CRYPTO_SHA384_MAX_INLINE_UPDATE_SZ * 7 / 10);
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ sent = false;
+ ret = wh_Client_Sha384UpdateRequest(ctx, sha384, buf + consumed,
+ chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha384UpdateResponse(ctx, sha384);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha384FinalRequest(ctx, sha384);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha384FinalResponse(ctx, sha384, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA384 case C mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Case D: oversized-input rejection. UpdateRequest with inLen > capacity
+ * must return BADARGS without mutating sha. */
+ if (ret == 0) {
+ uint8_t savedDigest[WC_SHA512_DIGEST_SIZE];
+ word32 savedBuffLen;
+ uint32_t cap;
+ int rc;
+ (void)wc_Sha384Free(sha384);
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret == 0) {
+ memcpy(savedDigest, sha384->digest, WC_SHA512_DIGEST_SIZE);
+ savedBuffLen = sha384->buffLen;
+ cap = WH_MESSAGE_CRYPTO_SHA384_MAX_INLINE_UPDATE_SZ +
+ (uint32_t)(WC_SHA384_BLOCK_SIZE - 1u - sha384->buffLen);
+ sent = true;
+ rc = wh_Client_Sha384UpdateRequest(ctx, sha384, buf, cap + 1u,
+ &sent);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Async SHA384: expected BADARGS, got %d\n", rc);
+ ret = -1;
+ }
+ else if (sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA384: sent should remain false on err\n");
+ ret = -1;
+ }
+ else if (sha384->buffLen != savedBuffLen ||
+ memcmp(sha384->digest, savedDigest,
+ WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT(
+ "Async SHA384: state mutated on rejected call\n");
+ ret = -1;
+ }
+ }
+ (void)wc_Sha384Free(sha384);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA384 ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+/* Direct exercise of the new async DMA SHA384 primitives. */
+static int whTest_CryptoSha384DmaAsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha384 sha384[1];
+ uint8_t out[WC_SHA384_DIGEST_SIZE];
+ uint8_t ref[WC_SHA384_DIGEST_SIZE];
+ /* DMA bypasses the comm buffer, so any size goes; reuse the shared
+ * static buffer to keep stack pressure low. */
+ uint8_t* buf = whTest_Sha384BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha384BigBuf);
+ uint32_t i;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 17u + 3u) & 0xff);
+ }
+
+ /* Case A: single large DMA Update + Final */
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ if (ret == 0) {
+ bool sent = false;
+ ret = wh_Client_Sha384DmaUpdateRequest(ctx, sha384, buf, BUFSZ, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha384DmaUpdateResponse(ctx, sha384);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha384DmaFinalRequest(ctx, sha384);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha384DmaFinalResponse(ctx, sha384, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha384Free(sha384);
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA384 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: multiple DMA Update round-trips, then Final */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ ret = wc_InitSha384_ex(sha384, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ uint32_t chunk = 1024;
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ {
+ bool sent = false;
+ ret = wh_Client_Sha384DmaUpdateRequest(
+ ctx, sha384, buf + consumed, chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha384DmaUpdateResponse(ctx, sha384);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha384DmaFinalRequest(ctx, sha384);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha384DmaFinalResponse(ctx, sha384, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha384Free(sha384);
+ if (ret == 0) {
+ ret = whTest_Sha384Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA384_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA384 case B mismatch\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA384 DMA ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+static int _whTest_CryptoSha384(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha384Impl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha384LargeInput(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha384LargeInputImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha384Async(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha384AsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+static int _whTest_CryptoSha384DmaAsync(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID_DMA;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha384DmaAsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+#endif /* WOLFSSL_SHA384 */
+
+#ifdef WOLFSSL_SHA512
+static int whTest_CryptoSha512Impl(whClientContext* ctx, int devId, WC_RNG* rng)
+{
+ (void)ctx;
+ (void)rng; /* Not currently used */
+ int ret = WH_ERROR_OK;
+ wc_Sha512 sha512[1];
+ uint8_t out[WC_SHA512_DIGEST_SIZE];
+ /* Vector exactly one block size in length */
+ const char inOne[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ const uint8_t expectedOutOne[WC_SHA512_DIGEST_SIZE] = {
+ 0xb7, 0x3d, 0x19, 0x29, 0xaa, 0x61, 0x59, 0x34, 0xe6, 0x1a, 0x87,
+ 0x15, 0x96, 0xb3, 0xf3, 0xb3, 0x33, 0x59, 0xf4, 0x2b, 0x81, 0x75,
+ 0x60, 0x2e, 0x89, 0xf7, 0xe0, 0x6e, 0x5f, 0x65, 0x8a, 0x24, 0x36,
+ 0x67, 0x80, 0x7e, 0xd3, 0x00, 0x31, 0x4b, 0x95, 0xca, 0xcd, 0xd5,
+ 0x79, 0xf3, 0xe3, 0x3a, 0xbd, 0xfb, 0xe3, 0x51, 0x90, 0x95, 0x19,
+ 0xa8, 0x46, 0xd4, 0x65, 0xc5, 0x95, 0x82, 0xf3, 0x21};
+ /* Vector long enough to span a SHA512 block */
+ const char inMulti[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"
+ "YZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"
+ "YZ1234567890abcdefghi";
+ const uint8_t expectedOutMulti[WC_SHA512_DIGEST_SIZE] = {
+ 0xf9, 0x09, 0xb7, 0xb7, 0x7d, 0xa2, 0x32, 0xc8, 0xcf, 0xa8, 0xcc,
+ 0xde, 0xc4, 0x36, 0x44, 0x74, 0x29, 0x4f, 0xc4, 0x9a, 0xcb, 0x60,
+ 0x13, 0x6b, 0xdb, 0x10, 0xd6, 0xa6, 0x9d, 0x1b, 0x45, 0xb2, 0x70,
+ 0xf5, 0x27, 0x9c, 0xe7, 0x80, 0x99, 0x19, 0x9b, 0x91, 0xb3, 0x83,
+ 0x7f, 0x70, 0xaf, 0x8e, 0x02, 0xd9, 0x6d, 0x20, 0xab, 0x1e, 0x72,
+ 0xde, 0x7a, 0x25, 0xa3, 0xe5, 0x60, 0x9e, 0xb0, 0x43};
+ /* Initialize SHA512 structure */
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha512 on devId 0x%X: %d\n", devId,
+ ret);
+ }
+ else {
+ /* Test SHA512 on a single block worth of data. Should trigger a server
+ * transaction */
+ ret = wc_Sha512Update(sha512, (const byte*)inOne, WC_SHA512_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha512Update %d\n", ret);
+ }
+ else {
+ /* Finalize should trigger a server transaction with empty buffer */
+ ret = wc_Sha512Final(sha512, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha512Final %d\n", ret);
+ }
+ else {
+ /* Compare the computed hash with the expected output */
+ if (memcmp(out, expectedOutOne, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA512 hash does not match expected.\n");
+ ret = -1;
+ }
+ memset(out, 0, WC_SHA512_DIGEST_SIZE);
+ }
+ }
+ /* Reset state for multi block test */
+ (void)wc_Sha512Free(sha512);
+ }
+ if (ret == 0) {
+ /* Multiblock test */
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitSha512 for devId 0x%X: %d\n",
+ devId, ret);
+ }
+ else {
+ /* Update with a non-block aligned length. Will not trigger server
+ * transaction */
+ ret = wc_Sha512Update(sha512, (const byte*)inMulti, 1);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha512Update (first) %d\n", ret);
+ }
+ else {
+ /* Update with a full block, will trigger block to be sent to
+ * server and one additional byte to be buffered */
+ ret = wc_Sha512Update(sha512, (const byte*)inMulti + 1,
+ WC_SHA512_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha512Update (mid) %d\n", ret);
+ }
+ else {
+ /* Update with the remaining data, should not trigger server
+ * transaction */
+ ret = wc_Sha512Update(
+ sha512, (const byte*)inMulti + 1 + WC_SHA512_BLOCK_SIZE,
+ strlen(inMulti) - 1 - WC_SHA512_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha512Update (last) %d\n",
+ ret);
+ }
+ else {
+ /* Finalize should trigger a server transaction on the
+ * remaining partial buffer */
+ ret = wc_Sha512Final(sha512, out);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_Sha512Final %d\n",
+ ret);
+ }
+ else {
+ /* Compare the computed hash with the expected
+ * output */
+ if (memcmp(out, expectedOutMulti,
+ WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA512 hash does not match the "
+ "expected output.\n");
+ ret = -1;
+ }
+ }
+ }
+ }
+ }
+ (void)wc_Sha512Free(sha512);
+ }
+ }
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA512 DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Hash a buffer with a pure-software SHA512 (no devId) so we can compare. */
+static int whTest_Sha512Reference(const uint8_t* in, uint32_t inLen,
+ uint8_t out[WC_SHA512_DIGEST_SIZE])
+{
+ wc_Sha512 sw[1];
+ int ret = wc_InitSha512_ex(sw, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_Sha512Update(sw, in, inLen);
+ }
+ if (ret == 0) {
+ ret = wc_Sha512Final(sw, out);
+ }
+ (void)wc_Sha512Free(sw);
+ return ret;
+}
+
+/* Drive the new multi-block wire format through the blocking wrapper. Tests:
+ * - large multi-request input,
+ * - exact per-call inline capacity boundary,
+ * - one-byte-over-capacity boundary (forces a tail in the client buffer),
+ * - non-aligned chunked update sequence.
+ *
+ * Buffer is sized to comfortably exceed the per-call inline capacity at any
+ * reasonable comm-buffer size. We use a static buffer to keep stack pressure
+ * low under ASAN. */
+static uint8_t
+ whTest_Sha512BigBuf[2 *
+ (WH_MESSAGE_CRYPTO_SHA512_MAX_INLINE_UPDATE_SZ + 128u)];
+
+static int whTest_CryptoSha512LargeInputImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha512 sha512[1];
+ uint8_t out[WC_SHA512_DIGEST_SIZE];
+ uint8_t ref[WC_SHA512_DIGEST_SIZE];
+ uint8_t* buf = whTest_Sha512BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha512BigBuf);
+ uint32_t i;
+
+ (void)ctx;
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)(i & 0xff);
+ }
+
+ /* Test 1: large single-update */
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha512Update(sha512, buf, BUFSZ);
+ }
+ if (ret == 0) {
+ ret = wc_Sha512Final(sha512, out);
+ }
+ (void)wc_Sha512Free(sha512);
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA512 large input mismatch\n");
+ ret = -1;
+ }
+
+ /* Test 2: exactly the per-call inline capacity in one shot */
+ if (ret == 0) {
+ const uint32_t cap = WH_MESSAGE_CRYPTO_SHA512_MAX_INLINE_UPDATE_SZ;
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha512Update(sha512, buf, cap);
+ }
+ if (ret == 0) {
+ ret = wc_Sha512Final(sha512, out);
+ }
+ (void)wc_Sha512Free(sha512);
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, cap, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA512 capacity-boundary mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 3: capacity + 1 byte (one full request, then a tail buffered) */
+ if (ret == 0) {
+ const uint32_t cap1 =
+ WH_MESSAGE_CRYPTO_SHA512_MAX_INLINE_UPDATE_SZ + 1u;
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha512Update(sha512, buf, cap1);
+ }
+ if (ret == 0) {
+ ret = wc_Sha512Final(sha512, out);
+ }
+ (void)wc_Sha512Free(sha512);
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, cap1, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA512 capacity+1 mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Test 4: non-aligned chunk stress test */
+ if (ret == 0) {
+ const uint32_t chunks[] = {13, 17, 1280, 41, 1};
+ const size_t nChunks = sizeof(chunks) / sizeof(chunks[0]);
+ uint32_t total = 0;
+ size_t k;
+ for (k = 0; k < nChunks; k++) {
+ total += chunks[k];
+ }
+ if (total > BUFSZ) {
+ WH_ERROR_PRINT("test buffer too small for chunked stress test\n");
+ ret = -1;
+ }
+ if (ret == 0) {
+ uint32_t off = 0;
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ for (k = 0; ret == 0 && k < nChunks; k++) {
+ ret = wc_Sha512Update(sha512, buf + off, chunks[k]);
+ off += chunks[k];
+ }
+ if (ret == 0) {
+ ret = wc_Sha512Final(sha512, out);
+ }
+ (void)wc_Sha512Free(sha512);
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, total, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("SHA512 chunked stress mismatch\n");
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA512 LARGE-INPUT DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+/* Direct exercise of the new async non-DMA SHA512 primitives. */
+static int whTest_CryptoSha512AsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha512 sha512[1];
+ uint8_t out[WC_SHA512_DIGEST_SIZE];
+ uint8_t ref[WC_SHA512_DIGEST_SIZE];
+ /* Use the same large static buffer as the LargeInput test. */
+ uint8_t* buf = whTest_Sha512BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha512BigBuf);
+ uint32_t i;
+ bool sent;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 31u + 7u) & 0xff);
+ }
+
+ /* Case A: basic UpdateRequest -> UpdateResponse -> Final */
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret == 0) {
+ sent = false;
+ ret = wh_Client_Sha512UpdateRequest(ctx, sha512, buf, 256, &sent);
+ }
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha512UpdateResponse(ctx, sha512);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha512FinalRequest(ctx, sha512);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha512FinalResponse(ctx, sha512, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, 256, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA512 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: pure-buffer-fill update (sent must be false), then finalize */
+ if (ret == 0) {
+ (void)wc_Sha512Free(sha512);
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ }
+ if (ret == 0) {
+ sent = true; /* expect to be cleared to false */
+ ret = wh_Client_Sha512UpdateRequest(ctx, sha512, buf, 10, &sent);
+ if (ret == 0 && sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA512: expected sent==false on small update\n");
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha512FinalRequest(ctx, sha512);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha512FinalResponse(ctx, sha512, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, 10, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA512 case B mismatch\n");
+ ret = -1;
+ }
+
+ /* Case C: multi-round async updates that span more than the per-call
+ * inline capacity (forces multiple Request/Response pairs). */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ (void)wc_Sha512Free(sha512);
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ /* arbitrary, 70% of max inline */
+ uint32_t chunk =
+ (WH_MESSAGE_CRYPTO_SHA512_MAX_INLINE_UPDATE_SZ * 7 / 10);
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ sent = false;
+ ret = wh_Client_Sha512UpdateRequest(ctx, sha512, buf + consumed,
+ chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha512UpdateResponse(ctx, sha512);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha512FinalRequest(ctx, sha512);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha512FinalResponse(ctx, sha512, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async SHA512 case C mismatch\n");
+ ret = -1;
+ }
+ }
+
+ /* Case D: oversized-input rejection. UpdateRequest with inLen > capacity
+ * must return BADARGS without mutating sha. */
+ if (ret == 0) {
+ uint8_t savedDigest[WC_SHA512_DIGEST_SIZE];
+ word32 savedBuffLen;
+ uint32_t cap;
+ int rc;
+ (void)wc_Sha512Free(sha512);
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret == 0) {
+ memcpy(savedDigest, sha512->digest, WC_SHA512_DIGEST_SIZE);
+ savedBuffLen = sha512->buffLen;
+ cap = WH_MESSAGE_CRYPTO_SHA512_MAX_INLINE_UPDATE_SZ +
+ (uint32_t)(WC_SHA512_BLOCK_SIZE - 1u - sha512->buffLen);
+ sent = true;
+ rc = wh_Client_Sha512UpdateRequest(ctx, sha512, buf, cap + 1u,
+ &sent);
+ if (rc != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Async SHA512: expected BADARGS, got %d\n", rc);
+ ret = -1;
+ }
+ else if (sent != false) {
+ WH_ERROR_PRINT(
+ "Async SHA512: sent should remain false on err\n");
+ ret = -1;
+ }
+ else if (sha512->buffLen != savedBuffLen ||
+ memcmp(sha512->digest, savedDigest,
+ WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT(
+ "Async SHA512: state mutated on rejected call\n");
+ ret = -1;
+ }
+ }
+ (void)wc_Sha512Free(sha512);
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA512 ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+/* Direct exercise of the new async DMA SHA512 primitives. */
+static int whTest_CryptoSha512DmaAsyncImpl(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret = WH_ERROR_OK;
+ wc_Sha512 sha512[1];
+ uint8_t out[WC_SHA512_DIGEST_SIZE];
+ uint8_t ref[WC_SHA512_DIGEST_SIZE];
+ /* DMA bypasses the comm buffer, so any size goes; reuse the shared
+ * static buffer to keep stack pressure low. */
+ uint8_t* buf = whTest_Sha512BigBuf;
+ uint32_t BUFSZ = (uint32_t)sizeof(whTest_Sha512BigBuf);
+ uint32_t i;
+
+ (void)rng;
+
+ for (i = 0; i < BUFSZ; i++) {
+ buf[i] = (uint8_t)((i * 17u + 3u) & 0xff);
+ }
+
+ /* Case A: single large DMA Update + Final */
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ if (ret == 0) {
+ bool sent = false;
+ ret = wh_Client_Sha512DmaUpdateRequest(ctx, sha512, buf, BUFSZ, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha512DmaUpdateResponse(ctx, sha512);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha512DmaFinalRequest(ctx, sha512);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha512DmaFinalResponse(ctx, sha512, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha512Free(sha512);
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA512 case A mismatch\n");
+ ret = -1;
+ }
+
+ /* Case B: multiple DMA Update round-trips, then Final */
+ if (ret == 0) {
+ uint32_t consumed = 0;
+ ret = wc_InitSha512_ex(sha512, NULL, devId);
+ while (ret == 0 && consumed < BUFSZ) {
+ uint32_t chunk = 1024;
+ if (consumed + chunk > BUFSZ) {
+ chunk = BUFSZ - consumed;
+ }
+ {
+ bool sent = false;
+ ret = wh_Client_Sha512DmaUpdateRequest(
+ ctx, sha512, buf + consumed, chunk, &sent);
+ if (ret == 0 && sent) {
+ do {
+ ret = wh_Client_Sha512DmaUpdateResponse(ctx, sha512);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+ consumed += chunk;
+ }
+ if (ret == 0) {
+ ret = wh_Client_Sha512DmaFinalRequest(ctx, sha512);
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_Sha512DmaFinalResponse(ctx, sha512, out);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ (void)wc_Sha512Free(sha512);
+ if (ret == 0) {
+ ret = whTest_Sha512Reference(buf, BUFSZ, ref);
+ }
+ if (ret == 0 && memcmp(out, ref, WC_SHA512_DIGEST_SIZE) != 0) {
+ WH_ERROR_PRINT("Async DMA SHA512 case B mismatch\n");
+ ret = -1;
+ }
+ }
+
+ if (ret == 0) {
+ WH_TEST_PRINT("SHA512 DMA ASYNC DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+static int _whTest_CryptoSha512(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha512Impl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha512LargeInput(whClientContext* ctx, int devId)
+{
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, WH_DEV_ID);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha512LargeInputImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+static int _whTest_CryptoSha512Async(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha512AsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+
+#ifdef WOLFHSM_CFG_DMA
+static int _whTest_CryptoSha512DmaAsync(whClientContext* ctx)
+{
+ int devId = WH_DEV_ID_DMA;
+ int ret;
+ WC_RNG rng[1];
+
+ ret = wc_InitRng_ex(rng, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret);
+ return ret;
+ }
+ ret = whTest_CryptoSha512DmaAsyncImpl(ctx, devId, rng);
+ (void)wc_FreeRng(rng);
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+#endif /* WOLFSSL_SHA512 */
+
+int whTest_Crypto_Sha(whClientContext* ctx)
+{
+ int i, devId;
+
+ /* Synchronous hashing (plain + large input) dispatches through the
+ * cryptocb, so run it on every devId to cover the normal and DMA server
+ * transports. The explicit request/response suites are transport-specific
+ * (comm-buffer Async vs DmaAsync), not devId-routed -- run each once. */
+#ifdef WOLFSSL_SHA224
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha224(ctx, devId));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha224LargeInput(ctx, devId));
+ }
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha224Async(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha224DmaAsync(ctx));
+#endif
+#endif
+#ifndef NO_SHA256
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha256(ctx, devId));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha256LargeInput(ctx, devId));
+ }
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha256Async(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha256DmaAsync(ctx));
+#endif
+#endif
+#ifdef WOLFSSL_SHA384
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha384(ctx, devId));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha384LargeInput(ctx, devId));
+ }
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha384Async(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha384DmaAsync(ctx));
+#endif
+#endif
+#ifdef WOLFSSL_SHA512
+ WH_TEST_FOREACH_DEVID(i, devId) {
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha512(ctx, devId));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha512LargeInput(ctx, devId));
+ }
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha512Async(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha512DmaAsync(ctx));
+#endif
+#endif
+ (void)ctx;
+ (void)i;
+ (void)devId;
+ return 0;
+}
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/config/wolfhsm_cfg.h b/test-refactor/config/wolfhsm_cfg.h
new file mode 100644
index 000000000..ea8402ce1
--- /dev/null
+++ b/test-refactor/config/wolfhsm_cfg.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+/*
+ * wolfhsm_cfg.h
+ *
+ * wolfHSM compile-time options for the test-refactor suite. Sibling of
+ * test/config/wolfhsm_cfg.h; the two are intentionally allowed to diverge
+ * as the refactor matures.
+ */
+
+#ifndef WOLFHSM_CFG_H_
+#define WOLFHSM_CFG_H_
+
+#include "port/posix/posix_time.h"
+#define WOLFHSM_CFG_PORT_GETTIME posixGetTime
+
+
+/** wolfHSM settings. Simple overrides to show they work */
+/* #define WOLFHSM_CFG_NO_CRYPTO */
+/* #define WOLFHSM_CFG_SHE_EXTENSION */
+
+#define WOLFHSM_CFG_COMM_DATA_LEN (1024 * 8)
+
+/* Enable global keys feature for testing */
+#define WOLFHSM_CFG_GLOBAL_KEYS
+
+/* Enable logging feature for testing */
+#define WOLFHSM_CFG_LOGGING
+
+#define WOLFHSM_CFG_NVM_OBJECT_COUNT 30
+#define WOLFHSM_CFG_SERVER_KEYCACHE_COUNT 9
+#define WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE 300
+#define WOLFHSM_CFG_SERVER_KEYCACHE_BIG_COUNT 3
+#define WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE WOLFHSM_CFG_COMM_DATA_LEN
+#define WOLFHSM_CFG_DMAADDR_COUNT 8
+#define WOLFHSM_CFG_SERVER_CUSTOMCB_COUNT 6
+
+#define WOLFHSM_CFG_CERTIFICATE_MANAGER
+#define WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT
+
+/* Enable Image Manager feature */
+#define WOLFHSM_CFG_SERVER_IMG_MGR
+
+#ifndef WOLFHSM_CFG_NO_CRYPTO
+#define WOLFHSM_CFG_KEYWRAP
+#endif
+
+/* Test log-based NVM flash backend */
+#define WOLFHSM_CFG_SERVER_NVM_FLASH_LOG
+
+/* WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS is intentionally NOT
+ * defined here. Persistent-artifact tests require working reset hooks
+ * (whTestPort_ResetServer/Client) which are no-ops today. */
+
+#define WOLFHSM_CFG_ENABLE_TIMEOUT
+
+#endif /* WOLFHSM_CFG_H_ */
diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile
index c19267fd2..760adb3e3 100644
--- a/test-refactor/posix/Makefile
+++ b/test-refactor/posix/Makefile
@@ -12,6 +12,7 @@ BIN = wh_test_refactor
PROJECT_DIR ?= .
REFACTOR_DIR ?= $(PROJECT_DIR)/..
CONFIG_DIR ?= $(REFACTOR_DIR)/../test/config
+REFACTOR_CFG_DIR ?= $(REFACTOR_DIR)/config
WOLFSSL_DIR ?= ../../../wolfssl
WOLFHSM_DIR ?= $(REFACTOR_DIR)/..
WOLFHSM_PORT_DIR ?= $(WOLFHSM_DIR)/port/posix
@@ -25,6 +26,7 @@ INC = -I$(PROJECT_DIR) \
-I$(REFACTOR_DIR)/server \
-I$(REFACTOR_DIR)/client-server \
-I$(REFACTOR_DIR)/misc \
+ -I$(REFACTOR_CFG_DIR) \
-I$(CONFIG_DIR) \
-I$(TEST_DIR) \
-I$(WOLFSSL_DIR) \
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index 0ba5674ae..a20d10ee9 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -27,6 +27,10 @@
* The weak stub returns WH_TEST_SKIPPED; the real test, when
* compiled in, provides a strong symbol that the linker picks
* instead.
+ *
+ * Per-file crypto suites are aggregated into a single whTest_Crypto_*
+ * entry point per source file; the per-subtest functions are file-static
+ * and run via WH_TEST_RUN_SUBTEST from inside the group entry point.
*/
#include "wh_test_list.h"
@@ -35,12 +39,17 @@
WH_TEST_DECL(whTest_Dma);
WH_TEST_DECL(whTest_CertVerify);
WH_TEST_DECL(whTest_ClientCerts);
-WH_TEST_DECL(whTest_CryptoAes);
-WH_TEST_DECL(whTest_CryptoEcc256);
-WH_TEST_DECL(whTest_CryptoEd25519BufferTooSmall);
-WH_TEST_DECL(whTest_CryptoMlDsaBufferTooSmall);
-WH_TEST_DECL(whTest_CryptoRsaBufferTooSmall);
-WH_TEST_DECL(whTest_CryptoSha256);
+WH_TEST_DECL(whTest_Crypto_Aes);
+WH_TEST_DECL(whTest_Crypto_Cmac);
+WH_TEST_DECL(whTest_Crypto_Curve25519);
+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_MlDsa);
+WH_TEST_DECL(whTest_Crypto_Rng);
+WH_TEST_DECL(whTest_Crypto_Rsa);
+WH_TEST_DECL(whTest_Crypto_Sha);
WH_TEST_DECL(whTest_Echo);
WH_TEST_DECL(whTest_ServerInfo);
WH_TEST_DECL(whTest_WolfCryptTest);
@@ -57,13 +66,17 @@ const size_t whTestsServerCount = sizeof(whTestsServer) / sizeof(whTestsServer[0
const whTestCase whTestsClient[] = {
{ "whTest_ClientCerts", whTest_ClientCerts },
- { "whTest_CryptoAes", whTest_CryptoAes },
- { "whTest_CryptoEcc256", whTest_CryptoEcc256 },
- { "whTest_CryptoEd25519BufferTooSmall",
- whTest_CryptoEd25519BufferTooSmall },
- { "whTest_CryptoMlDsaBufferTooSmall", whTest_CryptoMlDsaBufferTooSmall },
- { "whTest_CryptoRsaBufferTooSmall", whTest_CryptoRsaBufferTooSmall },
- { "whTest_CryptoSha256", whTest_CryptoSha256 },
+ { "whTest_Crypto_Aes", whTest_Crypto_Aes },
+ { "whTest_Crypto_Cmac", whTest_Crypto_Cmac },
+ { "whTest_Crypto_Curve25519", whTest_Crypto_Curve25519 },
+ { "whTest_Crypto_Ecc", whTest_Crypto_Ecc },
+ { "whTest_Crypto_Ed25519", whTest_Crypto_Ed25519 },
+ { "whTest_Crypto_Kdf", whTest_Crypto_Kdf },
+ { "whTest_Crypto_KeyPolicy", whTest_Crypto_KeyPolicy },
+ { "whTest_Crypto_MlDsa", whTest_Crypto_MlDsa },
+ { "whTest_Crypto_Rng", whTest_Crypto_Rng },
+ { "whTest_Crypto_Rsa", whTest_Crypto_Rsa },
+ { "whTest_Crypto_Sha", whTest_Crypto_Sha },
{ "whTest_Echo", whTest_Echo },
{ "whTest_ServerInfo", whTest_ServerInfo },
{ "whTest_WolfCryptTest", whTest_WolfCryptTest },
diff --git a/test/wh_test_common.h b/test/wh_test_common.h
index aab655162..662dd871e 100644
--- a/test/wh_test_common.h
+++ b/test/wh_test_common.h
@@ -83,6 +83,28 @@
} while (0)
+/*
+ * Iterate (idxVar, devIdVar) over every registered crypto devId: WH_DEV_ID,
+ * plus WH_DEV_ID_DMA under a WOLFHSM_CFG_DMA build. Use to wrap a test body
+ * whose wc_* calls route through the wolfHSM cryptocb, so both the normal and
+ * DMA server transports get exercised. On a non-DMA build WH_DEV_IDS_ARRAY has
+ * a single entry, so the loop is one pass with no extra cost.
+ *
+ * Declare `int idxVar, devIdVar;` before the loop (C90). The using TU must
+ * include wolfhsm/wh_client.h (for WH_DEV_IDS_ARRAY / WH_NUM_DEVIDS).
+ *
+ * Do NOT use this for code that does not dispatch through the cryptocb --
+ * software reference computations, the explicit *Dma* request/response
+ * handlers, or comm-buffer request/response APIs. Those do not vary by devId
+ * and should run once (typically on WH_DEV_ID).
+ */
+#define WH_TEST_FOREACH_DEVID(idxVar, devIdVar) \
+ for ((idxVar) = 0; \
+ ((idxVar) < WH_NUM_DEVIDS) && \
+ (((devIdVar) = WH_DEV_IDS_ARRAY[(idxVar)]) || 1); \
+ (idxVar)++)
+
+
/*
* Helper macro for test error propagation
* Mimics "assert" semantics by evaluating the "statement" argument, and if not