diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c
index 22f52360f..118d5a9a7 100644
--- a/src/wh_client_crypto.c
+++ b/src/wh_client_crypto.c
@@ -3404,6 +3404,11 @@ int wh_Client_Curve25519MakeCacheKey(whClientContext* ctx, uint16_t size,
return WH_ERROR_BADARGS;
}
+ /* Ephemeral keygen belongs to the export path, not the cache path. */
+ if (flags & WH_NVM_FLAGS_EPHEMERAL) {
+ return WH_ERROR_BADARGS;
+ }
+
return _Curve25519MakeKey(ctx, size, inout_key_id, flags, label, label_len,
NULL);
}
@@ -3920,6 +3925,11 @@ int wh_Client_Ed25519MakeCacheKey(whClientContext* ctx, whKeyId* inout_key_id,
return WH_ERROR_BADARGS;
}
+ /* Ephemeral keygen belongs to the export path, not the cache path. */
+ if (flags & WH_NVM_FLAGS_EPHEMERAL) {
+ return WH_ERROR_BADARGS;
+ }
+
return _Ed25519MakeKey(ctx, inout_key_id, flags, label_len, label, NULL);
}
@@ -10324,6 +10334,11 @@ int wh_Client_MlDsaMakeCacheKey(whClientContext* ctx, int size, int level,
return WH_ERROR_BADARGS;
}
+ /* Ephemeral keygen belongs to the export path, not the cache path. */
+ if (flags & WH_NVM_FLAGS_EPHEMERAL) {
+ return WH_ERROR_BADARGS;
+ }
+
return _MlDsaMakeKey(ctx, size, level, inout_key_id, flags, label_len,
label, NULL);
}
@@ -11503,6 +11518,11 @@ int wh_Client_MlKemMakeCacheKey(whClientContext* ctx, int level,
return WH_ERROR_BADARGS;
}
+ /* Ephemeral keygen belongs to the export path, not the cache path. */
+ if (flags & WH_NVM_FLAGS_EPHEMERAL) {
+ return WH_ERROR_BADARGS;
+ }
+
return _MlKemMakeKey(ctx, level, inout_key_id, flags, label_len,
label, NULL);
}
diff --git a/test-refactor/client-server/wh_test_crypto_curve25519.c b/test-refactor/client-server/wh_test_crypto_curve25519.c
index 0bd8ea6eb..7b35a556b 100644
--- a/test-refactor/client-server/wh_test_crypto_curve25519.c
+++ b/test-refactor/client-server/wh_test_crypto_curve25519.c
@@ -467,11 +467,30 @@ static int _whTest_CryptoCurve25519CacheKeyAndExportPublic(whClientContext* ctx)
return ret;
}
+/* Cache keygen must reject WH_NVM_FLAGS_EPHEMERAL */
+static int _whTest_CryptoCurve25519MakeCacheKeyEphemeral(whClientContext* ctx)
+{
+ whKeyId keyId = WH_KEYID_ERASED;
+ int ret;
+
+ ret = wh_Client_Curve25519MakeCacheKey(ctx, CURVE25519_KEYSIZE, &keyId,
+ WH_NVM_FLAGS_EPHEMERAL, NULL, 0);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Curve25519MakeCacheKey with EPHEMERAL returned %d "
+ "(expected BADARGS)\n",
+ ret);
+ return WH_TEST_FAIL;
+ }
+ WH_TEST_PRINT("CURVE25519 CACHE-KEY EPHEMERAL REJECT SUCCESS\n");
+ return 0;
+}
+
int whTest_Crypto_Curve25519(whClientContext* ctx)
{
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519(ctx));
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519ExportPublicKey(ctx));
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519CacheKeyAndExportPublic(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519MakeCacheKeyEphemeral(ctx));
return 0;
}
#endif /* HAVE_CURVE25519 */
diff --git a/test-refactor/client-server/wh_test_crypto_ed25519.c b/test-refactor/client-server/wh_test_crypto_ed25519.c
index a3eb56921..3a779d835 100644
--- a/test-refactor/client-server/wh_test_crypto_ed25519.c
+++ b/test-refactor/client-server/wh_test_crypto_ed25519.c
@@ -727,6 +727,24 @@ static int _whTest_CryptoEd25519BufferTooSmall(whClientContext* ctx)
return ret;
}
+/* Cache keygen must reject WH_NVM_FLAGS_EPHEMERAL */
+static int _whTest_CryptoEd25519MakeCacheKeyEphemeral(whClientContext* ctx)
+{
+ whKeyId keyId = WH_KEYID_ERASED;
+ int ret;
+
+ ret = wh_Client_Ed25519MakeCacheKey(ctx, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0,
+ NULL);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Ed25519MakeCacheKey with EPHEMERAL returned %d "
+ "(expected BADARGS)\n",
+ ret);
+ return WH_TEST_FAIL;
+ }
+ WH_TEST_PRINT("Ed25519 CACHE-KEY EPHEMERAL REJECT SUCCESS\n");
+ return 0;
+}
+
int whTest_Crypto_Ed25519(whClientContext* ctx)
{
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519Inline(ctx));
@@ -737,6 +755,7 @@ int whTest_Crypto_Ed25519(whClientContext* ctx)
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519ExportPublicKey(ctx));
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519CacheKeyAndExportPublic(ctx));
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519BufferTooSmall(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519MakeCacheKeyEphemeral(ctx));
return 0;
}
#endif /* HAVE_ED25519 */
diff --git a/test-refactor/client-server/wh_test_crypto_mldsa.c b/test-refactor/client-server/wh_test_crypto_mldsa.c
index 636962c2a..64f734bd1 100644
--- a/test-refactor/client-server/wh_test_crypto_mldsa.c
+++ b/test-refactor/client-server/wh_test_crypto_mldsa.c
@@ -1319,6 +1319,24 @@ static int _whTest_CryptoMlDsaBufferTooSmall(whClientContext* ctx)
wc_MlDsaKey_Free(key);
return ret;
}
+
+/* Cache keygen must reject WH_NVM_FLAGS_EPHEMERAL */
+static int _whTest_CryptoMlDsaMakeCacheKeyEphemeral(whClientContext* ctx)
+{
+ whKeyId keyId = WH_KEYID_ERASED;
+ int ret;
+
+ ret = wh_Client_MlDsaMakeCacheKey(ctx, 0, WC_ML_DSA_44, &keyId,
+ WH_NVM_FLAGS_EPHEMERAL, 0, NULL);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("MlDsaMakeCacheKey with EPHEMERAL returned %d "
+ "(expected BADARGS)\n",
+ ret);
+ return WH_TEST_FAIL;
+ }
+ WH_TEST_PRINT("ML-DSA CACHE-KEY EPHEMERAL REJECT SUCCESS\n");
+ return 0;
+}
#endif /* make/sign/verify && ML_DSA_44 */
int whTest_Crypto_MlDsa(whClientContext* ctx)
@@ -1345,6 +1363,7 @@ int whTest_Crypto_MlDsa(whClientContext* ctx)
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaCacheKeyAndExportPublic(ctx));
#endif
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaBufferTooSmall(ctx));
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaMakeCacheKeyEphemeral(ctx));
#ifdef WOLFHSM_CFG_DMA
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaDmaClient(ctx));
WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaExportPublicKeyDma(ctx));
diff --git a/test-refactor/client-server/wh_test_crypto_mlkem.c b/test-refactor/client-server/wh_test_crypto_mlkem.c
new file mode 100644
index 000000000..7a252bfb1
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_mlkem.c
@@ -0,0 +1,82 @@
+/*
+ * 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_mlkem.c
+ *
+ * ML-KEM tests routed through the server:
+ * _whTest_CryptoMlKemMakeCacheKeyEphemeral - cache keygen rejects
+ * WH_NVM_FLAGS_EPHEMERAL
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+
+#include
+
+#include "wolfssl/wolfcrypt/settings.h"
+#include "wolfssl/wolfcrypt/types.h"
+#include "wolfssl/wolfcrypt/wc_mlkem.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 WOLFSSL_HAVE_MLKEM
+
+/* Cache keygen must reject WH_NVM_FLAGS_EPHEMERAL */
+static int _whTest_CryptoMlKemMakeCacheKeyEphemeral(whClientContext* ctx)
+{
+ whKeyId keyId = WH_KEYID_ERASED;
+ int ret;
+ /* Valid level so the server level check cannot mask the gate */
+ const int level =
+#if !defined(WOLFSSL_NO_ML_KEM_512)
+ WC_ML_KEM_512;
+#elif !defined(WOLFSSL_NO_ML_KEM_768)
+ WC_ML_KEM_768;
+#else
+ WC_ML_KEM_1024;
+#endif
+
+ ret = wh_Client_MlKemMakeCacheKey(ctx, level, &keyId,
+ WH_NVM_FLAGS_EPHEMERAL, 0, NULL);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("MlKemMakeCacheKey with EPHEMERAL returned %d "
+ "(expected BADARGS)\n",
+ ret);
+ return WH_TEST_FAIL;
+ }
+ WH_TEST_PRINT("ML-KEM CACHE-KEY EPHEMERAL REJECT SUCCESS\n");
+ return 0;
+}
+
+int whTest_Crypto_MlKem(whClientContext* ctx)
+{
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlKemMakeCacheKeyEphemeral(ctx));
+ return 0;
+}
+
+#endif /* WOLFSSL_HAVE_MLKEM */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index fa2994089..a6824e987 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -74,6 +74,7 @@ WH_TEST_DECL(whTest_Crypto_KeyWrap);
WH_TEST_DECL(whTest_Crypto_Keystore);
WH_TEST_DECL(whTest_Crypto_Lms);
WH_TEST_DECL(whTest_Crypto_MlDsa);
+WH_TEST_DECL(whTest_Crypto_MlKem);
WH_TEST_DECL(whTest_Crypto_Rng);
WH_TEST_DECL(whTest_Crypto_Rsa);
WH_TEST_DECL(whTest_Crypto_Sha);
@@ -161,6 +162,7 @@ const whTestCase whTestsClient[] = {
{"whTest_Crypto_Keystore", whTest_Crypto_Keystore},
{"whTest_Crypto_Lms", whTest_Crypto_Lms},
{"whTest_Crypto_MlDsa", whTest_Crypto_MlDsa},
+ {"whTest_Crypto_MlKem", whTest_Crypto_MlKem},
{"whTest_Crypto_Rng", whTest_Crypto_Rng},
{"whTest_Crypto_Rsa", whTest_Crypto_Rsa},
{"whTest_Crypto_Sha", whTest_Crypto_Sha},
diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c
index b688bc811..d4640e642 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -17958,10 +17958,7 @@ int whTest_CryptoKeyRevocationAesCbc(whClientContext* client, WC_RNG* rng)
#endif /* !NO_AES && HAVE_AES_CBC && \
WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS */
-/* Negative tests: every cache-and-export keygen function must reject
- * WH_NVM_FLAGS_EPHEMERAL, a NULL inout_key_id, and a NULL pub with
- * WH_ERROR_BADARGS, before contacting the server. level is passed as 0 for the
- * PQC calls since the argument guards run before any level validation. */
+/* Cache keygen must reject EPHEMERAL flags and NULL args with BADARGS */
static int whTest_CryptoMakeCacheKeyExportPublicArgs(whClientContext* ctx)
{
int ret = 0;
@@ -18010,9 +18007,12 @@ static int whTest_CryptoMakeCacheKeyExportPublicArgs(whClientContext* ctx)
#ifdef HAVE_CURVE25519
if (ret == 0) {
curve25519_key cv[1] = {0};
- if (wh_Client_Curve25519MakeCacheKeyAndExportPublic(
- ctx, CURVE25519_KEYSIZE, &keyId, WH_NVM_FLAGS_EPHEMERAL, NULL, 0,
- cv) != WH_ERROR_BADARGS ||
+ if (wh_Client_Curve25519MakeCacheKey(ctx, CURVE25519_KEYSIZE, &keyId,
+ WH_NVM_FLAGS_EPHEMERAL, NULL,
+ 0) != WH_ERROR_BADARGS ||
+ wh_Client_Curve25519MakeCacheKeyAndExportPublic(
+ ctx, CURVE25519_KEYSIZE, &keyId, WH_NVM_FLAGS_EPHEMERAL, NULL,
+ 0, cv) != WH_ERROR_BADARGS ||
wh_Client_Curve25519MakeCacheKeyAndExportPublic(
ctx, CURVE25519_KEYSIZE, NULL, WH_NVM_FLAGS_NONE, NULL, 0,
cv) != WH_ERROR_BADARGS ||
@@ -18027,11 +18027,14 @@ static int whTest_CryptoMakeCacheKeyExportPublicArgs(whClientContext* ctx)
#ifdef HAVE_ED25519
if (ret == 0) {
ed25519_key ed[1] = {0};
- if (wh_Client_Ed25519MakeCacheKeyAndExportPublic(
+ if (wh_Client_Ed25519MakeCacheKey(ctx, &keyId, WH_NVM_FLAGS_EPHEMERAL,
+ 0, NULL) != WH_ERROR_BADARGS ||
+ wh_Client_Ed25519MakeCacheKeyAndExportPublic(
ctx, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, ed) !=
WH_ERROR_BADARGS ||
wh_Client_Ed25519MakeCacheKeyAndExportPublic(
- ctx, NULL, WH_NVM_FLAGS_NONE, 0, NULL, ed) != WH_ERROR_BADARGS ||
+ ctx, NULL, WH_NVM_FLAGS_NONE, 0, NULL, ed) !=
+ WH_ERROR_BADARGS ||
wh_Client_Ed25519MakeCacheKeyAndExportPublic(
ctx, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, NULL) !=
WH_ERROR_BADARGS) {
@@ -18043,7 +18046,19 @@ static int whTest_CryptoMakeCacheKeyExportPublicArgs(whClientContext* ctx)
#ifdef WOLFSSL_MLDSA_PUBLIC_KEY
if (ret == 0) {
wc_MlDsaKey mldsa[1] = {0};
- if (wh_Client_MlDsaMakeCacheKeyAndExportPublic(
+ /* Valid level so the server level check cannot mask the gate */
+ const int mldsaLevel =
+#if !defined(WOLFSSL_NO_ML_DSA_44)
+ WC_ML_DSA_44;
+#elif !defined(WOLFSSL_NO_ML_DSA_65)
+ WC_ML_DSA_65;
+#else
+ WC_ML_DSA_87;
+#endif
+ if (wh_Client_MlDsaMakeCacheKey(ctx, 0, mldsaLevel, &keyId,
+ WH_NVM_FLAGS_EPHEMERAL, 0,
+ NULL) != WH_ERROR_BADARGS ||
+ wh_Client_MlDsaMakeCacheKeyAndExportPublic(
ctx, 0, 0, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, mldsa) !=
WH_ERROR_BADARGS ||
wh_Client_MlDsaMakeCacheKeyAndExportPublic(
@@ -18075,7 +18090,19 @@ static int whTest_CryptoMakeCacheKeyExportPublicArgs(whClientContext* ctx)
#ifdef WOLFSSL_HAVE_MLKEM
if (ret == 0) {
MlKemKey mlkem[1] = {0};
- if (wh_Client_MlKemMakeCacheKeyAndExportPublic(
+ /* Valid level so the server level check cannot mask the gate */
+ const int mlkemLevel =
+#if !defined(WOLFSSL_NO_ML_KEM_512)
+ WC_ML_KEM_512;
+#elif !defined(WOLFSSL_NO_ML_KEM_768)
+ WC_ML_KEM_768;
+#else
+ WC_ML_KEM_1024;
+#endif
+ if (wh_Client_MlKemMakeCacheKey(ctx, mlkemLevel, &keyId,
+ WH_NVM_FLAGS_EPHEMERAL, 0,
+ NULL) != WH_ERROR_BADARGS ||
+ wh_Client_MlKemMakeCacheKeyAndExportPublic(
ctx, 0, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, mlkem) !=
WH_ERROR_BADARGS ||
wh_Client_MlKemMakeCacheKeyAndExportPublic(