diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c
index db0871605..f72257c04 100644
--- a/src/wh_server_cert.c
+++ b/src/wh_server_cert.c
@@ -481,11 +481,19 @@ static int _verifyChainAgainstCmStore(
}
/* This is the leaf cert, so if requested, cache the public key */
else if (flags & WH_CERT_FLAGS_CACHE_LEAF_PUBKEY) {
+ /* The whole id allocation + cache import chain below is already
+ * atomic: every caller of this function reaches it through the
+ * cert request dispatch, which holds WH_SERVER_NVM_LOCK across
+ * the entire verify call. The NVM lock is non-recursive, so we
+ * must NOT re-acquire it here -- use the unlocked GetUniqueId +
+ * GetCacheSlotChecked building blocks under the caller's lock.
+ */
/* If the keyId is erased, get a unique key id for the public
* key. Otherwise cache the key using the provided keyId */
if (WH_KEYID_ISERASED(*inout_keyId)) {
rc = wh_Server_KeystoreGetUniqueId(server, inout_keyId);
if (rc != WH_ERROR_OK) {
+ wc_FreeDecodedCert(&dc);
return rc;
}
}
diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c
index 5755676c3..e39f47624 100644
--- a/src/wh_server_crypto.c
+++ b/src/wh_server_crypto.c
@@ -227,6 +227,21 @@ static int _HandleMlKemDecapsDma(whServerContext* ctx, uint16_t magic,
#endif /* WOLFHSM_CFG_DMA */
#endif /* WOLFSSL_HAVE_MLKEM */
+static void _CryptoEvictKeyLocked(whServerContext* ctx, whKeyId keyId)
+{
+ int ret;
+
+ if ((ctx == NULL) || WH_KEYID_ISERASED(keyId)) {
+ return;
+ }
+
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ (void)wh_Server_KeystoreEvictKey(ctx, keyId);
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ }
+}
+
/** Public server crypto functions */
#ifndef NO_RSA
@@ -298,6 +313,34 @@ int wh_Server_CacheExportRsaKey(whServerContext* ctx, whKeyId keyId,
return ret;
}
+int wh_Server_CacheExportRsaKeyEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage, RsaKey* key)
+{
+ uint8_t* cacheBuf;
+ whNvmMetadata* cacheMeta;
+ int ret;
+
+ if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) {
+ return WH_ERROR_BADARGS;
+ }
+ /* Freshen, check usage and deserialize under one hold of the NVM lock so
+ * the policy verdict, the metadata length and the key bytes all come from
+ * the same snapshot of the shared cache slot. This matters for the global
+ * shared cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Crypto_RsaDeserializeKeyDer(cacheMeta->len, cacheBuf, key);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ }
+ return ret;
+}
+
#ifdef WOLFSSL_KEY_GEN
static int _HandleRsaKeyGen(whServerContext* ctx, uint16_t magic, int devId,
const void* cryptoDataIn, uint16_t inSize,
@@ -356,21 +399,24 @@ static int _HandleRsaKeyGen(whServerContext* ctx, uint16_t magic, int devId,
}
else {
/* Must import the key into the cache and return keyid */
- if (WH_KEYID_ISERASED(key_id)) {
- /* Generate a new id */
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- WH_DEBUG_SERVER_VERBOSE("RsaKeyGen UniqueId: keyId:%u, ret:%d\n", key_id, ret);
- if (ret != WH_ERROR_OK) {
- /* Early return on unique ID generation failure */
- wc_FreeRsaKey(rsa);
- return ret;
+ /* Hold the NVM lock so id allocation and cache import are
+ * atomic with respect to other server contexts under
+ * THREADSAFE. This matters for the shared global cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(key_id)) {
+ /* Generate a new id */
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
+ WH_DEBUG_SERVER_VERBOSE(
+ "RsaKeyGen UniqueId: keyId:%u, ret:%d\n", key_id,
+ ret);
}
- }
-
- if (ret == 0) {
- ret = wh_Server_CacheImportRsaKey(ctx, rsa, key_id, flags,
- label_size, label);
- }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_CacheImportRsaKey(
+ ctx, rsa, key_id, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
WH_DEBUG_SERVER_VERBOSE("RsaKeyGen CacheKeyRsa: keyId:%u, ret:%d\n", key_id, ret);
if (ret == 0) {
res.keyId = wh_KeyId_TranslateToClient(key_id);
@@ -448,66 +494,61 @@ static int _HandleRsaFunction(whServerContext* ctx, uint16_t magic, int devId,
return BAD_FUNC_ARG;
}
- /* Validate key usage policy based on RSA operation type */
- if (!WH_KEYID_ISERASED(key_id)) {
- whNvmFlags requiredUsage = WH_NVM_FLAGS_NONE;
- switch (op_type) {
- case RSA_PUBLIC_ENCRYPT:
- case RSA_PRIVATE_ENCRYPT:
- requiredUsage = WH_NVM_FLAGS_USAGE_ENCRYPT;
- break;
- case RSA_PUBLIC_DECRYPT:
- case RSA_PRIVATE_DECRYPT:
- requiredUsage = WH_NVM_FLAGS_USAGE_DECRYPT;
- break;
- }
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id, requiredUsage);
- if (ret != WH_ERROR_OK) {
+ /* Determine the required key usage based on the RSA operation type */
+ whNvmFlags requiredUsage = WH_NVM_FLAGS_NONE;
+ switch (op_type) {
+ case RSA_PUBLIC_ENCRYPT:
+ case RSA_PRIVATE_ENCRYPT:
+ requiredUsage = WH_NVM_FLAGS_USAGE_ENCRYPT;
+ break;
+ case RSA_PUBLIC_DECRYPT:
+ case RSA_PRIVATE_DECRYPT:
+ requiredUsage = WH_NVM_FLAGS_USAGE_DECRYPT;
+ break;
+ }
+
+ /* init rsa key */
+ ret = wc_InitRsaKey_ex(rsa, NULL, devId);
+ /* load the key from the keystore, enforcing the usage policy against the
+ * same locked snapshot of the key that is exported */
+ if (ret == 0) {
+ ret =
+ wh_Server_CacheExportRsaKeyEnforce(ctx, key_id, requiredUsage, rsa);
+ if (ret == WH_ERROR_USAGE) {
/* Currently wolfCrypt doesn't have a way for crypto callbacks to
distinguish if a low level RSA operation (like encrypt/decrypt) is
being performed as part of a higher level operation like
sign/verify. Until that information is propagated to the
callback, the usage flags are treated as equivalent. */
- if (ret == WH_ERROR_USAGE) {
- if (op_type == RSA_PUBLIC_DECRYPT) {
- /* Decrypt usage flag wasn't set so this might be a verify
- * operation. Attempt to enforce against the verify flag */
- ret = wh_Server_KeystoreFindEnforceKeyUsage(
- ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY);
- }
- else if (op_type == RSA_PRIVATE_ENCRYPT) {
- /* Encrypt usage flag wasn't set so this might be a sign
- * operation. Attempt to enforce against the sign flag */
- ret = wh_Server_KeystoreFindEnforceKeyUsage(
- ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN);
- }
+ if (op_type == RSA_PUBLIC_DECRYPT) {
+ /* Decrypt usage flag wasn't set so this might be a verify
+ * operation. Attempt to enforce against the verify flag */
+ ret = wh_Server_CacheExportRsaKeyEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, rsa);
}
- if (ret != WH_ERROR_OK) {
- goto cleanup;
+ else if (op_type == RSA_PRIVATE_ENCRYPT) {
+ /* Encrypt usage flag wasn't set so this might be a sign
+ * operation. Attempt to enforce against the sign flag */
+ ret = wh_Server_CacheExportRsaKeyEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, rsa);
}
}
- }
-
- /* init rsa key */
- ret = wc_InitRsaKey_ex(rsa, NULL, devId);
- /* load the key from the keystore */
- if (ret == 0) {
- ret = wh_Server_CacheExportRsaKey(ctx, key_id, rsa);
- WH_DEBUG_SERVER_VERBOSE("CacheExportRsaKey keyid:%u, ret:%d\n", key_id, ret);
+ WH_DEBUG_SERVER_VERBOSE("CacheExportRsaKey keyid:%u, ret:%d\n", key_id,
+ ret);
if (ret == 0) {
/* do the rsa operation */
- ret = wc_RsaFunction(in, in_len, out, &out_len,
- op_type, rsa, ctx->crypto->rng);
- WH_DEBUG_SERVER_VERBOSE("RsaFunction in:%p %u, out:%p, opType:%d, outLen:%d, ret:%d\n",
- in, in_len, out, op_type, out_len, ret);
+ ret = wc_RsaFunction(in, in_len, out, &out_len, op_type, rsa,
+ ctx->crypto->rng);
+ WH_DEBUG_SERVER_VERBOSE(
+ "RsaFunction in:%p %u, out:%p, opType:%d, outLen:%d, ret:%d\n",
+ in, in_len, out, op_type, out_len, ret);
}
/* free the key */
wc_FreeRsaKey(rsa);
}
-cleanup:
if (evict != 0) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
whMessageCrypto_RsaResponse res;
@@ -550,9 +591,10 @@ static int _HandleRsaGetSize(whServerContext* ctx, uint16_t magic, int devId,
/* init rsa key */
ret = wc_InitRsaKey_ex(rsa, NULL, devId);
- /* load the key from the keystore */
+ /* load the key from the keystore (no usage requirement for size query) */
if (ret == 0) {
- ret = wh_Server_CacheExportRsaKey(ctx, key_id, rsa);
+ ret = wh_Server_CacheExportRsaKeyEnforce(ctx, key_id, WH_NVM_FLAGS_NONE,
+ rsa);
/* get the size */
if (ret == 0) {
key_size = wc_RsaEncryptSize(rsa);
@@ -566,7 +608,7 @@ static int _HandleRsaGetSize(whServerContext* ctx, uint16_t magic, int devId,
WH_DEBUG_SERVER_VERBOSE("evicting temp key:%x options:%u evict:%u\n",
key_id, options, evict);
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
res.keySize = key_size;
@@ -641,6 +683,34 @@ int wh_Server_EccKeyCacheExport(whServerContext* ctx, whKeyId keyId,
}
return ret;
}
+
+int wh_Server_EccKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage, ecc_key* key)
+{
+ uint8_t* cacheBuf;
+ whNvmMetadata* cacheMeta;
+ int ret;
+
+ if ((ctx == NULL) || (key == NULL) || WH_KEYID_ISERASED(keyId)) {
+ return WH_ERROR_BADARGS;
+ }
+ /* Freshen, check usage and deserialize under one hold of the NVM lock so
+ * the policy verdict, the metadata length and the key bytes all come from
+ * the same snapshot of the shared cache slot. This matters for the shared
+ * global cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Crypto_EccDeserializeKeyDer(cacheBuf, cacheMeta->len, key);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ }
+ return ret;
+}
#endif /* HAVE_ECC */
#ifdef HAVE_ED25519
@@ -698,6 +768,37 @@ int wh_Server_CacheExportEd25519Key(whServerContext* ctx, whKeyId keyId,
}
return ret;
}
+
+int wh_Server_CacheExportEd25519KeyEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ ed25519_key* key)
+{
+ uint8_t* cacheBuf = NULL;
+ whNvmMetadata* cacheMeta;
+ int ret;
+
+ if ((ctx == NULL) || (key == NULL) || WH_KEYID_ISERASED(keyId)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Freshen, check usage and deserialize under one hold of the NVM lock so
+ * the policy verdict, the metadata length and the key bytes all come from
+ * the same snapshot of the shared cache slot. This matters for the shared
+ * global cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Crypto_Ed25519DeserializeKeyDer(cacheBuf, cacheMeta->len,
+ key);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ }
+ return ret;
+}
#endif /* HAVE_ED25519 */
#ifdef HAVE_CURVE25519
@@ -762,6 +863,38 @@ int wh_Server_CacheExportCurve25519Key(whServerContext* server, whKeyId keyId,
}
return ret;
}
+
+int wh_Server_CacheExportCurve25519KeyEnforce(whServerContext* server,
+ whKeyId keyId,
+ whNvmFlags requiredUsage,
+ curve25519_key* key)
+{
+ uint8_t* cacheBuf;
+ whNvmMetadata* cacheMeta;
+ int ret;
+
+ if ((server == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) {
+ return WH_ERROR_BADARGS;
+ }
+ /* Freshen, check usage and deserialize under one hold of the NVM lock so
+ * the policy verdict, the metadata length and the key bytes all come from
+ * the same snapshot of the shared cache slot. This matters for the shared
+ * global cache. */
+ ret = WH_SERVER_NVM_LOCK(server);
+ if (ret == WH_ERROR_OK) {
+ ret =
+ wh_Server_KeystoreFreshenKey(server, keyId, &cacheBuf, &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Crypto_Curve25519DeserializeKey(cacheBuf, cacheMeta->len,
+ key);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(server);
+ }
+ return ret;
+}
#endif /* HAVE_CURVE25519 */
#ifdef WOLFSSL_HAVE_MLDSA
@@ -829,6 +962,38 @@ int wh_Server_MlDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId,
}
return ret;
}
+
+int wh_Server_MlDsaKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ wc_MlDsaKey* key)
+{
+ uint8_t* cacheBuf;
+ whNvmMetadata* cacheMeta;
+ int ret;
+
+ if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Freshen, check usage and deserialize under one hold of the NVM lock so
+ * the policy verdict, the metadata length and the key bytes all come from
+ * the same snapshot of the shared cache slot. This matters for the shared
+ * global cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret =
+ wh_Crypto_MlDsaDeserializeKeyDer(cacheBuf, cacheMeta->len, key);
+ WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ }
+ return ret;
+}
#endif /* WOLFSSL_HAVE_MLDSA */
#ifdef WOLFSSL_HAVE_MLKEM
@@ -883,6 +1048,37 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId,
}
return ret;
}
+
+int wh_Server_MlKemKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ MlKemKey* key)
+{
+ uint8_t* cacheBuf;
+ whNvmMetadata* cacheMeta;
+ int ret;
+
+ if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Freshen, check usage and deserialize under one hold of the NVM lock so
+ * the policy verdict, the metadata length and the key bytes all come from
+ * the same snapshot of the shared cache slot. This matters for the shared
+ * global cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Crypto_MlKemDeserializeKey(cacheBuf, cacheMeta->len, key);
+ WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ }
+ return ret;
+}
#endif /* WOLFSSL_HAVE_MLKEM */
/* The sign path (and its slot callbacks) is unavailable in verify-only builds;
@@ -1210,20 +1406,24 @@ static int _HandleEccKeyGen(whServerContext* ctx, uint16_t magic, int devId,
/* Must import the key into the cache and return keyid
*/
res_size = 0;
- if (WH_KEYID_ISERASED(key_id)) {
- /* Generate a new id */
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", key_id, ret);
- if (ret != WH_ERROR_OK) {
- /* Early return on unique ID generation failure */
- wc_ecc_free(key);
- return ret;
+ /* Hold the NVM lock so id allocation and cache import are
+ * atomic with respect to other server contexts under
+ * THREADSAFE. This matters for the shared
+ * global cache. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(key_id)) {
+ /* Generate a new id */
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
+ WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", key_id,
+ ret);
}
- }
- if (ret == 0) {
- ret = wh_Server_EccKeyCacheImport(ctx, key, key_id, flags,
- label_size, label);
- }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_EccKeyCacheImport(
+ ctx, key, key_id, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
WH_DEBUG_SERVER("CacheImport: keyId:%u, ret:%d\n", key_id, ret);
/* TODO: RSA has the following, should we do the same? */
/*
@@ -1281,15 +1481,6 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic,
whNvmFlags flags = (whNvmFlags)req.flags;
int cache = !(flags & WH_NVM_FLAGS_EPHEMERAL);
- /* Validate key usage policy for key derivation (private key) */
- if (!WH_KEYID_ISERASED(prv_key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, prv_key_id,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
/* Response message */
byte* res_out =
(byte*)cryptoDataOut + sizeof(whMessageCrypto_EcdhResponse);
@@ -1305,12 +1496,15 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic,
/* set rng */
ret = wc_ecc_set_rng(prv_key, ctx->crypto->rng);
if (ret == 0) {
- /* load the private key */
- ret = wh_Server_EccKeyCacheExport(ctx, prv_key_id, prv_key);
- }
- if (ret == WH_ERROR_OK) {
- /* load the public key */
- ret = wh_Server_EccKeyCacheExport(ctx, pub_key_id, pub_key);
+ /* load the private key, enforcing the derive usage policy
+ * against the same locked snapshot that is exported */
+ ret = wh_Server_EccKeyCacheExportEnforce(
+ ctx, prv_key_id, WH_NVM_FLAGS_USAGE_DERIVE, prv_key);
+ if (ret == WH_ERROR_OK) {
+ /* load the public key (no usage requirement) */
+ ret = wh_Server_EccKeyCacheExportEnforce(
+ ctx, pub_key_id, WH_NVM_FLAGS_NONE, pub_key);
+ }
}
if (ret == WH_ERROR_OK) {
/* make shared secret */
@@ -1356,14 +1550,13 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic,
}
}
}
-cleanup:
if (evict_pub) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, pub_key_id);
+ _CryptoEvictKeyLocked(ctx, pub_key_id);
}
if (evict_prv) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, prv_key_id);
+ _CryptoEvictKeyLocked(ctx, prv_key_id);
}
if (ret == 0) {
whMessageCrypto_EcdhResponse res;
@@ -1423,15 +1616,6 @@ static int _HandleEccSign(whServerContext* ctx, uint16_t magic, int devId,
uint32_t options = req.options;
int evict = !!(options & WH_MESSAGE_CRYPTO_ECCSIGN_OPTIONS_EVICT);
- /* Validate key usage policy for signing */
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_SIGN);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
/* Response message */
byte* res_out =
(byte*)cryptoDataOut + sizeof(whMessageCrypto_EccSignResponse);
@@ -1442,8 +1626,10 @@ static int _HandleEccSign(whServerContext* ctx, uint16_t magic, int devId,
/* init private key */
ret = wc_ecc_init_ex(key, NULL, devId);
if (ret == 0) {
- /* load the private key */
- ret = wh_Server_EccKeyCacheExport(ctx, key_id, key);
+ /* load the private key, enforcing the sign usage policy against the
+ * same locked snapshot that is exported */
+ ret = wh_Server_EccKeyCacheExportEnforce(ctx, key_id,
+ WH_NVM_FLAGS_USAGE_SIGN, key);
if (ret == WH_ERROR_OK) {
WH_DEBUG_SERVER_VERBOSE("EccSign: key_id=%x, in_len=%u, res_len=%u, ret=%d\n",
key_id, (unsigned)in_len, (unsigned)res_len, ret);
@@ -1455,10 +1641,9 @@ static int _HandleEccSign(whServerContext* ctx, uint16_t magic, int devId,
}
wc_ecc_free(key);
}
-cleanup:
if (evict != 0) {
/* typecasting to void so that not overwrite ret */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
whMessageCrypto_EccSignResponse res;
@@ -1518,15 +1703,6 @@ static int _HandleEccVerify(whServerContext* ctx, uint16_t magic, int devId,
int export_pub_key =
!!(options & WH_MESSAGE_CRYPTO_ECCVERIFY_OPTIONS_EXPORTPUB);
- /* Validate key usage policy for verification */
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_VERIFY);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
/* Response message */
byte* res_pub =
(uint8_t*)(cryptoDataOut) + sizeof(whMessageCrypto_EccVerifyResponse);
@@ -1538,8 +1714,10 @@ static int _HandleEccVerify(whServerContext* ctx, uint16_t magic, int devId,
/* init public key */
ret = wc_ecc_init_ex(key, NULL, devId);
if (ret == 0) {
- /* load the public key */
- ret = wh_Server_EccKeyCacheExport(ctx, key_id, key);
+ /* load the public key, enforcing the verify usage policy against the
+ * same locked snapshot that is exported */
+ ret = wh_Server_EccKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key);
if (ret == WH_ERROR_OK) {
/* verify the signature */
ret = wc_ecc_verify_hash(req_sig, sig_len, req_hash, hash_len,
@@ -1566,10 +1744,9 @@ static int _HandleEccVerify(whServerContext* ctx, uint16_t magic, int devId,
wc_ecc_free(key);
}
-cleanup:
if (evict != 0) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
res.pubSz = pub_size;
@@ -1721,6 +1898,30 @@ int wh_Server_CmacKdfKeyCacheImport(whServerContext* ctx,
}
#endif /* HAVE_CMAC_KDF */
+#if defined(HAVE_HKDF) || defined(HAVE_CMAC_KDF)
+/* Bound on KDF inputs supplied by key ID.
+ *
+ * When a KDF input (HKDF IKM, CMAC-KDF salt or Z) is named by key ID rather
+ * than passed inline, the handler copies the key out of the cache slot into a
+ * private buffer and releases the NVM lock before calling wolfCrypt. The copy
+ * is what makes the usage-policy verdict and the key bytes come from the same
+ * snapshot; deriving straight from the live slot instead would either race a
+ * concurrent evict/overwrite or require holding the NVM lock across wolfCrypt.
+ *
+ * The consequence is that a cached KDF input larger than its private buffer is
+ * rejected with WH_ERROR_NOSPACE. WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE bounds
+ * the IKM and Z buffers; it covers every secret wolfHSM can itself cache as a
+ * derive input, the largest being a P-521 ECDH shared secret at MAX_ECC_BYTES,
+ *
+ */
+#ifdef HAVE_ECC
+WH_UTILS_STATIC_ASSERT(
+ WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE >= MAX_ECC_BYTES,
+ "WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE too small to hold an ECDH shared "
+ "secret as a cached KDF input");
+#endif /* HAVE_ECC */
+#endif /* HAVE_HKDF || HAVE_CMAC_KDF */
+
#ifdef HAVE_HKDF
static int _HandleHkdf(whServerContext* ctx, uint16_t magic, int devId,
const void* cryptoDataIn, uint16_t inSize,
@@ -1776,38 +1977,39 @@ static int _HandleHkdf(whServerContext* ctx, uint16_t magic, int devId,
const uint8_t* info = salt + saltSz;
/* Buffer for cached key if needed */
- uint8_t* cachedKeyBuf = NULL;
- whNvmMetadata* cachedKeyMeta = NULL;
+ uint8_t cachedKeyBuf[WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE];
+ whNvmMetadata cachedKeyMeta[1];
+
+ /* Get pointer to where output data would be stored (after response struct).
+ * Declared before the first goto so no jump skips an initialization. */
+ uint8_t* out =
+ (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_HkdfResponse);
+ uint16_t max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN -
+ ((uint8_t*)out - (uint8_t*)cryptoDataOut));
/* Check if we should use cached key as input */
if (inKeySz == 0 && !WH_KEYID_ISERASED(keyIdIn)) {
- /* Grab references to key in the cache */
- ret = wh_Server_KeystoreFreshenKey(ctx, keyIdIn, &cachedKeyBuf,
- &cachedKeyMeta);
+ /* Copy the derive input key into a private buffer, enforcing the
+ * derive usage policy against the same locked snapshot that is read.
+ * HKDF then runs on the private copy after the lock is released.
+ * An IKM larger than cachedKeyBuf yields WH_ERROR_NOSPACE; see the
+ * bound on cached KDF inputs documented above _HandleHkdf(). */
+ uint32_t cachedKeyLen = sizeof(cachedKeyBuf);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyIdIn, WH_NVM_FLAGS_USAGE_DERIVE, cachedKeyMeta,
+ cachedKeyBuf, &cachedKeyLen);
if (ret != WH_ERROR_OK) {
- return ret;
- }
- /* Validate key usage policy for key derivation (input key) */
- ret = wh_Server_KeystoreEnforceKeyUsage(cachedKeyMeta,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- return ret;
+ goto cleanup;
}
/* Update inKey pointer and size to use cached key */
inKey = cachedKeyBuf;
inKeySz = cachedKeyMeta->len;
}
- /* Get pointer to where output data would be stored (after response struct)
- */
- uint8_t* out =
- (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_HkdfResponse);
- uint16_t max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN -
- ((uint8_t*)out - (uint8_t*)cryptoDataOut));
-
/* Check if output size is valid */
if (outSz > max_size) {
- return WH_ERROR_BADARGS;
+ ret = WH_ERROR_BADARGS;
+ goto cleanup;
}
/* Generate the key into the output buffer */
@@ -1823,20 +2025,22 @@ static int _HandleHkdf(whServerContext* ctx, uint16_t magic, int devId,
}
else {
/* Must import the key into the cache and return keyid */
- if (WH_KEYID_ISERASED(key_id)) {
- /* Generate a new id */
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- WH_DEBUG_SERVER_VERBOSE("HkdfKeyGen UniqueId: keyId:%u, ret:%d\n", key_id, ret);
- if (ret != WH_ERROR_OK) {
- /* Early return on unique ID generation failure */
- return ret;
+ /* Hold the NVM lock so id allocation and cache import are atomic
+ * with respect to other server contexts under THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(key_id)) {
+ /* Generate a new id */
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
+ WH_DEBUG_SERVER_VERBOSE(
+ "HkdfKeyGen UniqueId: keyId:%u, ret:%d\n", key_id, ret);
}
- }
-
- if (ret == 0) {
- ret = wh_Server_HkdfKeyCacheImport(ctx, out, outSz, key_id,
- flags, label_size, label);
- }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_HkdfKeyCacheImport(
+ ctx, out, outSz, key_id, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
WH_DEBUG_SERVER_VERBOSE("HkdfKeyGen CacheImport: keyId:%u, ret:%d\n", key_id, ret);
if (ret == WH_ERROR_OK) {
res.keyIdOut = wh_KeyId_TranslateToClient(key_id);
@@ -1857,6 +2061,9 @@ static int _HandleHkdf(whServerContext* ctx, uint16_t magic, int devId,
}
}
+cleanup:
+ /* The IKM copy is plaintext key material, so don't leave it on the stack */
+ wc_ForceZero(cachedKeyBuf, sizeof(cachedKeyBuf));
return ret;
}
#endif /* HAVE_HKDF */
@@ -1914,25 +2121,35 @@ static int _HandleCmacKdf(whServerContext* ctx, uint16_t magic, int devId,
const uint8_t* z = salt + saltSz;
const uint8_t* fixedInfo = z + zSz;
- uint8_t* cachedSaltBuf = NULL;
- whNvmMetadata* cachedSaltMeta = NULL;
- uint8_t* cachedZBuf = NULL;
- whNvmMetadata* cachedZMeta = NULL;
+ /* The salt is a CMAC key, so wolfCrypt accepts only an AES key size here */
+ uint8_t cachedSaltBuf[AES_256_KEY_SIZE];
+ whNvmMetadata cachedSaltMeta[1];
+ uint8_t cachedZBuf[WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE];
+ whNvmMetadata cachedZMeta[1];
+
+ /* Declared before the first goto so no jump skips an initialization */
+ uint8_t* out =
+ (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_CmacKdfResponse);
+ uint16_t max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN -
+ ((uint8_t*)out - (uint8_t*)cryptoDataOut));
if (saltSz == 0) {
if (WH_KEYID_ISERASED(saltKeyId)) {
- return WH_ERROR_BADARGS;
- }
- ret = wh_Server_KeystoreFreshenKey(ctx, saltKeyId, &cachedSaltBuf,
- &cachedSaltMeta);
- if (ret != WH_ERROR_OK) {
- return ret;
+ ret = WH_ERROR_BADARGS;
+ goto cleanup;
}
- /* Validate key usage policy for cached salt */
- ret = wh_Server_KeystoreEnforceKeyUsage(cachedSaltMeta,
- WH_NVM_FLAGS_USAGE_DERIVE);
+ /* Copy the derive salt into a private buffer, enforcing the derive
+ * usage policy against the same locked snapshot that is read. The KDF
+ * then runs on the private copy after the lock is released.
+ * A salt too big for cachedSaltBuf yields WH_ERROR_NOSPACE, where it
+ * would otherwise reach wc_KDA_KDF_twostep_cmac() and be rejected as
+ * BAD_FUNC_ARG. Both are failures; only the code differs. */
+ uint32_t cachedSaltLen = sizeof(cachedSaltBuf);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, saltKeyId, WH_NVM_FLAGS_USAGE_DERIVE, cachedSaltMeta,
+ cachedSaltBuf, &cachedSaltLen);
if (ret != WH_ERROR_OK) {
- return ret;
+ goto cleanup;
}
salt = cachedSaltBuf;
saltSz = cachedSaltMeta->len;
@@ -1940,34 +2157,33 @@ static int _HandleCmacKdf(whServerContext* ctx, uint16_t magic, int devId,
if (zSz == 0) {
if (WH_KEYID_ISERASED(zKeyId)) {
- return WH_ERROR_BADARGS;
- }
- ret = wh_Server_KeystoreFreshenKey(ctx, zKeyId, &cachedZBuf,
- &cachedZMeta);
- if (ret != WH_ERROR_OK) {
- return ret;
+ ret = WH_ERROR_BADARGS;
+ goto cleanup;
}
- /* Validate key usage policy for key derivation (Z key) */
- ret = wh_Server_KeystoreEnforceKeyUsage(cachedZMeta,
- WH_NVM_FLAGS_USAGE_DERIVE);
+ /* Copy the derive Z value into a private buffer, enforcing the derive
+ * usage policy against the same locked snapshot that is read. The KDF
+ * then runs on the private copy after the lock is released.
+ * A Z larger than cachedZBuf yields WH_ERROR_NOSPACE; see the bound on
+ * cached KDF inputs documented above _HandleHkdf(). */
+ uint32_t cachedZLen = sizeof(cachedZBuf);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, zKeyId, WH_NVM_FLAGS_USAGE_DERIVE, cachedZMeta, cachedZBuf,
+ &cachedZLen);
if (ret != WH_ERROR_OK) {
- return ret;
+ goto cleanup;
}
z = cachedZBuf;
zSz = cachedZMeta->len;
}
if ((salt == NULL) || (z == NULL) || (outSz == 0)) {
- return WH_ERROR_BADARGS;
+ ret = WH_ERROR_BADARGS;
+ goto cleanup;
}
- uint8_t* out =
- (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_CmacKdfResponse);
- uint16_t max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN -
- ((uint8_t*)out - (uint8_t*)cryptoDataOut));
-
if (outSz > max_size) {
- return WH_ERROR_BADARGS;
+ ret = WH_ERROR_BADARGS;
+ goto cleanup;
}
ret = wc_KDA_KDF_twostep_cmac(salt, saltSz, z, zSz,
@@ -1980,15 +2196,19 @@ static int _HandleCmacKdf(whServerContext* ctx, uint16_t magic, int devId,
res.outSz = outSz;
}
else {
- if (WH_KEYID_ISERASED(keyIdOut)) {
- ret = wh_Server_KeystoreGetUniqueId(ctx, &keyIdOut);
- if (ret != WH_ERROR_OK) {
- return ret;
+ /* Hold the NVM lock so id allocation and cache import are atomic
+ * with respect to other server contexts under THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(keyIdOut)) {
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &keyIdOut);
}
- }
-
- ret = wh_Server_CmacKdfKeyCacheImport(ctx, out, outSz, keyIdOut,
- flags, label_size, label);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_CmacKdfKeyCacheImport(
+ ctx, out, outSz, keyIdOut, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
if (ret == WH_ERROR_OK) {
res.keyIdOut = wh_KeyId_TranslateToClient(keyIdOut);
res.outSz = 0;
@@ -2005,6 +2225,11 @@ static int _HandleCmacKdf(whServerContext* ctx, uint16_t magic, int devId,
}
}
+cleanup:
+ /* The salt and Z copies are plaintext derive secrets, so don't leave them
+ * on the stack */
+ wc_ForceZero(cachedSaltBuf, sizeof(cachedSaltBuf));
+ wc_ForceZero(cachedZBuf, sizeof(cachedZBuf));
return ret;
}
#endif /* HAVE_CMAC_KDF */
@@ -2060,22 +2285,23 @@ static int _HandleCurve25519KeyGen(whServerContext* ctx, uint16_t magic,
else {
ser_size = 0;
/* Must import the key into the cache and return keyid */
- if (WH_KEYID_ISERASED(key_id)) {
- /* Generate a new id */
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n",
- key_id, ret);
- if (ret != WH_ERROR_OK) {
- /* Early return on unique ID generation failure */
- wc_curve25519_free(key);
- return ret;
+ /* Hold the NVM lock so id allocation and cache import are
+ * atomic with respect to other server contexts under
+ * THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(key_id)) {
+ /* Generate a new id */
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
+ WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", key_id,
+ ret);
}
- }
-
- if (ret == 0) {
- ret = wh_Server_CacheImportCurve25519Key(
- ctx, key, key_id, flags, label_size, label);
- }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_CacheImportCurve25519Key(
+ ctx, key, key_id, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
WH_DEBUG_SERVER_VERBOSE("CacheImport: keyId:%u, ret:%d\n",
key_id, ret);
}
@@ -2133,15 +2359,6 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic,
whNvmFlags flags = (whNvmFlags)req.flags;
int cache = !(flags & WH_NVM_FLAGS_EPHEMERAL);
- /* Validate key usage policy for key derivation (private key) */
- if (!WH_KEYID_ISERASED(prv_key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, prv_key_id,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
/* Response message */
uint8_t* res_out = (uint8_t*)cryptoDataOut +
sizeof(whMessageCrypto_Curve25519Response);
@@ -2162,10 +2379,15 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic,
}
#endif
if (ret == 0) {
- ret = wh_Server_CacheExportCurve25519Key(ctx, prv_key_id, priv);
- }
- if (ret == 0) {
- ret = wh_Server_CacheExportCurve25519Key(ctx, pub_key_id, pub);
+ /* load the private key, enforcing the derive usage policy
+ * against the same locked snapshot that is exported */
+ ret = wh_Server_CacheExportCurve25519KeyEnforce(
+ ctx, prv_key_id, WH_NVM_FLAGS_USAGE_DERIVE, priv);
+ if (ret == WH_ERROR_OK) {
+ /* load the public key (no usage requirement) */
+ ret = wh_Server_CacheExportCurve25519KeyEnforce(
+ ctx, pub_key_id, WH_NVM_FLAGS_NONE, pub);
+ }
}
if (ret == 0) {
ret = wc_curve25519_shared_secret_ex(priv, pub, res_out,
@@ -2210,14 +2432,13 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic,
}
}
}
-cleanup:
if (evict_pub) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, pub_key_id);
+ _CryptoEvictKeyLocked(ctx, pub_key_id);
}
if (evict_prv) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, prv_key_id);
+ _CryptoEvictKeyLocked(ctx, prv_key_id);
}
if (ret == 0) {
uint16_t payload_len;
@@ -2283,17 +2504,20 @@ static int _HandleEd25519KeyGen(whServerContext* ctx, uint16_t magic, int devId,
}
else {
ser_size = 0;
- if (WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- if (ret != WH_ERROR_OK) {
- wc_ed25519_free(key);
- return ret;
+ /* Hold the NVM lock so id allocation and cache import are
+ * atomic with respect to other server contexts under
+ * THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(key_id)) {
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
}
- }
- if (ret == 0) {
- ret = wh_Server_CacheImportEd25519Key(
- ctx, key, key_id, flags, label_size, label);
- }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_CacheImportEd25519Key(
+ ctx, key, key_id, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
}
}
wc_ed25519_free(key);
@@ -2364,21 +2588,16 @@ static int _HandleEd25519Sign(whServerContext* ctx, uint16_t magic, int devId,
uint8_t* req_ctx = req_msg + msg_len;
int evict = !!(req.options & WH_MESSAGE_CRYPTO_ED25519_SIGN_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_SIGN);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
uint8_t* res_sig =
(uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_Ed25519SignResponse);
word32 sig_len = sizeof(sig);
ret = wc_ed25519_init_ex(key, NULL, devId);
if (ret == 0) {
- ret = wh_Server_CacheExportEd25519Key(ctx, key_id, key);
+ /* load the private key, enforcing the sign usage policy against the
+ * same locked snapshot that is exported */
+ ret = wh_Server_CacheExportEd25519KeyEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key);
if (ret == WH_ERROR_OK) {
ret = wc_ed25519_sign_msg_ex(req_msg, msg_len, sig, &sig_len, key,
(byte)req.type, req_ctx,
@@ -2395,10 +2614,9 @@ static int _HandleEd25519Sign(whServerContext* ctx, uint16_t magic, int devId,
memcpy(res_sig, sig, sig_len);
}
-cleanup:
if (evict) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
@@ -2465,19 +2683,14 @@ static int _HandleEd25519Verify(whServerContext* ctx, uint16_t magic, int devId,
int evict =
!!(req.options & WH_MESSAGE_CRYPTO_ED25519_VERIFY_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_VERIFY);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
int result = 0;
ret = wc_ed25519_init_ex(key, NULL, devId);
if (ret == 0) {
- ret = wh_Server_CacheExportEd25519Key(ctx, key_id, key);
+ /* load the public key, enforcing the verify usage policy against the
+ * same locked snapshot that is exported */
+ ret = wh_Server_CacheExportEd25519KeyEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key);
if (ret == WH_ERROR_OK) {
ret = wc_ed25519_verify_msg_ex(req_sig, sig_len, req_msg, msg_len,
&result, key, (byte)req.type,
@@ -2486,9 +2699,8 @@ static int _HandleEd25519Verify(whServerContext* ctx, uint16_t magic, int devId,
wc_ed25519_free(key);
}
-cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
@@ -2545,14 +2757,6 @@ static int _HandleEd25519SignDma(whServerContext* ctx, uint16_t magic,
WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId);
int evict = !!(req.options & WH_MESSAGE_CRYPTO_ED25519_SIGN_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_SIGN);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
memset(&res, 0, sizeof(res));
sigLen = req.sig.sz;
@@ -2573,7 +2777,10 @@ static int _HandleEd25519SignDma(whServerContext* ctx, uint16_t magic,
if (ret == WH_ERROR_OK) {
ret = wc_ed25519_init_ex(key, NULL, devId);
if (ret == 0) {
- ret = wh_Server_CacheExportEd25519Key(ctx, key_id, key);
+ /* load the private key, enforcing the sign usage policy against
+ * the same locked snapshot that is exported */
+ ret = wh_Server_CacheExportEd25519KeyEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key);
if (ret == WH_ERROR_OK) {
ret = wc_ed25519_sign_msg_ex(msgAddr, req.msg.sz, sigAddr,
&sigLen, key, (byte)req.type,
@@ -2596,9 +2803,8 @@ static int _HandleEd25519SignDma(whServerContext* ctx, uint16_t magic,
ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
-cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == WH_ERROR_OK) {
@@ -2653,14 +2859,6 @@ static int _HandleEd25519VerifyDma(whServerContext* ctx, uint16_t magic,
int evict =
!!(req.options & WH_MESSAGE_CRYPTO_ED25519_VERIFY_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_VERIFY);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
memset(&res, 0, sizeof(res));
ret = wh_Server_DmaProcessClientAddress(
@@ -2681,7 +2879,10 @@ static int _HandleEd25519VerifyDma(whServerContext* ctx, uint16_t magic,
if (ret == WH_ERROR_OK) {
ret = wc_ed25519_init_ex(key, NULL, devId);
if (ret == 0) {
- ret = wh_Server_CacheExportEd25519Key(ctx, key_id, key);
+ /* load the public key, enforcing the verify usage policy against
+ * the same locked snapshot that is exported */
+ ret = wh_Server_CacheExportEd25519KeyEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key);
if (ret == WH_ERROR_OK) {
int verified = 0;
ret = wc_ed25519_verify_msg_ex(
@@ -2702,9 +2903,8 @@ static int _HandleEd25519VerifyDma(whServerContext* ctx, uint16_t magic,
ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz,
WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0});
-cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == WH_ERROR_OK) {
@@ -2729,8 +2929,8 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId,
Aes aes[1] = {0};
whMessageCrypto_AesCtrRequest req;
whMessageCrypto_AesCtrResponse res;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
if (inSize < sizeof(whMessageCrypto_AesCtrRequest)) {
return WH_ERROR_BADARGS;
@@ -2771,13 +2971,14 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId,
WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] tmp ", tmp, AES_BLOCK_SIZE);
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, key_id, &cachedKey, &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the usage
+ * policy against the same locked snapshot that is read. The crypto
+ * below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, key_id,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
/* override the incoming values with cached key */
key = cachedKey;
@@ -2849,6 +3050,7 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId,
ret = wh_MessageCrypto_TranslateAesCtrResponse(
magic, &res, (whMessageCrypto_AesCtrResponse*)cryptoDataOut);
}
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
@@ -2869,8 +3071,8 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId,
word32 outSz = 0;
whKeyId keyId;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
(void)seq;
@@ -2922,14 +3124,15 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId,
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(keyId)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cachedKey,
- &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the
+ * usage policy against the same locked snapshot that is read. The
+ * crypto below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyId,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
+ : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
key = cachedKey;
keyLen = keyMeta->len;
@@ -3045,6 +3248,7 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId,
*outSize = sizeof(whMessageCrypto_AesCtrDmaResponse) +
AES_IV_SIZE + AES_BLOCK_SIZE;
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
#endif /* WOLFHSM_CFG_DMA */
@@ -3059,8 +3263,8 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId,
Aes aes[1] = {0};
whMessageCrypto_AesEcbRequest req;
whMessageCrypto_AesEcbResponse res;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
if (inSize < sizeof(whMessageCrypto_AesEcbRequest)) {
return WH_ERROR_BADARGS;
@@ -3098,13 +3302,14 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId,
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, key_id, &cachedKey, &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the usage
+ * policy against the same locked snapshot that is read. The crypto
+ * below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, key_id,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
/* override the incoming values with cached key */
key = cachedKey;
@@ -3158,6 +3363,7 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId,
ret = wh_MessageCrypto_TranslateAesEcbResponse(
magic, &res, (whMessageCrypto_AesEcbResponse*)cryptoDataOut);
}
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
@@ -3177,8 +3383,8 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId,
word32 outSz = 0;
whKeyId keyId;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
(void)seq;
@@ -3219,14 +3425,15 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId,
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(keyId)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cachedKey,
- &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the
+ * usage policy against the same locked snapshot that is read. The
+ * crypto below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyId,
+ req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
+ : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
key = cachedKey;
keyLen = keyMeta->len;
@@ -3325,6 +3532,7 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId,
magic, &res, (whMessageCrypto_AesEcbDmaResponse*)cryptoDataOut);
*outSize = sizeof(whMessageCrypto_AesEcbDmaResponse);
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
#endif /* WOLFHSM_CFG_DMA */
@@ -3339,8 +3547,8 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId,
Aes aes[1] = {0};
whMessageCrypto_AesCbcRequest req;
whMessageCrypto_AesCbcResponse res;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
/* Validate minimum size */
if (inSize < sizeof(whMessageCrypto_AesCbcRequest)) {
@@ -3382,13 +3590,14 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId,
WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] IV", iv, AES_BLOCK_SIZE);
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, key_id, &cachedKey, &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the usage
+ * policy against the same locked snapshot that is read. The crypto
+ * below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, key_id,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
/* override the incoming values with cached key */
key = cachedKey;
@@ -3443,6 +3652,7 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId,
ret = wh_MessageCrypto_TranslateAesCbcResponse(
magic, &res, (whMessageCrypto_AesCbcResponse*)cryptoDataOut);
}
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
@@ -3462,8 +3672,8 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId,
word32 outSz = 0;
whKeyId keyId;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
(void)seq;
@@ -3511,14 +3721,15 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId,
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(keyId)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cachedKey,
- &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the
+ * usage policy against the same locked snapshot that is read. The
+ * crypto below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyId,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
+ : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
key = cachedKey;
keyLen = keyMeta->len;
@@ -3617,6 +3828,7 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId,
magic, &res, (whMessageCrypto_AesCbcDmaResponse*)cryptoDataOut);
*outSize = sizeof(whMessageCrypto_AesCbcDmaResponse) + AES_IV_SIZE;
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
#endif /* WOLFHSM_CFG_DMA */
@@ -3629,8 +3841,8 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId,
{
int ret = WH_ERROR_OK;
Aes aes[1] = {0};
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
/* Validate minimum size */
if (inSize < sizeof(whMessageCrypto_AesGcmRequest)) {
@@ -3691,14 +3903,16 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId,
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, key_id, &cachedKey, &keyMeta);
- WH_DEBUG_SERVER_VERBOSE("AesGcm FreshenKey key_id:%u ret:%d\n", key_id, ret);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the usage
+ * policy against the same locked snapshot that is read. The crypto
+ * below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, key_id,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
+ WH_DEBUG_SERVER_VERBOSE("AesGcm ReadKey key_id:%u ret:%d\n", key_id,
+ ret);
if (ret == WH_ERROR_OK) {
/* override the incoming values with cached key */
key = cachedKey;
@@ -3767,6 +3981,7 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId,
ret = wh_MessageCrypto_TranslateAesGcmResponse(
magic, &res, (whMessageCrypto_AesGcmResponse*)cryptoDataOut);
}
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
@@ -3787,8 +4002,8 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
word32 outSz = 0;
whKeyId keyId;
- uint8_t* cachedKey = NULL;
- whNvmMetadata* keyMeta = NULL;
+ uint8_t cachedKey[AES_256_KEY_SIZE];
+ whNvmMetadata keyMeta[1];
(void)seq;
@@ -3839,14 +4054,15 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
/* Freshen key and validate usage policy if key is not erased */
if (!WH_KEYID_ISERASED(keyId)) {
- ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cachedKey,
- &keyMeta);
- if (ret == WH_ERROR_OK) {
- /* Validate key usage policy */
- ret = wh_Server_KeystoreEnforceKeyUsage(
- keyMeta, enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
- : WH_NVM_FLAGS_USAGE_DECRYPT);
- }
+ /* Copy the key + metadata into private buffers, enforcing the
+ * usage policy against the same locked snapshot that is read. The
+ * crypto below then runs entirely on the private copy. */
+ uint32_t cachedKeyLen = sizeof(cachedKey);
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyId,
+ enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT
+ : WH_NVM_FLAGS_USAGE_DECRYPT,
+ keyMeta, cachedKey, &cachedKeyLen);
if (ret == WH_ERROR_OK) {
key = cachedKey;
keyLen = keyMeta->len;
@@ -3962,6 +4178,7 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
magic, &res, (whMessageCrypto_AesGcmDmaResponse*)cryptoDataOut);
*outSize = sizeof(whMessageCrypto_AesGcmDmaResponse) + res.authTagSz;
+ wc_ForceZero(cachedKey, sizeof(cachedKey));
return ret;
}
#endif /* WOLFHSM_CFG_DMA */
@@ -3988,17 +4205,17 @@ static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey,
whKeyId keyId = wh_KeyId_TranslateFromClient(
WH_KEYTYPE_CRYPTO, ctx->comm->client_id, clientKeyId);
- /* Validate key usage policy - CMAC accepts sign or verify */
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, keyId,
- WH_NVM_FLAGS_USAGE_SIGN);
+ /* Copy the key into the caller's private buffer, enforcing the usage
+ * policy against the same locked snapshot that is read. CMAC accepts
+ * sign or verify usage, so retry with verify on a usage failure; each
+ * attempt is an atomic policy-check + read. */
+ uint32_t reqKeyLen = *outKeyLen;
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyId, WH_NVM_FLAGS_USAGE_SIGN, NULL, outKey, outKeyLen);
if (ret == WH_ERROR_USAGE) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(
- ctx, keyId, WH_NVM_FLAGS_USAGE_VERIFY);
- }
-
- if (ret == WH_ERROR_OK) {
- ret =
- wh_Server_KeystoreReadKey(ctx, keyId, NULL, outKey, outKeyLen);
+ *outKeyLen = reqKeyLen;
+ ret = wh_Server_KeystoreReadKeyEnforce(
+ ctx, keyId, WH_NVM_FLAGS_USAGE_VERIFY, NULL, outKey, outKeyLen);
}
if (ret == WH_ERROR_OK) {
@@ -4133,6 +4350,7 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId,
*outSize = sizeof(res) + res.outSz;
}
}
+ wc_ForceZero(tmpKey, sizeof(tmpKey));
WH_DEBUG_SERVER_VERBOSE("cmac end ret:%d\n", ret);
return ret;
}
@@ -4646,22 +4864,24 @@ static int _HandleMlDsaKeyGen(whServerContext* ctx, uint16_t magic, int devId,
/* Must import the key into the cache and return keyid
*/
res_size = 0;
- if (WH_KEYID_ISERASED(key_id)) {
- /* Generate a new id */
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n",
- key_id, ret);
- if (ret != WH_ERROR_OK) {
- /* Early return on unique ID generation failure
- */
- wc_MlDsaKey_Free(key);
- return ret;
+ /* Hold the NVM lock so id allocation and cache import
+ * are atomic with respect to other server contexts
+ * under THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(key_id)) {
+ /* Generate a new id */
+ ret =
+ wh_Server_KeystoreGetUniqueId(ctx, &key_id);
+ WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n",
+ key_id, ret);
}
- }
- if (ret == 0) {
- ret = wh_Server_MlDsaKeyCacheImport(
- ctx, key, key_id, flags, label_size, label);
- }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_MlDsaKeyCacheImport(
+ ctx, key, key_id, flags, label_size, label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
WH_DEBUG_SERVER("CacheImport: keyId:%u, ret:%d\n",
key_id, ret);
}
@@ -4721,15 +4941,6 @@ static int _HandleMlDsaSign(whServerContext* ctx, uint16_t magic, int devId,
uint32_t options = req.options;
int evict = !!(options & WH_MESSAGE_CRYPTO_MLDSA_SIGN_OPTIONS_EVICT);
- /* Validate key usage policy for signing */
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_SIGN);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
/* Validate input length against available data to prevent buffer overread
*/
if (inSize < sizeof(whMessageCrypto_MlDsaSignRequest)) {
@@ -4757,8 +4968,10 @@ static int _HandleMlDsaSign(whServerContext* ctx, uint16_t magic, int devId,
/* init private key */
ret = wc_MlDsaKey_Init(key, NULL, devId);
if (ret == 0) {
- /* load the private key */
- ret = wh_Server_MlDsaKeyCacheExport(ctx, key_id, key);
+ /* load the private key, enforcing the sign usage policy against the
+ * same locked snapshot that is exported */
+ ret = wh_Server_MlDsaKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key);
if (ret == WH_ERROR_OK) {
/* sign the input using appropriate FIPS 204 API */
if (preHashType != WC_HASH_TYPE_NONE) {
@@ -4774,10 +4987,9 @@ static int _HandleMlDsaSign(whServerContext* ctx, uint16_t magic, int devId,
}
wc_MlDsaKey_Free(key);
}
-cleanup:
if (evict != 0) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
res.sz = res_len;
@@ -4828,15 +5040,6 @@ static int _HandleMlDsaVerify(whServerContext* ctx, uint16_t magic, int devId,
(uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_MlDsaVerifyRequest);
int evict = !!(options & WH_MESSAGE_CRYPTO_MLDSA_VERIFY_OPTIONS_EVICT);
- /* Validate key usage policy for verification */
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_VERIFY);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
/* Validate lengths against available payload (overflow-safe) */
if (inSize < sizeof(whMessageCrypto_MlDsaVerifyRequest)) {
return WH_ERROR_BADARGS;
@@ -4862,8 +5065,10 @@ static int _HandleMlDsaVerify(whServerContext* ctx, uint16_t magic, int devId,
/* init public key */
ret = wc_MlDsaKey_Init(key, NULL, devId);
if (ret == 0) {
- /* load the public key */
- ret = wh_Server_MlDsaKeyCacheExport(ctx, key_id, key);
+ /* load the public key, enforcing the verify usage policy against the
+ * same locked snapshot that is exported */
+ ret = wh_Server_MlDsaKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key);
if (ret == WH_ERROR_OK) {
/* verify the signature using appropriate FIPS 204 API */
if (preHashType != WC_HASH_TYPE_NONE) {
@@ -4879,10 +5084,9 @@ static int _HandleMlDsaVerify(whServerContext* ctx, uint16_t magic, int devId,
}
wc_MlDsaKey_Free(key);
}
-cleanup:
if (evict != 0) {
/* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
res.res = result;
@@ -4995,14 +5199,20 @@ static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId,
&res_size);
}
else {
- if (WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
- }
+ /* Hold the NVM lock so id allocation and cache import are
+ * atomic with respect to other server contexts under
+ * THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
if (ret == WH_ERROR_OK) {
- ret = wh_Server_MlKemKeyCacheImport(ctx, key, key_id,
- req.flags, label_size,
- req.label);
- }
+ if (WH_KEYID_ISERASED(key_id)) {
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id);
+ }
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_MlKemKeyCacheImport(
+ ctx, key, key_id, req.flags, label_size, req.label);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
}
}
wc_MlKemKey_Free(key);
@@ -5061,14 +5271,6 @@ static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId,
req.keyId);
evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_ENCAPS_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
if (!_IsMlKemLevelSupported((int)req.level)) {
ret = WH_ERROR_BADARGS;
goto cleanup;
@@ -5077,7 +5279,10 @@ static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId,
ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId);
if (ret == 0) {
keyInited = 1;
- ret = wh_Server_MlKemKeyCacheExport(ctx, key_id, key);
+ /* Export the key, enforcing the derive usage policy against the same
+ * locked snapshot that is exported */
+ ret = wh_Server_MlKemKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_DERIVE, key);
}
/* Verify the exported key matches the requested level */
@@ -5122,7 +5327,7 @@ static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId,
}
cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
return ret;
#endif /* WOLFSSL_MLKEM_NO_ENCAPSULATE */
@@ -5169,14 +5374,6 @@ static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId,
req.keyId);
evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_DECAPS_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
if (!_IsMlKemLevelSupported((int)req.level)) {
ret = WH_ERROR_BADARGS;
goto cleanup;
@@ -5192,7 +5389,10 @@ static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId,
ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId);
if (ret == WH_ERROR_OK) {
keyInited = 1;
- ret = wh_Server_MlKemKeyCacheExport(ctx, key_id, key);
+ /* Export the key, enforcing the derive usage policy against the same
+ * locked snapshot that is exported */
+ ret = wh_Server_MlKemKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_DERIVE, key);
}
/* Verify the exported key matches the requested level */
@@ -5232,7 +5432,7 @@ static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId,
}
cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
return ret;
#endif /* WOLFSSL_MLKEM_NO_DECAPSULATE */
@@ -6189,30 +6389,33 @@ static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic,
whKeyId keyId = wh_KeyId_TranslateFromClient(
WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId);
- if (WH_KEYID_ISERASED(keyId)) {
- /* Generate a new id */
- ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId);
- WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n",
- keyId, ret);
- if (ret != WH_ERROR_OK) {
- /* Early return on unique ID generation failure
- */
- wc_MlDsaKey_Free(key);
- return ret;
+ /* Hold the NVM lock so id allocation and cache import
+ * are atomic with respect to other server contexts
+ * under THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
+ if (ret == WH_ERROR_OK) {
+ if (WH_KEYID_ISERASED(keyId)) {
+ /* Generate a new id */
+ ret =
+ wh_Server_KeystoreGetUniqueId(ctx, &keyId);
+ WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n",
+ keyId, ret);
}
- }
-
- if (ret == 0) {
- ret = wh_Server_MlDsaKeyCacheImport(
- ctx, key, keyId, req.flags, req.labelSize,
- req.label);
- WH_DEBUG_SERVER("CacheImport: keyId:%u, ret:%d\n",
- keyId, ret);
- if (ret == 0) {
- res.keyId = wh_KeyId_TranslateToClient(keyId);
- res.keySize = keySize;
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_MlDsaKeyCacheImport(
+ ctx, key, keyId, req.flags, req.labelSize,
+ req.label);
+ WH_DEBUG_SERVER(
+ "CacheImport: keyId:%u, ret:%d\n", keyId,
+ ret);
+ if (ret == 0) {
+ res.keyId =
+ wh_KeyId_TranslateToClient(keyId);
+ res.keySize = keySize;
+ }
}
- }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
}
}
}
@@ -6297,7 +6500,11 @@ static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId,
if (ret == 0) {
/* Export key from cache */
/* TODO: sanity check security level against key pulled from cache? */
- ret = wh_Server_MlDsaKeyCacheExport(ctx, key_id, key);
+ /* Export the key, enforcing the sign usage policy against the same
+ * locked snapshot that is exported. The non-DMA sign handler enforces
+ * the same policy. */
+ ret = wh_Server_MlDsaKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key);
if (ret == 0) {
/* Process client message buffer address */
ret = wh_Server_DmaProcessClientAddress(
@@ -6342,17 +6549,16 @@ static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId,
(whServerDmaFlags){0});
}
}
-
- /* Evict key if requested */
- if (evict) {
- /* User requested to evict from cache, even if the call failed
- */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
- }
}
wc_MlDsaKey_Free(key);
}
+ /* Evict key if requested */
+ if (evict) {
+ /* User requested to evict from cache, even if the call failed */
+ _CryptoEvictKeyLocked(ctx, key_id);
+ }
+
if (ret == 0) {
/* Set response signature length */
res.sigLen = sigLen;
@@ -6431,8 +6637,11 @@ static int _HandleMlDsaVerifyDma(whServerContext* ctx, uint16_t magic,
return ret;
}
- /* Export key from cache */
- ret = wh_Server_MlDsaKeyCacheExport(ctx, key_id, key);
+ /* Export the key, enforcing the verify usage policy against the same
+ * locked snapshot that is exported. The non-DMA verify handler enforces
+ * the same policy. */
+ ret = wh_Server_MlDsaKeyCacheExportEnforce(ctx, key_id,
+ WH_NVM_FLAGS_USAGE_VERIFY, key);
if (ret == 0) {
/* Process client signature buffer address */
ret = wh_Server_DmaProcessClientAddress(
@@ -6474,12 +6683,12 @@ static int _HandleMlDsaVerifyDma(whServerContext* ctx, uint16_t magic,
(whServerDmaFlags){0});
}
}
+ }
- /* Evict key if requested */
- if (evict) {
- /* User requested to evict from cache, even if the call failed */
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
- }
+ /* Evict key if requested */
+ if (evict) {
+ /* User requested to evict from cache, even if the call failed */
+ _CryptoEvictKeyLocked(ctx, key_id);
}
if (ret == 0) {
@@ -6631,18 +6840,25 @@ static int _HandleMlKemKeyGenDma(whServerContext* ctx, uint16_t magic,
whKeyId keyId = wh_KeyId_TranslateFromClient(
WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId);
- if (WH_KEYID_ISERASED(keyId)) {
- ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId);
- }
+ /* Hold the NVM lock so id allocation and cache import are
+ * atomic with respect to other server contexts under
+ * THREADSAFE. */
+ ret = WH_SERVER_NVM_LOCK(ctx);
if (ret == WH_ERROR_OK) {
- ret = wh_Server_MlKemKeyCacheImport(
- ctx, key, keyId, req.flags, req.labelSize,
- req.label);
+ if (WH_KEYID_ISERASED(keyId)) {
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId);
+ }
if (ret == WH_ERROR_OK) {
- res.keyId = wh_KeyId_TranslateToClient(keyId);
- res.keySize = keySize;
+ ret = wh_Server_MlKemKeyCacheImport(
+ ctx, key, keyId, req.flags, req.labelSize,
+ req.label);
+ if (ret == WH_ERROR_OK) {
+ res.keyId = wh_KeyId_TranslateToClient(keyId);
+ res.keySize = keySize;
+ }
}
- }
+ (void)WH_SERVER_NVM_UNLOCK(ctx);
+ } /* WH_SERVER_NVM_LOCK() */
}
}
wc_MlKemKey_Free(key);
@@ -6704,14 +6920,6 @@ static int _HandleMlKemEncapsDma(whServerContext* ctx, uint16_t magic,
ctx->comm->client_id, req.keyId);
evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_ENCAPS_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
if (!_IsMlKemLevelSupported((int)req.level)) {
ret = WH_ERROR_BADARGS;
goto cleanup;
@@ -6720,7 +6928,10 @@ static int _HandleMlKemEncapsDma(whServerContext* ctx, uint16_t magic,
ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId);
if (ret == WH_ERROR_OK) {
keyInited = 1;
- ret = wh_Server_MlKemKeyCacheExport(ctx, key_id, key);
+ /* Export the key, enforcing the derive usage policy against the same
+ * locked snapshot that is exported */
+ ret = wh_Server_MlKemKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_DERIVE, key);
}
/* Verify the exported key matches the requested level */
@@ -6788,7 +6999,7 @@ static int _HandleMlKemEncapsDma(whServerContext* ctx, uint16_t magic,
}
cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
(void)wh_MessageCrypto_TranslateMlKemEncapsDmaResponse(
@@ -6841,14 +7052,6 @@ static int _HandleMlKemDecapsDma(whServerContext* ctx, uint16_t magic,
ctx->comm->client_id, req.keyId);
evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_DECAPS_OPTIONS_EVICT);
- if (!WH_KEYID_ISERASED(key_id)) {
- ret = wh_Server_KeystoreFindEnforceKeyUsage(ctx, key_id,
- WH_NVM_FLAGS_USAGE_DERIVE);
- if (ret != WH_ERROR_OK) {
- goto cleanup;
- }
- }
-
if (!_IsMlKemLevelSupported((int)req.level)) {
ret = WH_ERROR_BADARGS;
goto cleanup;
@@ -6857,7 +7060,10 @@ static int _HandleMlKemDecapsDma(whServerContext* ctx, uint16_t magic,
ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId);
if (ret == WH_ERROR_OK) {
keyInited = 1;
- ret = wh_Server_MlKemKeyCacheExport(ctx, key_id, key);
+ /* Export the key, enforcing the derive usage policy against the same
+ * locked snapshot that is exported */
+ ret = wh_Server_MlKemKeyCacheExportEnforce(
+ ctx, key_id, WH_NVM_FLAGS_USAGE_DERIVE, key);
}
/* Verify the exported key matches the requested level */
@@ -6916,7 +7122,7 @@ static int _HandleMlKemDecapsDma(whServerContext* ctx, uint16_t magic,
}
cleanup:
if (evict != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, key_id);
+ _CryptoEvictKeyLocked(ctx, key_id);
}
(void)wh_MessageCrypto_TranslateMlKemDecapsDmaResponse(
@@ -7425,7 +7631,7 @@ static int _HandleLmsVerifyDma(whServerContext* ctx, uint16_t magic, int devId,
}
if ((req.options & WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT) != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, keyId);
+ _CryptoEvictKeyLocked(ctx, keyId);
}
(void)wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaResponse(
@@ -7915,7 +8121,7 @@ static int _HandleXmssVerifyDma(whServerContext* ctx, uint16_t magic,
}
if ((req.options & WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT) != 0) {
- (void)wh_Server_KeystoreEvictKey(ctx, keyId);
+ _CryptoEvictKeyLocked(ctx, keyId);
}
(void)wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaResponse(
@@ -8274,6 +8480,7 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId,
}
}
+ wc_ForceZero(tmpKey, sizeof(tmpKey));
WH_DEBUG_SERVER_VERBOSE("dma cmac end ret:%d\n", ret);
return ret;
}
diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c
index 90cfb56be..1e89d7657 100644
--- a/src/wh_server_keystore.c
+++ b/src/wh_server_keystore.c
@@ -1064,6 +1064,43 @@ int wh_Server_KeystoreReadKeyChecked(whServerContext* server, whKeyId keyId,
return wh_Server_KeystoreReadKey(server, keyId, outMeta, out, outSz);
}
+int wh_Server_KeystoreReadKeyEnforce(whServerContext* server, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ whNvmMetadata* outMeta, uint8_t* out,
+ uint32_t* outSz)
+{
+ int ret;
+ whNvmMetadata meta[1];
+
+ if ((server == NULL) || (outSz == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Copy the key and check its usage flags under one hold of the NVM lock
+ * so the policy verdict and the key material come from the same snapshot:
+ * another server context cannot erase/re-cache the key between the check
+ * and the read. */
+ ret = WH_SERVER_NVM_LOCK(server);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreReadKey(server, keyId, meta, out, outSz);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreEnforceKeyUsage(meta, requiredUsage);
+ if (ret == WH_ERROR_OK) {
+ if (outMeta != NULL) {
+ memcpy((uint8_t*)outMeta, (uint8_t*)meta,
+ sizeof(whNvmMetadata));
+ }
+ }
+ else if (out != NULL) {
+ /* Don't hand back key material that failed the policy check */
+ memset(out, 0, *outSz);
+ }
+ }
+ (void)WH_SERVER_NVM_UNLOCK(server);
+ }
+ return ret;
+}
+
int wh_Server_KeystoreEvictKey(whServerContext* server, whNvmId keyId)
{
int ret = 0;
@@ -3216,6 +3253,10 @@ int wh_Server_KeystoreEnforceKeyUsage(const whNvmMetadata* meta,
return WH_ERROR_USAGE;
}
+/* May be deprecated soon: see wh_server_keystore.h. Enforcing here and using
+ * the key in a later lock scope leaves a TOCTOU window; prefer
+ * wh_Server_KeystoreReadKeyEnforce or the typed wh_Server_*CacheExport*Enforce
+ * wrappers. */
int wh_Server_KeystoreFindEnforceKeyUsage(whServerContext* server,
whKeyId keyId,
whNvmFlags requiredUsage)
@@ -3228,14 +3269,20 @@ int wh_Server_KeystoreFindEnforceKeyUsage(whServerContext* server,
return WH_ERROR_BADARGS;
}
- /* Freshen the key to obtain the metadata */
- ret = wh_Server_KeystoreFreshenKey(server, keyId, NULL, &meta);
- if (ret != WH_ERROR_OK) {
- return ret;
+ /* Freshen the key and read its metadata under the NVM lock so the shared
+ * cache slot cannot be evicted or overwritten by another server context
+ * while we inspect the usage flags. FreshenKey hands back a pointer into
+ * the shared slot, so the policy check must complete before we unlock. */
+ ret = WH_SERVER_NVM_LOCK(server);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_Server_KeystoreFreshenKey(server, keyId, NULL, &meta);
+ if (ret == WH_ERROR_OK) {
+ /* Enforce the usage policy with the obtained metadata */
+ ret = wh_Server_KeystoreEnforceKeyUsage(meta, requiredUsage);
+ }
+ (void)WH_SERVER_NVM_UNLOCK(server);
}
-
- /* Enforce the usage policy with the obtained metadata */
- return wh_Server_KeystoreEnforceKeyUsage(meta, requiredUsage);
+ return ret;
}
#endif /* !WOLFHSM_CFG_NO_CRYPTO && WOLFHSM_CFG_ENABLE_SERVER */
diff --git a/test-refactor/client-server/wh_test_crypto_mldsa.c b/test-refactor/client-server/wh_test_crypto_mldsa.c
index 4b69738bd..5e027f81e 100644
--- a/test-refactor/client-server/wh_test_crypto_mldsa.c
+++ b/test-refactor/client-server/wh_test_crypto_mldsa.c
@@ -916,9 +916,11 @@ static int _whTest_CryptoMlDsaVerifyOnlyDma(whClientContext* ctx)
}
}
/* Import the key into wolfHSM via the wolfCrypt structure. This is the
- * DMA-only verify test, so always import via the DMA path. */
+ * DMA-only verify test, so always import via the DMA path. The key must
+ * carry the verify usage flag, which the DMA verify handler enforces. */
if (ret == 0) {
- ret = wh_Client_MlDsaImportKeyDma(ctx, key, &keyId, 0, 0, NULL);
+ ret = wh_Client_MlDsaImportKeyDma(ctx, key, &keyId,
+ WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL);
if (ret == WH_ERROR_OK) {
evictKey = 1;
}
diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile
index 3169736ad..19cfc6350 100644
--- a/test-refactor/posix/Makefile
+++ b/test-refactor/posix/Makefile
@@ -85,6 +85,18 @@ ifeq ($(ASAN),1)
LDFLAGS += -fsanitize=address
endif
+# ThreadSanitizer. Enables the acquire/release transport shims in the
+# concurrent tests (WOLFHSM_CFG_TEST_STRESS_TSAN) so TSAN's analysis stays on
+# keystore/NVM locking rather than transport false positives.
+ifeq ($(TSAN),1)
+ ifeq ($(ASAN),1)
+ $(error TSAN and ASAN cannot be used together)
+ endif
+ CFLAGS += -fsanitize=thread -fPIE
+ LDFLAGS += -fsanitize=thread -pie
+ DEF += -DWOLFSSL_NO_FENCE -DWOLFHSM_CFG_TEST_STRESS_TSAN
+endif
+
# Enable threadsafe mode, adding lock protection to shared structures
ifeq ($(THREADSAFE),1)
DEF += -DWOLFHSM_CFG_THREADSAFE
@@ -285,10 +297,18 @@ coverage-json:
--filter '\.\./\.\./wolfhsm/.*' \
--json $(OUT)
+# Under TSAN: fail fast and non-zero on any detected race, using the shared
+# suppressions (wolfCrypt internals + SHM transport).
+ifeq ($(TSAN),1)
+ RUN_ENV = TSAN_OPTIONS="halt_on_error=1:exitcode=66:suppressions=$(TEST_DIR)/tsan.supp"
+else
+ RUN_ENV =
+endif
+
# Send the full test output to test-suite.log, not just the summary
check run: build_app
@rm -f test-suite.log
- @$(BUILD_DIR)/$(BIN).elf 2>&1 \
+ @$(RUN_ENV) $(BUILD_DIR)/$(BIN).elf 2>&1 \
| tee test-suite.log \
| grep --line-buffered -E \
'^whTest_|^All [0-9]+ tests passed|^[0-9]+ passed, [0-9]+ skipped'; \
diff --git a/test-refactor/posix/wh_test_keygen_unique_id.c b/test-refactor/posix/wh_test_keygen_unique_id.c
new file mode 100644
index 000000000..916842a7b
--- /dev/null
+++ b/test-refactor/posix/wh_test_keygen_unique_id.c
@@ -0,0 +1,699 @@
+/*
+ * 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/posix/wh_test_keygen_unique_id.c
+ *
+ * Concurrent unique-id allocation test for the crypto keygen handlers in
+ * src/wh_server_crypto.c.
+ *
+ * KU_NUM_CLIENTS client/server pairs share a single locked NVM. For each
+ * supported algorithm, all clients issue a barrier-aligned cache keygen
+ * with an ERASED global key id, so every server auto-allocates an id from
+ * the shared global namespace. Each round asserts that all successful
+ * keygens returned distinct ids.
+ *
+ * Requires WOLFHSM_CFG_THREADSAFE and WOLFHSM_CFG_GLOBAL_KEYS: global keys
+ * (USER == 0) live in the one shared server->nvm->globalCache, which is
+ * what makes the allocations contend. Under TSAN (TSAN=1) any unserialized
+ * access to that cache is additionally reported as a data race.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+/* pthread_barrier_t is unavailable on macOS. */
+#if defined(WOLFHSM_CFG_THREADSAFE) && defined(WOLFHSM_CFG_TEST_POSIX) && \
+ defined(WOLFHSM_CFG_GLOBAL_KEYS) && defined(WOLFHSM_CFG_ENABLE_CLIENT) && \
+ defined(WOLFHSM_CFG_ENABLE_SERVER) && !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ !defined(__APPLE__)
+#define WH_KU_ENABLED
+#endif
+
+#include "wh_test_common.h"
+#include "wh_test_list.h" /* WH_TEST_SKIPPED */
+#include "wh_test_keygen_unique_id.h"
+
+#ifndef WH_KU_ENABLED
+
+int whTest_KeygenUniqueIdConcurrent(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+#else /* WH_KU_ENABLED */
+
+#include
+#include
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_transport_mem.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_client_crypto.h"
+#include "wolfhsm/wh_server.h"
+#include "wolfhsm/wh_nvm.h"
+#include "wolfhsm/wh_nvm_flash.h"
+#include "wolfhsm/wh_flash_ramsim.h"
+#include "wolfhsm/wh_lock.h"
+#include "wolfhsm/wh_keyid.h"
+
+#include "port/posix/posix_lock.h"
+
+#include "wolfssl/wolfcrypt/settings.h"
+#include "wolfssl/wolfcrypt/types.h"
+#include "wolfssl/wolfcrypt/error-crypt.h"
+#ifdef HAVE_CURVE25519
+#include "wolfssl/wolfcrypt/curve25519.h"
+#endif
+#ifdef HAVE_ECC
+#include "wolfssl/wolfcrypt/ecc.h"
+#endif
+#ifdef HAVE_DILITHIUM
+#include "wolfssl/wolfcrypt/dilithium.h"
+#endif
+
+/* TSAN transport shims: the mem transport's notify counter establishes
+ * happens-before between client and server, but TSAN can't see it.
+ * Annotating the send/recv pairs keeps the analysis on keystore/NVM
+ * locking instead of transport false positives. */
+#ifdef WOLFHSM_CFG_TEST_STRESS_TSAN
+
+#ifndef __has_feature
+#define __has_feature(x) 0
+#endif
+#if !defined(__SANITIZE_THREAD__) && !__has_feature(thread_sanitizer)
+#error ThreadSanitizer not enabled for this build
+#endif
+
+#include
+
+static int ku_SendRequest(void* c, uint16_t len, const void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ __tsan_release((void*)ctx->req);
+ return wh_TransportMem_SendRequest(c, len, data);
+}
+static int ku_RecvResponse(void* c, uint16_t* out_len, void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ int rc = wh_TransportMem_RecvResponse(c, out_len, data);
+ if (rc == WH_ERROR_OK) {
+ __tsan_acquire((void*)ctx->resp);
+ }
+ return rc;
+}
+static int ku_RecvRequest(void* c, uint16_t* out_len, void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ int rc = wh_TransportMem_RecvRequest(c, out_len, data);
+ if (rc == WH_ERROR_OK) {
+ __tsan_acquire((void*)ctx->req);
+ }
+ return rc;
+}
+static int ku_SendResponse(void* c, uint16_t len, const void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ __tsan_release((void*)ctx->resp);
+ return wh_TransportMem_SendResponse(c, len, data);
+}
+
+static const whTransportClientCb clientTransportCb = {
+ .Init = wh_TransportMem_InitClear,
+ .Send = ku_SendRequest,
+ .Recv = ku_RecvResponse,
+ .Cleanup = wh_TransportMem_Cleanup,
+};
+static const whTransportServerCb serverTransportCb = {
+ .Init = wh_TransportMem_Init,
+ .Recv = ku_RecvRequest,
+ .Send = ku_SendResponse,
+ .Cleanup = wh_TransportMem_Cleanup,
+};
+
+#else /* !WOLFHSM_CFG_TEST_STRESS_TSAN */
+
+static const whTransportClientCb clientTransportCb = WH_TRANSPORT_MEM_CLIENT_CB;
+static const whTransportServerCb serverTransportCb = WH_TRANSPORT_MEM_SERVER_CB;
+
+#endif /* WOLFHSM_CFG_TEST_STRESS_TSAN */
+
+#define KU_ATOMIC_LOAD(ptr) __atomic_load_n((ptr), __ATOMIC_ACQUIRE)
+#define KU_ATOMIC_STORE(ptr, v) __atomic_store_n((ptr), (v), __ATOMIC_RELEASE)
+#define KU_ATOMIC_ADD(ptr, v) __atomic_add_fetch((ptr), (v), __ATOMIC_ACQ_REL)
+
+/* Four concurrent client/server pairs sharing one NVM. Four fits the
+ * regular key cache (9 slots) and over-subscribes the big-key cache (3),
+ * which is fine: only *successful* keygens must have distinct ids. */
+#define KU_NUM_CLIENTS 4
+
+#define KU_FLASH_RAM_SIZE (1024 * 1024)
+#define KU_FLASH_SECTOR_SIZE (128 * 1024)
+#define KU_FLASH_PAGE_SIZE 8
+
+/* Auto-id keygen messages are tiny: params in, key id out. */
+#define KU_BUFFER_SIZE 4096
+
+/* Rounds per algorithm; the slower big-key keygens use fewer to bound
+ * wall-clock. */
+#define KU_ROUNDS_SMALL 100
+#define KU_ROUNDS_BIG 24
+
+/* Each thunk issues one blocking cache keygen for a GLOBAL key with an
+ * ERASED id, so the server auto-allocates an id from the shared global
+ * namespace. *outId receives it in client (flagged) format, suitable for
+ * wh_Client_KeyEvict(). */
+typedef int (*kuGenFn)(whClientContext* client, whKeyId* outId);
+
+#define KU_ERASED_GLOBAL WH_CLIENT_KEYID_MAKE_GLOBAL(0)
+
+#ifdef HAVE_CURVE25519
+static int kuGen_Curve25519(whClientContext* client, whKeyId* outId)
+{
+ whKeyId id = KU_ERASED_GLOBAL;
+ int rc = wh_Client_Curve25519MakeCacheKey(
+ client, (uint16_t)CURVE25519_KEYSIZE, &id, WH_NVM_FLAGS_NONE, NULL, 0);
+ *outId = id;
+ return rc;
+}
+#endif
+
+#ifdef HAVE_ED25519
+static int kuGen_Ed25519(whClientContext* client, whKeyId* outId)
+{
+ whKeyId id = KU_ERASED_GLOBAL;
+ int rc =
+ wh_Client_Ed25519MakeCacheKey(client, &id, WH_NVM_FLAGS_NONE, 0, NULL);
+ *outId = id;
+ return rc;
+}
+#endif
+
+#if defined(HAVE_ECC) && defined(WOLFSSL_KEY_GEN)
+static int kuGen_Ecc(whClientContext* client, whKeyId* outId)
+{
+ whKeyId id = KU_ERASED_GLOBAL;
+ int rc = wh_Client_EccMakeCacheKey(client, 32, ECC_SECP256R1, &id,
+ WH_NVM_FLAGS_NONE, 0, NULL);
+ *outId = id;
+ return rc;
+}
+#endif
+
+#if defined(HAVE_DILITHIUM) && !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY)
+static int kuGen_MlDsa(whClientContext* client, whKeyId* outId)
+{
+ whKeyId id = KU_ERASED_GLOBAL;
+ int rc = wh_Client_MlDsaMakeCacheKey(client, 0, WC_ML_DSA_44, &id,
+ WH_NVM_FLAGS_NONE, 0, NULL);
+ *outId = id;
+ return rc;
+}
+#endif
+
+typedef struct {
+ const char* name;
+ kuGenFn gen;
+ int rounds;
+} KuAlgo;
+
+static const KuAlgo kuAlgos[] = {
+#ifdef HAVE_CURVE25519
+ {"Curve25519", kuGen_Curve25519, KU_ROUNDS_SMALL},
+#endif
+#ifdef HAVE_ED25519
+ {"Ed25519", kuGen_Ed25519, KU_ROUNDS_SMALL},
+#endif
+#if defined(HAVE_ECC) && defined(WOLFSSL_KEY_GEN)
+ {"ECC", kuGen_Ecc, KU_ROUNDS_SMALL},
+#endif
+#if defined(HAVE_DILITHIUM) && !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY)
+ {"ML-DSA", kuGen_MlDsa, KU_ROUNDS_BIG},
+#endif
+};
+
+#define KU_NUM_ALGOS ((int)(sizeof(kuAlgos) / sizeof(kuAlgos[0])))
+
+struct KuContext;
+
+typedef struct {
+ uint8_t reqBuf[KU_BUFFER_SIZE];
+ uint8_t respBuf[KU_BUFFER_SIZE];
+ whTransportMemConfig tmConfig;
+
+ whTransportMemClientContext clientTransportCtx;
+ whCommClientConfig clientCommConfig;
+ whClientContext client;
+ whClientConfig clientConfig;
+
+ whTransportMemServerContext serverTransportCtx;
+ whCommServerConfig serverCommConfig;
+ whServerContext server;
+ whServerConfig serverConfig;
+ whServerCryptoContext cryptoCtx;
+
+ pthread_t clientThread;
+ pthread_t serverThread;
+ int idx;
+
+ struct KuContext* shared;
+} KuPair;
+
+typedef struct KuContext {
+ /* Shared, locked NVM */
+ uint8_t flashMemory[KU_FLASH_RAM_SIZE];
+ whFlashRamsimCtx flashCtx;
+ whFlashRamsimCfg flashCfg;
+ whNvmFlashContext nvmFlashCtx;
+ whNvmFlashConfig nvmFlashCfg;
+ whNvmContext nvm;
+ whNvmConfig nvmCfg;
+
+ posixLockContext nvmLockCtx;
+ pthread_mutexattr_t mutexAttr;
+ posixLockConfig posixLockCfg;
+ whLockConfig lockCfg;
+
+ KuPair pairs[KU_NUM_CLIENTS];
+
+ /* Servers stay up across every algorithm; client threads are respawned
+ * per algorithm. A readiness counter rather than a barrier, so a server
+ * that fails to spawn does not hang the others. */
+ volatile int serversReady;
+ volatile int serverError;
+ volatile int stopFlag;
+
+ /* Per-round client synchronization (clients only) */
+ pthread_barrier_t roundStart;
+ pthread_barrier_t roundMid;
+ pthread_barrier_t roundEnd;
+
+ /* Current algorithm under test */
+ kuGenFn genFn;
+ int rounds;
+ const char* algoName;
+
+ /* Per-round results, indexed by client */
+ volatile whKeyId roundIds[KU_NUM_CLIENTS];
+ volatile int roundRc[KU_NUM_CLIENTS];
+
+ /* Outcome counters */
+ volatile int collisions;
+ volatile int hardErrors;
+ volatile int successes;
+} KuContext;
+
+/* The context is large (1 MB flash buffer); keep it off the thread stack. */
+static KuContext g_ku;
+
+static int kuInitSharedNvm(KuContext* ctx)
+{
+ static whFlashCb flashCb = WH_FLASH_RAMSIM_CB;
+ static whNvmCb nvmCb = WH_NVM_FLASH_CB;
+ static whLockCb lockCb = POSIX_LOCK_CB;
+ int rc;
+
+ memset(ctx->flashMemory, 0xFF, sizeof(ctx->flashMemory));
+
+ ctx->flashCfg.size = KU_FLASH_RAM_SIZE;
+ ctx->flashCfg.sectorSize = KU_FLASH_SECTOR_SIZE;
+ ctx->flashCfg.pageSize = KU_FLASH_PAGE_SIZE;
+ ctx->flashCfg.erasedByte = 0xFF;
+ ctx->flashCfg.memory = ctx->flashMemory;
+
+ ctx->nvmFlashCfg.cb = &flashCb;
+ ctx->nvmFlashCfg.context = &ctx->flashCtx;
+ ctx->nvmFlashCfg.config = &ctx->flashCfg;
+
+ rc = wh_NvmFlash_Init(&ctx->nvmFlashCtx, &ctx->nvmFlashCfg);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("NVM flash init failed: %d\n", rc);
+ return rc;
+ }
+
+ /* Error-checking mutex catches lock misuse (double-unlock, etc.). */
+ memset(&ctx->nvmLockCtx, 0, sizeof(ctx->nvmLockCtx));
+ pthread_mutexattr_init(&ctx->mutexAttr);
+ pthread_mutexattr_settype(&ctx->mutexAttr, PTHREAD_MUTEX_ERRORCHECK);
+ ctx->posixLockCfg.attr = &ctx->mutexAttr;
+ ctx->lockCfg.cb = &lockCb;
+ ctx->lockCfg.context = &ctx->nvmLockCtx;
+ ctx->lockCfg.config = &ctx->posixLockCfg;
+
+ ctx->nvmCfg.cb = &nvmCb;
+ ctx->nvmCfg.context = &ctx->nvmFlashCtx;
+ ctx->nvmCfg.config = &ctx->nvmFlashCfg;
+ ctx->nvmCfg.lockConfig = &ctx->lockCfg;
+
+ rc = wh_Nvm_Init(&ctx->nvm, &ctx->nvmCfg);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("NVM init failed: %d\n", rc);
+ return rc;
+ }
+ return WH_ERROR_OK;
+}
+
+static int kuInitPair(KuContext* ctx, int idx)
+{
+ KuPair* pair = &ctx->pairs[idx];
+ int rc;
+
+ pair->idx = idx;
+ pair->shared = ctx;
+
+ pair->tmConfig.req = (whTransportMemCsr*)pair->reqBuf;
+ pair->tmConfig.req_size = sizeof(pair->reqBuf);
+ pair->tmConfig.resp = (whTransportMemCsr*)pair->respBuf;
+ pair->tmConfig.resp_size = sizeof(pair->respBuf);
+
+ memset(&pair->clientTransportCtx, 0, sizeof(pair->clientTransportCtx));
+ pair->clientCommConfig.transport_cb = &clientTransportCb;
+ pair->clientCommConfig.transport_context = &pair->clientTransportCtx;
+ pair->clientCommConfig.transport_config = &pair->tmConfig;
+ /* client_id must be in [1, WH_CLIENT_ID_MAX]; distinct per pair. */
+ pair->clientCommConfig.client_id = (uint8_t)(1 + idx);
+ pair->clientConfig.comm = &pair->clientCommConfig;
+
+ memset(&pair->serverTransportCtx, 0, sizeof(pair->serverTransportCtx));
+ pair->serverCommConfig.transport_cb = &serverTransportCb;
+ pair->serverCommConfig.transport_context = &pair->serverTransportCtx;
+ pair->serverCommConfig.transport_config = &pair->tmConfig;
+ pair->serverCommConfig.server_id = (uint16_t)(200 + idx);
+
+ rc = wc_InitRng_ex(pair->cryptoCtx.rng, NULL, INVALID_DEVID);
+ if (rc != 0) {
+ WH_ERROR_PRINT("RNG init failed for pair %d: %d\n", idx, rc);
+ return rc;
+ }
+
+ /* All servers share the one locked NVM. */
+ pair->serverConfig.comm_config = &pair->serverCommConfig;
+ pair->serverConfig.nvm = &ctx->nvm;
+ pair->serverConfig.crypto = &pair->cryptoCtx;
+ pair->serverConfig.devId = INVALID_DEVID;
+
+ /* Init the client here (main thread) to avoid concurrent wolfCrypt
+ * init/register from the worker threads. */
+ rc = wh_Client_Init(&pair->client, &pair->clientConfig);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Client %d init failed: %d\n", idx, rc);
+ wc_FreeRng(pair->cryptoCtx.rng);
+ return rc;
+ }
+ return WH_ERROR_OK;
+}
+
+static void kuCleanupPair(KuPair* pair)
+{
+ wh_Client_Cleanup(&pair->client);
+ wc_FreeRng(pair->cryptoCtx.rng);
+}
+
+/* Serve requests for one pair until stopFlag is set. */
+static void* kuServerThread(void* arg)
+{
+ KuPair* pair = (KuPair*)arg;
+ KuContext* ctx = pair->shared;
+ int rc;
+
+ rc = wh_Server_Init(&pair->server, &pair->serverConfig);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Server %d init failed: %d\n", pair->idx, rc);
+ KU_ATOMIC_STORE(&ctx->serverError, 1);
+ KU_ATOMIC_ADD(&ctx->serversReady, 1);
+ return NULL;
+ }
+ rc = wh_Server_SetConnected(&pair->server, WH_COMM_CONNECTED);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Server %d SetConnected failed: %d\n", pair->idx, rc);
+ KU_ATOMIC_STORE(&ctx->serverError, 1);
+ wh_Server_Cleanup(&pair->server);
+ KU_ATOMIC_ADD(&ctx->serversReady, 1);
+ return NULL;
+ }
+
+ /* Announce readiness; main waits for all servers before clients run. */
+ KU_ATOMIC_ADD(&ctx->serversReady, 1);
+
+ while (!KU_ATOMIC_LOAD(&ctx->stopFlag)) {
+ rc = wh_Server_HandleRequestMessage(&pair->server);
+ if (rc == WH_ERROR_NOTREADY) {
+ sched_yield();
+ }
+ /* Other per-request errors surface to the client as response codes. */
+ }
+
+ wh_Server_Cleanup(&pair->server);
+ return NULL;
+}
+
+/* Per-round detector, run by client 0 once every keygen has landed. */
+static void kuCheckRound(KuContext* ctx, int round)
+{
+ int i;
+ int j;
+
+ for (i = 0; i < KU_NUM_CLIENTS; i++) {
+ int rc = ctx->roundRc[i];
+ if (rc == WH_ERROR_OK) {
+ KU_ATOMIC_ADD(&ctx->successes, 1);
+ }
+ else if (rc != WH_ERROR_NOSPACE) {
+ /* NOSPACE is expected: the global id/cache space is finite under
+ * contention. Any other failure invalidates the round. */
+ KU_ATOMIC_ADD(&ctx->hardErrors, 1);
+ WH_ERROR_PRINT("%s round %d: client %d keygen failed: %d\n",
+ ctx->algoName, round, i, rc);
+ }
+ }
+
+ /* Two successful concurrent keygens must never auto-allocate the same
+ * id. */
+ for (i = 0; i < KU_NUM_CLIENTS; i++) {
+ if (ctx->roundRc[i] != WH_ERROR_OK) {
+ continue;
+ }
+ for (j = i + 1; j < KU_NUM_CLIENTS; j++) {
+ if (ctx->roundRc[j] != WH_ERROR_OK) {
+ continue;
+ }
+ if (ctx->roundIds[i] == ctx->roundIds[j]) {
+ KU_ATOMIC_ADD(&ctx->collisions, 1);
+ WH_ERROR_PRINT(
+ "%s round %d: id collision 0x%04X (clients %d and %d)\n",
+ ctx->algoName, round, (unsigned)ctx->roundIds[i], i, j);
+ }
+ }
+ }
+}
+
+/* Run ctx->rounds barrier-aligned keygens for the current algorithm. */
+static void* kuClientThread(void* arg)
+{
+ KuPair* pair = (KuPair*)arg;
+ KuContext* ctx = pair->shared;
+ int round;
+
+ for (round = 0; round < ctx->rounds; round++) {
+ whKeyId id = WH_KEYID_ERASED;
+ int rc;
+
+ /* Line all clients up so their id allocations overlap. */
+ pthread_barrier_wait(&ctx->roundStart);
+
+ rc = ctx->genFn(&pair->client, &id);
+ ctx->roundRc[pair->idx] = rc;
+ ctx->roundIds[pair->idx] = (rc == WH_ERROR_OK) ? id : WH_KEYID_ERASED;
+
+ /* All ids recorded before the detector reads them. */
+ pthread_barrier_wait(&ctx->roundMid);
+
+ if (pair->idx == 0) {
+ kuCheckRound(ctx, round);
+ }
+
+ /* Drop this round's key so the next round starts from a clean cache
+ * (best effort: a colliding id may already be gone). */
+ if (rc == WH_ERROR_OK) {
+ (void)wh_Client_KeyEvict(&pair->client, id);
+ }
+
+ /* Hold everyone until this round's keys are evicted. */
+ pthread_barrier_wait(&ctx->roundEnd);
+ }
+ return NULL;
+}
+
+int whTest_KeygenUniqueIdConcurrent(void* ctx_arg)
+{
+ KuContext* ctx = &g_ku;
+ int i;
+ int a;
+ int rc;
+ int result = WH_TEST_SUCCESS;
+ int serversUp = 0;
+ int nvmInited = 0;
+ int pairsInited = 0;
+ int barriersInited = 0;
+
+ (void)ctx_arg;
+
+ if (KU_NUM_ALGOS == 0) {
+ return WH_TEST_SKIPPED;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ WH_TEST_PRINT(" Concurrent keygen unique-id: %d clients, %d algo(s)\n",
+ KU_NUM_CLIENTS, KU_NUM_ALGOS);
+
+ rc = wolfCrypt_Init();
+ if (rc != 0) {
+ WH_ERROR_PRINT("wolfCrypt_Init failed: %d\n", rc);
+ return rc;
+ }
+
+ rc = kuInitSharedNvm(ctx);
+ if (rc != WH_ERROR_OK) {
+ result = rc;
+ goto out;
+ }
+ nvmInited = 1;
+
+ for (i = 0; i < KU_NUM_CLIENTS; i++) {
+ rc = kuInitPair(ctx, i);
+ if (rc != WH_ERROR_OK) {
+ result = rc;
+ goto out;
+ }
+ pairsInited = i + 1;
+ }
+
+ /* Round barriers synchronize the client threads only. */
+ if (pthread_barrier_init(&ctx->roundStart, NULL, KU_NUM_CLIENTS) != 0 ||
+ pthread_barrier_init(&ctx->roundMid, NULL, KU_NUM_CLIENTS) != 0 ||
+ pthread_barrier_init(&ctx->roundEnd, NULL, KU_NUM_CLIENTS) != 0) {
+ WH_ERROR_PRINT("barrier init failed\n");
+ result = WH_ERROR_ABORTED;
+ goto out;
+ }
+ barriersInited = 1;
+
+ /* Bring up the server threads and wait until all are connected. */
+ for (i = 0; i < KU_NUM_CLIENTS; i++) {
+ rc = pthread_create(&ctx->pairs[i].serverThread, NULL, kuServerThread,
+ &ctx->pairs[i]);
+ if (rc != 0) {
+ WH_ERROR_PRINT("server thread %d create failed: %d\n", i, rc);
+ KU_ATOMIC_STORE(&ctx->stopFlag, 1);
+ result = WH_ERROR_ABORTED;
+ goto join_servers;
+ }
+ serversUp = i + 1;
+ }
+ while (KU_ATOMIC_LOAD(&ctx->serversReady) < serversUp) {
+ sched_yield();
+ }
+
+ if (KU_ATOMIC_LOAD(&ctx->serverError)) {
+ WH_ERROR_PRINT("a server failed to start\n");
+ result = WH_ERROR_ABORTED;
+ goto stop_servers;
+ }
+
+ /* Run each algorithm: spawn client threads, run all rounds, join. */
+ for (a = 0; a < KU_NUM_ALGOS; a++) {
+ /* Snapshot so the per-algorithm line reports deltas; the counters
+ * stay cumulative for the final pass/fail check. */
+ int okBefore = KU_ATOMIC_LOAD(&ctx->successes);
+ int collisionsBefore = KU_ATOMIC_LOAD(&ctx->collisions);
+
+ ctx->genFn = kuAlgos[a].gen;
+ ctx->rounds = kuAlgos[a].rounds;
+ ctx->algoName = kuAlgos[a].name;
+ memset((void*)ctx->roundIds, 0, sizeof(ctx->roundIds));
+ memset((void*)ctx->roundRc, 0, sizeof(ctx->roundRc));
+
+ for (i = 0; i < KU_NUM_CLIENTS; i++) {
+ rc = pthread_create(&ctx->pairs[i].clientThread, NULL,
+ kuClientThread, &ctx->pairs[i]);
+ if (rc != 0) {
+ WH_ERROR_PRINT("client thread %d create failed: %d\n", i, rc);
+ /* A short barrier party would hang the started clients; join
+ * them first, then abort. Creation failure is not expected. */
+ result = WH_ERROR_ABORTED;
+ for (--i; i >= 0; i--) {
+ pthread_join(ctx->pairs[i].clientThread, NULL);
+ }
+ goto stop_servers;
+ }
+ }
+ for (i = 0; i < KU_NUM_CLIENTS; i++) {
+ pthread_join(ctx->pairs[i].clientThread, NULL);
+ }
+
+ WH_TEST_PRINT(
+ " %-10s: %d rounds x %d clients, %d ok, %d collision(s)\n",
+ kuAlgos[a].name, kuAlgos[a].rounds, KU_NUM_CLIENTS,
+ KU_ATOMIC_LOAD(&ctx->successes) - okBefore,
+ KU_ATOMIC_LOAD(&ctx->collisions) - collisionsBefore);
+ }
+
+stop_servers:
+ KU_ATOMIC_STORE(&ctx->stopFlag, 1);
+
+join_servers:
+ for (i = 0; i < serversUp; i++) {
+ pthread_join(ctx->pairs[i].serverThread, NULL);
+ }
+
+ if (result == WH_TEST_SUCCESS) {
+ if (KU_ATOMIC_LOAD(&ctx->collisions) != 0) {
+ WH_ERROR_PRINT("FAILED: %d unique-id collision(s) detected\n",
+ KU_ATOMIC_LOAD(&ctx->collisions));
+ result = WH_ERROR_ABORTED;
+ }
+ else if (KU_ATOMIC_LOAD(&ctx->hardErrors) != 0) {
+ WH_ERROR_PRINT("FAILED: %d keygen error(s) during the run\n",
+ KU_ATOMIC_LOAD(&ctx->hardErrors));
+ result = WH_ERROR_ABORTED;
+ }
+ }
+
+out:
+ if (barriersInited) {
+ pthread_barrier_destroy(&ctx->roundStart);
+ pthread_barrier_destroy(&ctx->roundMid);
+ pthread_barrier_destroy(&ctx->roundEnd);
+ }
+ for (i = 0; i < pairsInited; i++) {
+ kuCleanupPair(&ctx->pairs[i]);
+ }
+ if (nvmInited) {
+ pthread_mutexattr_destroy(&ctx->mutexAttr);
+ wh_Nvm_Cleanup(&ctx->nvm);
+ }
+ wolfCrypt_Cleanup();
+
+ return result;
+}
+
+#endif /* WH_KU_ENABLED */
diff --git a/test-refactor/posix/wh_test_keygen_unique_id.h b/test-refactor/posix/wh_test_keygen_unique_id.h
new file mode 100644
index 000000000..82f2772b8
--- /dev/null
+++ b/test-refactor/posix/wh_test_keygen_unique_id.h
@@ -0,0 +1,34 @@
+/*
+ * 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/posix/wh_test_keygen_unique_id.h
+ *
+ * Concurrent keygen unique-id test. See the .c file for details.
+ */
+
+#ifndef WH_TEST_KEYGEN_UNIQUE_ID_H_
+#define WH_TEST_KEYGEN_UNIQUE_ID_H_
+
+/* Self-contained: spins up its own shared NVM and N client/server pairs.
+ * Matches the whTestGroup_RunOne() entry-point contract; ctx is unused.
+ * Returns WH_TEST_SUCCESS, WH_TEST_SKIPPED when the required build features
+ * are absent, or a negative error code on failure. */
+int whTest_KeygenUniqueIdConcurrent(void* ctx);
+
+#endif /* WH_TEST_KEYGEN_UNIQUE_ID_H_ */
diff --git a/test-refactor/posix/wh_test_keyread_race.c b/test-refactor/posix/wh_test_keyread_race.c
new file mode 100644
index 000000000..aa1c15f4b
--- /dev/null
+++ b/test-refactor/posix/wh_test_keyread_race.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/posix/wh_test_keyread_race.c
+ *
+ * Concurrent cached-key read test for the crypto request handlers in
+ * src/wh_server_crypto.c. Companion to wh_test_keygen_unique_id.c, which
+ * covers the write side.
+ *
+ * KR_NUM_CLIENTS client/server pairs share a single locked NVM, pre-seeded
+ * with KR_NUM_KEYS committed global AES keys -- more keys than the shared
+ * cache has slots, so most reads miss and reload from NVM. Each key is a
+ * distinct constant byte and therefore encrypts a fixed plaintext to a
+ * distinct, pre-computed ciphertext. Clients run barrier-aligned rounds,
+ * each asking its server to AES-ECB encrypt that plaintext under a rotating
+ * key id and comparing against the expected ciphertext; a mismatch means
+ * the handler read the wrong key material out of the cache.
+ *
+ * Requires WOLFHSM_CFG_THREADSAFE and WOLFHSM_CFG_GLOBAL_KEYS: global keys
+ * (USER == 0) live in the one shared server->nvm->globalCache, so the reads
+ * contend on the same slots. Under TSAN (TSAN=1) any unserialized access to
+ * that cache is additionally reported as a data race.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+/* pthread_barrier_t is unavailable on macOS. AES-ECB with a server-cached
+ * key is the operation under test, so gate on it too. */
+#if defined(WOLFHSM_CFG_THREADSAFE) && defined(WOLFHSM_CFG_TEST_POSIX) && \
+ defined(WOLFHSM_CFG_GLOBAL_KEYS) && defined(WOLFHSM_CFG_ENABLE_CLIENT) && \
+ defined(WOLFHSM_CFG_ENABLE_SERVER) && !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ !defined(NO_AES) && defined(HAVE_AES_ECB) && !defined(__APPLE__)
+#define WH_KR_ENABLED
+#endif
+
+#include "wh_test_common.h"
+#include "wh_test_list.h" /* WH_TEST_SKIPPED */
+#include "wh_test_keyread_race.h"
+
+#ifndef WH_KR_ENABLED
+
+int whTest_KeyReadRace(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+#else /* WH_KR_ENABLED */
+
+#include
+#include
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_transport_mem.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_client_crypto.h"
+#include "wolfhsm/wh_server.h"
+#include "wolfhsm/wh_nvm.h"
+#include "wolfhsm/wh_nvm_flash.h"
+#include "wolfhsm/wh_flash_ramsim.h"
+#include "wolfhsm/wh_lock.h"
+#include "wolfhsm/wh_keyid.h"
+#include "wolfhsm/wh_common.h"
+
+#include "port/posix/posix_lock.h"
+
+#include "wolfssl/wolfcrypt/settings.h"
+#include "wolfssl/wolfcrypt/types.h"
+#include "wolfssl/wolfcrypt/error-crypt.h"
+#include "wolfssl/wolfcrypt/aes.h"
+
+/* TSAN transport shims: the mem transport's notify counter establishes
+ * happens-before between client and server, but TSAN can't see it.
+ * Annotating the send/recv pairs keeps the analysis on keystore/NVM
+ * locking instead of transport false positives. */
+#ifdef WOLFHSM_CFG_TEST_STRESS_TSAN
+
+#ifndef __has_feature
+#define __has_feature(x) 0
+#endif
+#if !defined(__SANITIZE_THREAD__) && !__has_feature(thread_sanitizer)
+#error ThreadSanitizer not enabled for this build
+#endif
+
+#include
+
+static int kr_SendRequest(void* c, uint16_t len, const void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ __tsan_release((void*)ctx->req);
+ return wh_TransportMem_SendRequest(c, len, data);
+}
+static int kr_RecvResponse(void* c, uint16_t* out_len, void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ int rc = wh_TransportMem_RecvResponse(c, out_len, data);
+ if (rc == WH_ERROR_OK) {
+ __tsan_acquire((void*)ctx->resp);
+ }
+ return rc;
+}
+static int kr_RecvRequest(void* c, uint16_t* out_len, void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ int rc = wh_TransportMem_RecvRequest(c, out_len, data);
+ if (rc == WH_ERROR_OK) {
+ __tsan_acquire((void*)ctx->req);
+ }
+ return rc;
+}
+static int kr_SendResponse(void* c, uint16_t len, const void* data)
+{
+ whTransportMemContext* ctx = (whTransportMemContext*)c;
+ __tsan_release((void*)ctx->resp);
+ return wh_TransportMem_SendResponse(c, len, data);
+}
+
+static const whTransportClientCb clientTransportCb = {
+ .Init = wh_TransportMem_InitClear,
+ .Send = kr_SendRequest,
+ .Recv = kr_RecvResponse,
+ .Cleanup = wh_TransportMem_Cleanup,
+};
+static const whTransportServerCb serverTransportCb = {
+ .Init = wh_TransportMem_Init,
+ .Recv = kr_RecvRequest,
+ .Send = kr_SendResponse,
+ .Cleanup = wh_TransportMem_Cleanup,
+};
+
+#else /* !WOLFHSM_CFG_TEST_STRESS_TSAN */
+
+static const whTransportClientCb clientTransportCb = WH_TRANSPORT_MEM_CLIENT_CB;
+static const whTransportServerCb serverTransportCb = WH_TRANSPORT_MEM_SERVER_CB;
+
+#endif /* WOLFHSM_CFG_TEST_STRESS_TSAN */
+
+#define KR_ATOMIC_LOAD(ptr) __atomic_load_n((ptr), __ATOMIC_ACQUIRE)
+#define KR_ATOMIC_STORE(ptr, v) __atomic_store_n((ptr), (v), __ATOMIC_RELEASE)
+#define KR_ATOMIC_ADD(ptr, v) __atomic_add_fetch((ptr), (v), __ATOMIC_ACQ_REL)
+
+/* Four concurrent client/server pairs sharing one NVM. */
+#define KR_NUM_CLIENTS 4
+
+/* More keys than the shared small-key cache has slots (16 by default), so
+ * most reads miss and race to claim a slot and reload from NVM, but within
+ * the NVM directory capacity (WOLFHSM_CFG_NVM_OBJECT_COUNT, default 32). */
+#define KR_NUM_KEYS 24
+
+/* AES-128: a key size every AES build supports. Each key is a distinct
+ * constant byte, so one wrong key byte changes the whole ciphertext block
+ * by the cipher's avalanche. */
+#define KR_KEYSZ AES_128_KEY_SIZE
+
+/* Rounds per client, each barrier-aligned so the server-side reads overlap. */
+#define KR_ROUNDS 400
+
+#define KR_FLASH_RAM_SIZE (1024 * 1024)
+#define KR_FLASH_SECTOR_SIZE (128 * 1024)
+#define KR_FLASH_PAGE_SIZE 8
+
+/* One 16-byte block in, key referenced by id. */
+#define KR_BUFFER_SIZE 4096
+
+/* Distinct nonzero constant per key. Keys 0..KR_NUM_KEYS-1 -> 0x01..0x18. */
+#define KR_KEYBYTE(i) ((uint8_t)(0x01 + (i)))
+/* Server-internal id of the i-th seeded global key (ids start at 1). */
+#define KR_SERVER_KEYID(i) \
+ WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_KEYUSER_GLOBAL, (whNvmId)((i) + 1))
+/* Client-facing id the client passes to reference that same global key. */
+#define KR_CLIENT_KEYID(i) WH_CLIENT_KEYID_MAKE_GLOBAL((whKeyId)((i) + 1))
+
+struct KrContext;
+
+typedef struct {
+ uint8_t reqBuf[KR_BUFFER_SIZE];
+ uint8_t respBuf[KR_BUFFER_SIZE];
+ whTransportMemConfig tmConfig;
+
+ whTransportMemClientContext clientTransportCtx;
+ whCommClientConfig clientCommConfig;
+ whClientContext client;
+ whClientConfig clientConfig;
+
+ whTransportMemServerContext serverTransportCtx;
+ whCommServerConfig serverCommConfig;
+ whServerContext server;
+ whServerConfig serverConfig;
+ whServerCryptoContext cryptoCtx;
+
+ pthread_t clientThread;
+ pthread_t serverThread;
+ int idx;
+
+ struct KrContext* shared;
+} KrPair;
+
+typedef struct KrContext {
+ /* Shared, locked NVM */
+ uint8_t flashMemory[KR_FLASH_RAM_SIZE];
+ whFlashRamsimCtx flashCtx;
+ whFlashRamsimCfg flashCfg;
+ whNvmFlashContext nvmFlashCtx;
+ whNvmFlashConfig nvmFlashCfg;
+ whNvmContext nvm;
+ whNvmConfig nvmCfg;
+
+ posixLockContext nvmLockCtx;
+ pthread_mutexattr_t mutexAttr;
+ posixLockConfig posixLockCfg;
+ whLockConfig lockCfg;
+
+ KrPair pairs[KR_NUM_CLIENTS];
+
+ /* Fixed plaintext block and the per-key ciphertext oracle, computed once
+ * at setup with software AES. */
+ uint8_t plaintext[AES_BLOCK_SIZE];
+ uint8_t expected[KR_NUM_KEYS][AES_BLOCK_SIZE];
+
+ /* A readiness counter rather than a barrier, so a server that fails to
+ * spawn does not hang the others. */
+ volatile int serversReady;
+ volatile int serverError;
+ volatile int stopFlag;
+
+ /* Per-round client synchronization */
+ pthread_barrier_t roundStart;
+ pthread_barrier_t roundEnd;
+
+ /* Outcome counters, per client to avoid cross-thread contention. */
+ volatile int mismatches[KR_NUM_CLIENTS];
+ volatile int hardErrors[KR_NUM_CLIENTS];
+ volatile int successes[KR_NUM_CLIENTS];
+} KrContext;
+
+/* The context is large (1 MB flash buffer); keep it off the thread stack. */
+static KrContext g_kr;
+
+static int krInitSharedNvm(KrContext* ctx)
+{
+ static whFlashCb flashCb = WH_FLASH_RAMSIM_CB;
+ static whNvmCb nvmCb = WH_NVM_FLASH_CB;
+ static whLockCb lockCb = POSIX_LOCK_CB;
+ int rc;
+
+ memset(ctx->flashMemory, 0xFF, sizeof(ctx->flashMemory));
+
+ ctx->flashCfg.size = KR_FLASH_RAM_SIZE;
+ ctx->flashCfg.sectorSize = KR_FLASH_SECTOR_SIZE;
+ ctx->flashCfg.pageSize = KR_FLASH_PAGE_SIZE;
+ ctx->flashCfg.erasedByte = 0xFF;
+ ctx->flashCfg.memory = ctx->flashMemory;
+
+ ctx->nvmFlashCfg.cb = &flashCb;
+ ctx->nvmFlashCfg.context = &ctx->flashCtx;
+ ctx->nvmFlashCfg.config = &ctx->flashCfg;
+
+ rc = wh_NvmFlash_Init(&ctx->nvmFlashCtx, &ctx->nvmFlashCfg);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("NVM flash init failed: %d\n", rc);
+ return rc;
+ }
+
+ /* Error-checking mutex catches lock misuse (double-unlock, etc.). */
+ memset(&ctx->nvmLockCtx, 0, sizeof(ctx->nvmLockCtx));
+ pthread_mutexattr_init(&ctx->mutexAttr);
+ pthread_mutexattr_settype(&ctx->mutexAttr, PTHREAD_MUTEX_ERRORCHECK);
+ ctx->posixLockCfg.attr = &ctx->mutexAttr;
+ ctx->lockCfg.cb = &lockCb;
+ ctx->lockCfg.context = &ctx->nvmLockCtx;
+ ctx->lockCfg.config = &ctx->posixLockCfg;
+
+ ctx->nvmCfg.cb = &nvmCb;
+ ctx->nvmCfg.context = &ctx->nvmFlashCtx;
+ ctx->nvmCfg.config = &ctx->nvmFlashCfg;
+ ctx->nvmCfg.lockConfig = &ctx->lockCfg;
+
+ rc = wh_Nvm_Init(&ctx->nvm, &ctx->nvmCfg);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("NVM init failed: %d\n", rc);
+ return rc;
+ }
+ return WH_ERROR_OK;
+}
+
+/* Commit KR_NUM_KEYS distinct global AES keys straight to NVM (not the
+ * cache), and compute the ciphertext each produces for the fixed plaintext. */
+static int krSeedKeys(KrContext* ctx)
+{
+ uint8_t data[KR_KEYSZ];
+ whNvmMetadata meta;
+ Aes aes[1];
+ int i;
+ int rc;
+
+ for (i = 0; i < AES_BLOCK_SIZE; i++) {
+ ctx->plaintext[i] = (uint8_t)(0xA0 + i);
+ }
+
+ for (i = 0; i < KR_NUM_KEYS; i++) {
+ memset(data, KR_KEYBYTE(i), sizeof(data));
+
+ memset(&meta, 0, sizeof(meta));
+ meta.id = KR_SERVER_KEYID(i);
+ meta.access = WH_NVM_ACCESS_ANY;
+ meta.flags = WH_NVM_FLAGS_USAGE_ANY;
+ meta.len = KR_KEYSZ;
+
+ rc = wh_Nvm_AddObject(&ctx->nvm, &meta, sizeof(data), data);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("seed key %d add failed: %d\n", i, rc);
+ return rc;
+ }
+
+ /* Independent software-AES oracle for this key. */
+ rc = wc_AesInit(aes, NULL, INVALID_DEVID);
+ if (rc == 0) {
+ rc = wc_AesSetKey(aes, data, sizeof(data), NULL, AES_ENCRYPTION);
+ }
+ if (rc == 0) {
+ rc = wc_AesEcbEncrypt(aes, ctx->expected[i], ctx->plaintext,
+ AES_BLOCK_SIZE);
+ }
+ (void)wc_AesFree(aes);
+ if (rc != 0) {
+ WH_ERROR_PRINT("oracle encrypt for key %d failed: %d\n", i, rc);
+ return WH_ERROR_ABORTED;
+ }
+ }
+ return WH_ERROR_OK;
+}
+
+static int krInitPair(KrContext* ctx, int idx)
+{
+ KrPair* pair = &ctx->pairs[idx];
+ int rc;
+
+ pair->idx = idx;
+ pair->shared = ctx;
+
+ pair->tmConfig.req = (whTransportMemCsr*)pair->reqBuf;
+ pair->tmConfig.req_size = sizeof(pair->reqBuf);
+ pair->tmConfig.resp = (whTransportMemCsr*)pair->respBuf;
+ pair->tmConfig.resp_size = sizeof(pair->respBuf);
+
+ memset(&pair->clientTransportCtx, 0, sizeof(pair->clientTransportCtx));
+ pair->clientCommConfig.transport_cb = &clientTransportCb;
+ pair->clientCommConfig.transport_context = &pair->clientTransportCtx;
+ pair->clientCommConfig.transport_config = &pair->tmConfig;
+ /* client_id must be in [1, WH_CLIENT_ID_MAX]; distinct per pair. */
+ pair->clientCommConfig.client_id = (uint8_t)(1 + idx);
+ pair->clientConfig.comm = &pair->clientCommConfig;
+
+ memset(&pair->serverTransportCtx, 0, sizeof(pair->serverTransportCtx));
+ pair->serverCommConfig.transport_cb = &serverTransportCb;
+ pair->serverCommConfig.transport_context = &pair->serverTransportCtx;
+ pair->serverCommConfig.transport_config = &pair->tmConfig;
+ pair->serverCommConfig.server_id = (uint16_t)(200 + idx);
+
+ rc = wc_InitRng_ex(pair->cryptoCtx.rng, NULL, INVALID_DEVID);
+ if (rc != 0) {
+ WH_ERROR_PRINT("RNG init failed for pair %d: %d\n", idx, rc);
+ return rc;
+ }
+
+ /* All servers share the one locked NVM. */
+ pair->serverConfig.comm_config = &pair->serverCommConfig;
+ pair->serverConfig.nvm = &ctx->nvm;
+ pair->serverConfig.crypto = &pair->cryptoCtx;
+ pair->serverConfig.devId = INVALID_DEVID;
+
+ /* Init the client here (main thread) to avoid concurrent wolfCrypt
+ * init/register from the worker threads. */
+ rc = wh_Client_Init(&pair->client, &pair->clientConfig);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Client %d init failed: %d\n", idx, rc);
+ wc_FreeRng(pair->cryptoCtx.rng);
+ return rc;
+ }
+ return WH_ERROR_OK;
+}
+
+static void krCleanupPair(KrPair* pair)
+{
+ wh_Client_Cleanup(&pair->client);
+ wc_FreeRng(pair->cryptoCtx.rng);
+}
+
+/* Serve requests for one pair until stopFlag is set. */
+static void* krServerThread(void* arg)
+{
+ KrPair* pair = (KrPair*)arg;
+ KrContext* ctx = pair->shared;
+ int rc;
+
+ rc = wh_Server_Init(&pair->server, &pair->serverConfig);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Server %d init failed: %d\n", pair->idx, rc);
+ KR_ATOMIC_STORE(&ctx->serverError, 1);
+ KR_ATOMIC_ADD(&ctx->serversReady, 1);
+ return NULL;
+ }
+ rc = wh_Server_SetConnected(&pair->server, WH_COMM_CONNECTED);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Server %d SetConnected failed: %d\n", pair->idx, rc);
+ KR_ATOMIC_STORE(&ctx->serverError, 1);
+ wh_Server_Cleanup(&pair->server);
+ KR_ATOMIC_ADD(&ctx->serversReady, 1);
+ return NULL;
+ }
+
+ /* Announce readiness; main waits for all servers before clients run. */
+ KR_ATOMIC_ADD(&ctx->serversReady, 1);
+
+ while (!KR_ATOMIC_LOAD(&ctx->stopFlag)) {
+ rc = wh_Server_HandleRequestMessage(&pair->server);
+ if (rc == WH_ERROR_NOTREADY) {
+ sched_yield();
+ }
+ /* Other per-request errors surface to the client as response codes. */
+ }
+
+ wh_Server_Cleanup(&pair->server);
+ return NULL;
+}
+
+/* Run KR_ROUNDS barrier-aligned AES-ECB encrypts against a rotating shared
+ * global key, checking each ciphertext against the oracle. */
+static void* krClientThread(void* arg)
+{
+ KrPair* pair = (KrPair*)arg;
+ KrContext* ctx = pair->shared;
+ int round;
+
+ for (round = 0; round < KR_ROUNDS; round++) {
+ /* Rotate the key per client so all keys cycle through the shared
+ * cache and evict one another. */
+ int keyIdx = (round * KR_NUM_CLIENTS + pair->idx) % KR_NUM_KEYS;
+ whKeyId cid = KR_CLIENT_KEYID(keyIdx);
+ uint8_t out[AES_BLOCK_SIZE];
+ Aes aes[1];
+ int rc;
+
+ memset(out, 0, sizeof(out));
+
+ /* Line all clients up so their server-side reads overlap. */
+ pthread_barrier_wait(&ctx->roundStart);
+
+ /* INVALID_DEVID: the request goes through the explicit client API
+ * below, not the wolfCrypt cryptocb, so the Aes struct only has to
+ * carry the key id. */
+ rc = wc_AesInit(aes, NULL, INVALID_DEVID);
+ if (rc == 0) {
+ rc = wh_Client_AesSetKeyId(aes, cid);
+ }
+ if (rc == 0) {
+ rc = wh_Client_AesEcb(&pair->client, aes, 1, ctx->plaintext,
+ AES_BLOCK_SIZE, out);
+ }
+ (void)wc_AesFree(aes);
+
+ if (rc != 0) {
+ /* Not a mismatch, but it invalidates the round. */
+ KR_ATOMIC_ADD(&ctx->hardErrors[pair->idx], 1);
+ WH_ERROR_PRINT("round %d: client %d ECB key 0x%04X failed: %d\n",
+ round, pair->idx, (unsigned)cid, rc);
+ }
+ else if (memcmp(out, ctx->expected[keyIdx], AES_BLOCK_SIZE) != 0) {
+ /* Ciphertext for some other key: the server read the wrong key
+ * material out of the shared cache. */
+ KR_ATOMIC_ADD(&ctx->mismatches[pair->idx], 1);
+ WH_ERROR_PRINT(
+ "round %d: client %d key 0x%04X ciphertext mismatch\n", round,
+ pair->idx, (unsigned)cid);
+ }
+ else {
+ KR_ATOMIC_ADD(&ctx->successes[pair->idx], 1);
+ }
+
+ /* Hold everyone until every read in this round is done. */
+ pthread_barrier_wait(&ctx->roundEnd);
+ }
+ return NULL;
+}
+
+int whTest_KeyReadRace(void* ctx_arg)
+{
+ KrContext* ctx = &g_kr;
+ int i;
+ int rc;
+ int result = WH_TEST_SUCCESS;
+ int serversUp = 0;
+ int nvmInited = 0;
+ int pairsInited = 0;
+ int barriersInited = 0;
+ int clientsStarted = 0;
+ int totalMiss = 0;
+ int totalHard = 0;
+ int totalSuccess = 0;
+
+ (void)ctx_arg;
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ WH_TEST_PRINT(" Concurrent key read: %d clients, %d keys, %d rounds\n",
+ KR_NUM_CLIENTS, KR_NUM_KEYS, KR_ROUNDS);
+
+ rc = wolfCrypt_Init();
+ if (rc != 0) {
+ WH_ERROR_PRINT("wolfCrypt_Init failed: %d\n", rc);
+ return rc;
+ }
+
+ rc = krInitSharedNvm(ctx);
+ if (rc != WH_ERROR_OK) {
+ result = rc;
+ goto out;
+ }
+ nvmInited = 1;
+
+ rc = krSeedKeys(ctx);
+ if (rc != WH_ERROR_OK) {
+ result = rc;
+ goto out;
+ }
+
+ for (i = 0; i < KR_NUM_CLIENTS; i++) {
+ rc = krInitPair(ctx, i);
+ if (rc != WH_ERROR_OK) {
+ result = rc;
+ goto out;
+ }
+ pairsInited = i + 1;
+ }
+
+ /* Round barriers synchronize the client threads only. */
+ if (pthread_barrier_init(&ctx->roundStart, NULL, KR_NUM_CLIENTS) != 0 ||
+ pthread_barrier_init(&ctx->roundEnd, NULL, KR_NUM_CLIENTS) != 0) {
+ WH_ERROR_PRINT("barrier init failed\n");
+ result = WH_ERROR_ABORTED;
+ goto out;
+ }
+ barriersInited = 1;
+
+ /* Bring up the server threads and wait until all are connected. */
+ for (i = 0; i < KR_NUM_CLIENTS; i++) {
+ rc = pthread_create(&ctx->pairs[i].serverThread, NULL, krServerThread,
+ &ctx->pairs[i]);
+ if (rc != 0) {
+ WH_ERROR_PRINT("server thread %d create failed: %d\n", i, rc);
+ KR_ATOMIC_STORE(&ctx->stopFlag, 1);
+ result = WH_ERROR_ABORTED;
+ goto join_servers;
+ }
+ serversUp = i + 1;
+ }
+ while (KR_ATOMIC_LOAD(&ctx->serversReady) < serversUp) {
+ sched_yield();
+ }
+
+ if (KR_ATOMIC_LOAD(&ctx->serverError)) {
+ WH_ERROR_PRINT("a server failed to start\n");
+ result = WH_ERROR_ABORTED;
+ goto stop_servers;
+ }
+
+ /* Spawn the client threads, run all rounds, join. */
+ for (i = 0; i < KR_NUM_CLIENTS; i++) {
+ rc = pthread_create(&ctx->pairs[i].clientThread, NULL, krClientThread,
+ &ctx->pairs[i]);
+ if (rc != 0) {
+ WH_ERROR_PRINT("client thread %d create failed: %d\n", i, rc);
+ /* A short barrier party would hang the started clients; join
+ * them first, then abort. Creation failure is not expected. */
+ result = WH_ERROR_ABORTED;
+ for (--i; i >= 0; i--) {
+ pthread_join(ctx->pairs[i].clientThread, NULL);
+ }
+ goto stop_servers;
+ }
+ clientsStarted = i + 1;
+ }
+ for (i = 0; i < clientsStarted; i++) {
+ pthread_join(ctx->pairs[i].clientThread, NULL);
+ }
+
+stop_servers:
+ KR_ATOMIC_STORE(&ctx->stopFlag, 1);
+
+join_servers:
+ for (i = 0; i < serversUp; i++) {
+ pthread_join(ctx->pairs[i].serverThread, NULL);
+ }
+
+ if (result == WH_TEST_SUCCESS) {
+ for (i = 0; i < KR_NUM_CLIENTS; i++) {
+ totalMiss += KR_ATOMIC_LOAD(&ctx->mismatches[i]);
+ totalHard += KR_ATOMIC_LOAD(&ctx->hardErrors[i]);
+ totalSuccess += KR_ATOMIC_LOAD(&ctx->successes[i]);
+ }
+ WH_TEST_PRINT(" reads=%d, mismatches=%d, hardErrors=%d\n",
+ totalSuccess, totalMiss, totalHard);
+ if (totalMiss != 0) {
+ WH_ERROR_PRINT("FAILED: %d ciphertext mismatch(es) detected\n",
+ totalMiss);
+ result = WH_ERROR_ABORTED;
+ }
+ else if (totalHard != 0) {
+ WH_ERROR_PRINT("FAILED: %d read error(s) during the run\n",
+ totalHard);
+ result = WH_ERROR_ABORTED;
+ }
+ }
+
+out:
+ if (barriersInited) {
+ pthread_barrier_destroy(&ctx->roundStart);
+ pthread_barrier_destroy(&ctx->roundEnd);
+ }
+ for (i = 0; i < pairsInited; i++) {
+ krCleanupPair(&ctx->pairs[i]);
+ }
+ if (nvmInited) {
+ pthread_mutexattr_destroy(&ctx->mutexAttr);
+ wh_Nvm_Cleanup(&ctx->nvm);
+ }
+ wolfCrypt_Cleanup();
+
+ return result;
+}
+
+#endif /* WH_KR_ENABLED */
diff --git a/test-refactor/posix/wh_test_keyread_race.h b/test-refactor/posix/wh_test_keyread_race.h
new file mode 100644
index 000000000..9dc7cd649
--- /dev/null
+++ b/test-refactor/posix/wh_test_keyread_race.h
@@ -0,0 +1,34 @@
+/*
+ * 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/posix/wh_test_keyread_race.h
+ *
+ * Concurrent cached-key read test. See the .c file for details.
+ */
+
+#ifndef WH_TEST_KEYREAD_RACE_H_
+#define WH_TEST_KEYREAD_RACE_H_
+
+/* Self-contained: spins up its own shared NVM and N client/server pairs.
+ * Matches the whTestGroup_RunOne() entry-point contract; ctx is unused.
+ * Returns WH_TEST_SUCCESS, WH_TEST_SKIPPED when the required build features
+ * are absent, or a negative error code on failure. */
+int whTest_KeyReadRace(void* ctx);
+
+#endif /* WH_TEST_KEYREAD_RACE_H_ */
diff --git a/test-refactor/posix/wh_test_posix_main.c b/test-refactor/posix/wh_test_posix_main.c
index 756ed6041..7cca15b97 100644
--- a/test-refactor/posix/wh_test_posix_main.c
+++ b/test-refactor/posix/wh_test_posix_main.c
@@ -47,6 +47,8 @@
#include "wh_test_posix_client.h"
#include "wh_test_posix_server.h"
+#include "wh_test_keygen_unique_id.h"
+#include "wh_test_keyread_race.h"
/* POSIX-only thread-safety stress test. Called directly rather
* than through the suite runner: the legacy test owns its own
@@ -284,6 +286,20 @@ int main(void)
if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
miscRc = rc;
}
+ /* Concurrent keygen and cached-key read tests. Both are
+ * self-contained (own shared NVM + client/server pairs), so they run
+ * in this pre-server slot rather than against the live
+ * _server/_client, and both skip themselves unless THREADSAFE +
+ * GLOBAL_KEYS + crypto are compiled in. */
+ rc = whTestGroup_RunOne("whTest_KeygenUniqueIdConcurrent",
+ whTest_KeygenUniqueIdConcurrent, NULL);
+ if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
+ miscRc = rc;
+ }
+ rc = whTestGroup_RunOne("whTest_KeyReadRace", whTest_KeyReadRace, NULL);
+ if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
+ miscRc = rc;
+ }
}
rc = pthread_create(&sthread, NULL, _serverThread, NULL);
diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c
index a5850cde5..65b3eb4b3 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -12184,9 +12184,11 @@ int whTestCrypto_MlDsaVerifyOnlyDma(whClientContext* ctx, int devId,
}
}
/* Import the key into wolfHSM via the wolfCrypt structure. This is the
- * DMA-only verify test, so always import via the DMA path. */
+ * DMA-only verify test, so always import via the DMA path. The verify
+ * usage flag is required: the DMA verify handler enforces it. */
if (ret == 0) {
- ret = wh_Client_MlDsaImportKeyDma(ctx, key, &keyId, 0, 0, NULL);
+ ret = wh_Client_MlDsaImportKeyDma(ctx, key, &keyId,
+ WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL);
if (ret == WH_ERROR_OK) {
evictKey = 1;
}
diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h
index 739172d37..5bf90b748 100644
--- a/wolfhsm/wh_server_crypto.h
+++ b/wolfhsm/wh_server_crypto.h
@@ -64,6 +64,12 @@ int wh_Server_CacheImportRsaKey(whServerContext* ctx, RsaKey* key,
/* Restore a RsaKey from a server key cache */
int wh_Server_CacheExportRsaKey(whServerContext* ctx, whKeyId keyId,
RsaKey* key);
+
+/* As wh_Server_CacheExportRsaKey, but checks the required usage flags against
+ * the same locked snapshot of the key. Acquires the NVM lock internally; must
+ * not be called with it held (the lock is non-recursive). */
+int wh_Server_CacheExportRsaKeyEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage, RsaKey* key);
#endif /* !NO_RSA */
#ifdef HAVE_ECC
@@ -72,6 +78,12 @@ int wh_Server_EccKeyCacheImport(whServerContext* ctx, ecc_key* key,
int wh_Server_EccKeyCacheExport(whServerContext* ctx, whKeyId keyId,
ecc_key* key);
+
+/* As wh_Server_EccKeyCacheExport, but checks the required usage flags against
+ * the same locked snapshot of the key. Acquires the NVM lock internally; must
+ * not be called with it held (the lock is non-recursive). */
+int wh_Server_EccKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage, ecc_key* key);
#endif
#ifdef HAVE_ED25519
@@ -81,6 +93,13 @@ int wh_Server_CacheImportEd25519Key(whServerContext* ctx, ed25519_key* key,
int wh_Server_CacheExportEd25519Key(whServerContext* ctx, whKeyId keyId,
ed25519_key* key);
+
+/* As wh_Server_CacheExportEd25519Key, but checks the required usage flags
+ * against the same locked snapshot of the key. Acquires the NVM lock
+ * internally; must not be called with it held (the lock is non-recursive). */
+int wh_Server_CacheExportEd25519KeyEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ ed25519_key* key);
#endif /* HAVE_ED25519 */
#ifdef HAVE_CURVE25519
@@ -92,6 +111,14 @@ int wh_Server_CacheImportCurve25519Key(whServerContext* server,
/* Restore a curve25519_key from a server key cache */
int wh_Server_CacheExportCurve25519Key(whServerContext* server, whKeyId keyId,
curve25519_key* key);
+
+/* As wh_Server_CacheExportCurve25519Key, but checks the required usage flags
+ * against the same locked snapshot of the key. Acquires the NVM lock
+ * internally; must not be called with it held (the lock is non-recursive). */
+int wh_Server_CacheExportCurve25519KeyEnforce(whServerContext* server,
+ whKeyId keyId,
+ whNvmFlags requiredUsage,
+ curve25519_key* key);
#endif /* HAVE_CURVE25519 */
#ifdef WOLFSSL_HAVE_MLDSA
@@ -102,6 +129,13 @@ int wh_Server_MlDsaKeyCacheImport(whServerContext* ctx, wc_MlDsaKey* key,
/* Restore a wc_MlDsaKey from a server key cache */
int wh_Server_MlDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId,
wc_MlDsaKey* key);
+
+/* As wh_Server_MlDsaKeyCacheExport, but checks the required usage flags
+ * against the same locked snapshot of the key. Acquires the NVM lock
+ * internally; must not be called with it held (the lock is non-recursive). */
+int wh_Server_MlDsaKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ wc_MlDsaKey* key);
#endif /* WOLFSSL_HAVE_MLDSA */
#ifdef WOLFSSL_HAVE_MLKEM
@@ -112,6 +146,13 @@ int wh_Server_MlKemKeyCacheImport(whServerContext* ctx, MlKemKey* key,
/* Restore a MlKemKey from a server key cache */
int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId,
MlKemKey* key);
+
+/* As wh_Server_MlKemKeyCacheExport, but checks the required usage flags
+ * against the same locked snapshot of the key. Acquires the NVM lock
+ * internally; must not be called with it held (the lock is non-recursive). */
+int wh_Server_MlKemKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ MlKemKey* key);
#endif /* WOLFSSL_HAVE_MLKEM */
/* Store raw key bytes into a server key cache slot with optional metadata.
diff --git a/wolfhsm/wh_server_keystore.h b/wolfhsm/wh_server_keystore.h
index d34588e7c..69458bec5 100644
--- a/wolfhsm/wh_server_keystore.h
+++ b/wolfhsm/wh_server_keystore.h
@@ -130,6 +130,40 @@ int wh_Server_KeystoreReadKeyChecked(whServerContext* server, whKeyId keyId,
whNvmMetadata* outMeta, uint8_t* out,
uint32_t* outSz);
+/**
+ * @brief Atomically read a key and enforce its usage policy
+ *
+ * Reads the key (as wh_Server_KeystoreReadKey) and checks the required usage
+ * flags against the same snapshot of the key, all under the NVM lock, so the
+ * policy checked can never belong to a different key generation than the key
+ * material returned. On a usage-policy failure the output buffer is cleared.
+ *
+ * Acquires WH_SERVER_NVM_LOCK internally under WOLFHSM_CFG_THREADSAFE. The
+ * lock is non-recursive: callers that already hold it (e.g. the SHE or cert
+ * request dispatch) must use wh_Server_KeystoreReadKey plus
+ * wh_Server_KeystoreEnforceKeyUsage directly instead.
+ *
+ * This copies the key out rather than handing back a pointer into the cache
+ * slot, which is what lets the caller use the key after the lock is dropped.
+ * The copy is bounded by the caller's buffer, so a key larger than *outSz is
+ * rejected with WH_ERROR_NOSPACE and nothing is written.
+ *
+ * @param[in] server Server context
+ * @param[in] keyId Key ID to read
+ * @param[in] requiredUsage Usage flags the key must have (may be
+ * WH_NVM_FLAGS_NONE for no usage requirement)
+ * @param[out] outMeta Key metadata (can be NULL)
+ * @param[out] out Buffer to store key data (can be NULL)
+ * @param[in,out] outSz Input: size of out buffer; Output: key size
+ * @return WH_ERROR_OK on success, WH_ERROR_USAGE if the key lacks the
+ * required usage flags, WH_ERROR_NOSPACE if the key does not fit in
+ * out, other error codes on read failure
+ */
+int wh_Server_KeystoreReadKeyEnforce(whServerContext* server, whKeyId keyId,
+ whNvmFlags requiredUsage,
+ whNvmMetadata* outMeta, uint8_t* out,
+ uint32_t* outSz);
+
/**
* @brief Remove a key from cache
*
@@ -280,11 +314,25 @@ int wh_Server_KeystoreEnforceKeyUsage(const whNvmMetadata* meta,
/**
* Validates that a key has the required usage policy flags set
*
+ * NOTE: this function may be deprecated soon. The policy check ends when it
+ * unlocks, so a caller that afterwards exports or uses the key does so in a
+ * separate lock scope and races an update to the key's usage flags. Prefer a
+ * primitive that enforces and reads under a single lock:
+ * wh_Server_KeystoreReadKeyEnforce for raw key bytes, or the typed
+ * wh_Server_*CacheExport*Enforce wrappers in wh_server_crypto.h. This function
+ * remains only for callers that genuinely need a standalone policy check and
+ * never touch the key material.
+ *
* This function enforces key usage policies by checking that the specified
* key has all the required usage flags set in its metadata. It retrieves
* the key metadata from the cache or NVM storage and performs a bitwise
* check against the required flags.
*
+ * Acquires WH_SERVER_NVM_LOCK internally under WOLFHSM_CFG_THREADSAFE. The
+ * lock is non-recursive: callers that already hold it (e.g. the SHE or cert
+ * request dispatch) must use wh_Server_KeystoreFreshenKey plus
+ * wh_Server_KeystoreEnforceKeyUsage directly instead.
+ *
* @param server Pointer to the server context
* @param keyId The translated server keyId (after client keyId translation)
* @param requiredUsage The required usage policy flags (e.g.,
diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h
index 1139fee9c..a6054c62b 100644
--- a/wolfhsm/wh_settings.h
+++ b/wolfhsm/wh_settings.h
@@ -84,6 +84,10 @@
* WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE - Size of each key in RAM
* Default: 1200
*
+ * WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE - Largest cached key usable as a KDF
+ * input (HKDF IKM, CMAC-KDF Z)
+ * Default: 256
+ *
* WOLFHSM_CFG_SERVER_CUSTOMCB_COUNT - Number of additional callbacks
* Default: 8
*
@@ -466,6 +470,15 @@
#endif /* WOLFHSM_CFG_KEYWRAP */
+#if defined(HAVE_HKDF) || defined(HAVE_CMAC_KDF)
+
+/* Largest cached key the server accepts as a KDF input supplied by key ID */
+#ifndef WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE
+#define WOLFHSM_CFG_SERVER_KDF_MAX_KEY_SIZE 256
+#endif
+
+#endif /* HAVE_HKDF || HAVE_CMAC_KDF */
+
#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT)
#if !defined(WOLFSSL_ACERT) || !defined(WOLFSSL_ASN_TEMPLATE)
#error \