diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c index 8f7076679..e1db7f46e 100644 --- a/src/wh_server_cert.c +++ b/src/wh_server_cert.c @@ -359,6 +359,8 @@ static int DerNextSequence(const uint8_t* input, uint32_t maxIdx, } +/* Caller (the cert request dispatch) holds the non-recursive + * WH_SERVER_NVM_LOCK, so only unlocked keystore primitives may be used here. */ static int _verifyChainAgainstCmStore( whServerContext* server, WOLFSSL_CERT_MANAGER* cm, const uint8_t* chain, uint32_t chain_len, const whNvmId* trustedRootNvmIds, uint16_t numRoots, @@ -486,6 +488,7 @@ static int _verifyChainAgainstCmStore( if (WH_KEYID_ISERASED(*inout_keyId)) { rc = wh_Server_KeystoreGetUniqueId(server, inout_keyId); if (rc != WH_ERROR_OK) { + wc_FreeDecodedCert(&dc); return rc; } } @@ -1165,7 +1168,7 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, if (rc == WH_ERROR_OK) { rc = wh_Server_CertVerifyCache_Clear(server); (void)WH_SERVER_NVM_UNLOCK(server); - } + } /* WH_SERVER_NVM_LOCK() */ #else rc = wh_Server_CertVerifyCache_Clear(server); #endif @@ -1193,7 +1196,7 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, rc = wh_Server_CertVerifyCache_SetEnabled(server, req.enable); (void)WH_SERVER_NVM_UNLOCK(server); - } + } /* WH_SERVER_NVM_LOCK() */ #else rc = wh_Server_CertVerifyCache_SetEnabled(server, req.enable); #endif diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 5b557973d..2e23deb09 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -230,6 +230,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); + } /* WH_SERVER_NVM_LOCK() */ +} + /** Public server crypto functions */ #ifndef NO_RSA @@ -302,6 +317,34 @@ int wh_Server_CacheExportRsaKey(whServerContext* ctx, whKeyId keyId, return ret; } +static int _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); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} + #ifdef WOLFSSL_KEY_GEN static int _HandleRsaKeyGen(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t inSize, @@ -360,21 +403,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) { /* Best-effort public key export: when the serialized @@ -454,81 +500,74 @@ static int _HandleRsaFunction(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_SERVER_VERBOSE("HandleRsaFunction opType:%d inLen:%u keyId:%u outLen:%u\n", op_type, in_len, key_id, out_len); - switch (op_type) - { - case RSA_PUBLIC_ENCRYPT: - case RSA_PUBLIC_DECRYPT: - case RSA_PRIVATE_ENCRYPT: - case RSA_PRIVATE_DECRYPT: - /* Valid op_types */ - break; - default: - /* Invalid opType */ - WH_DEBUG_SERVER_VERBOSE("Unknown opType:%d\n", op_type); - - 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) { + switch (op_type) { + case RSA_PUBLIC_ENCRYPT: + case RSA_PUBLIC_DECRYPT: + case RSA_PRIVATE_ENCRYPT: + case RSA_PRIVATE_DECRYPT: + /* Valid op_types */ + break; + default: + /* Invalid opType */ + WH_DEBUG_SERVER_VERBOSE("Unknown opType:%d\n", op_type); + + return BAD_FUNC_ARG; + } + + /* 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 = _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 = _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 = _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; @@ -571,9 +610,9 @@ 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 = _CacheExportRsaKeyEnforce(ctx, key_id, WH_NVM_FLAGS_NONE, rsa); /* get the size */ if (ret == 0) { key_size = wc_RsaEncryptSize(rsa); @@ -587,7 +626,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; @@ -663,6 +702,34 @@ int wh_Server_EccKeyCacheExport(whServerContext* ctx, whKeyId keyId, } return ret; } + +static int _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); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} #endif /* HAVE_ECC */ #ifdef HAVE_ED25519 @@ -721,6 +788,37 @@ int wh_Server_CacheExportEd25519Key(whServerContext* ctx, whKeyId keyId, } return ret; } + +static int _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); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} #endif /* HAVE_ED25519 */ #ifdef HAVE_CURVE25519 @@ -786,6 +884,38 @@ int wh_Server_CacheExportCurve25519Key(whServerContext* server, whKeyId keyId, } return ret; } + +static int _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); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} #endif /* HAVE_CURVE25519 */ #ifdef WOLFSSL_HAVE_MLDSA @@ -854,6 +984,38 @@ int wh_Server_MlDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, } return ret; } + +static int _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); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} #endif /* WOLFSSL_HAVE_MLDSA */ #ifdef WOLFSSL_HAVE_MLKEM @@ -909,6 +1071,36 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, } return ret; } + +static int _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); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} #endif /* WOLFSSL_HAVE_MLKEM */ /* The sign path (and its slot callbacks) is unavailable in verify-only builds; @@ -1233,20 +1425,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); if (ret == 0) { /* Best-effort public key export: when the serialized @@ -1317,15 +1513,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); @@ -1341,12 +1528,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 = _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 = _EccKeyCacheExportEnforce(ctx, pub_key_id, + WH_NVM_FLAGS_NONE, pub_key); + } } if (ret == WH_ERROR_OK) { /* make shared secret */ @@ -1392,14 +1582,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; @@ -1459,15 +1648,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); @@ -1478,8 +1658,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 = _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); @@ -1491,10 +1673,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; @@ -1554,15 +1735,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); @@ -1574,8 +1746,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 = _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, @@ -1602,10 +1776,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; @@ -1759,6 +1932,16 @@ 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. */ +#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, @@ -1814,38 +1997,36 @@ 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); - 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); + /* Copy the IKM out, enforcing the derive usage policy against the + * same locked snapshot; see the KDF input bound 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; + 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 */ @@ -1861,20 +2042,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); @@ -1895,6 +2078,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 */ @@ -1952,25 +2138,32 @@ 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 salt out, enforcing the derive usage policy against the + * same locked snapshot. An oversize salt now fails NOSPACE here + * rather than BAD_FUNC_ARG in wc_KDA_KDF_twostep_cmac() */ + 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; @@ -1978,34 +2171,30 @@ 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 Z out, enforcing the derive usage policy against the same + * locked snapshot; see the KDF input bound 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, @@ -2018,15 +2207,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; @@ -2043,6 +2236,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 */ @@ -2101,22 +2299,23 @@ static int _HandleCurve25519KeyGen(whServerContext* ctx, uint16_t magic, (out - (uint8_t*)cryptoDataOut)); 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); if (ret == 0) { @@ -2192,15 +2391,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); @@ -2221,10 +2411,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 = _CacheExportCurve25519KeyEnforce( + ctx, prv_key_id, WH_NVM_FLAGS_USAGE_DERIVE, priv); + if (ret == WH_ERROR_OK) { + /* load the public key (no usage requirement) */ + ret = _CacheExportCurve25519KeyEnforce( + ctx, pub_key_id, WH_NVM_FLAGS_NONE, pub); + } } if (ret == 0) { ret = wc_curve25519_shared_secret_ex(priv, pub, res_out, @@ -2269,14 +2464,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; @@ -2342,17 +2536,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() */ if (ret == 0) { /* Best-effort public key export: when the serialized * public key fits in the response body, return it so the @@ -2441,21 +2638,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 = _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, @@ -2472,10 +2664,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) { @@ -2542,19 +2733,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 = _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, @@ -2563,9 +2749,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) { @@ -2622,14 +2807,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; @@ -2650,7 +2827,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 = _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, @@ -2673,9 +2853,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) { @@ -2730,14 +2909,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( @@ -2758,7 +2929,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 = _CacheExportEd25519KeyEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_VERIFY, key); if (ret == WH_ERROR_OK) { int verified = 0; ret = wc_ed25519_verify_msg_ex( @@ -2779,9 +2953,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) { @@ -2806,8 +2979,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; @@ -2848,13 +3021,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; @@ -2926,6 +3100,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; } @@ -2946,8 +3121,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; @@ -2999,14 +3174,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; @@ -3122,6 +3298,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 */ @@ -3136,8 +3313,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; @@ -3175,13 +3352,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; @@ -3235,6 +3413,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; } @@ -3254,8 +3433,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; @@ -3296,14 +3475,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; @@ -3402,6 +3582,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 */ @@ -3416,8 +3597,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)) { @@ -3459,13 +3640,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; @@ -3520,6 +3702,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; } @@ -3539,8 +3722,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; @@ -3588,14 +3771,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; @@ -3694,6 +3878,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 */ @@ -3706,8 +3891,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)) { @@ -3768,14 +3953,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; @@ -3844,6 +4031,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; } @@ -3864,8 +4052,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; @@ -3916,14 +4104,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; @@ -4039,6 +4228,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 */ @@ -4065,17 +4255,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) { @@ -4210,6 +4400,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; } @@ -4857,22 +5048,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); #ifdef WOLFSSL_MLDSA_PUBLIC_KEY @@ -4953,15 +5146,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)) { @@ -4989,8 +5173,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 = _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) { @@ -5006,10 +5192,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; @@ -5060,15 +5245,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; @@ -5094,8 +5270,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 = _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) { @@ -5111,10 +5289,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; @@ -5227,14 +5404,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() */ if (ret == WH_ERROR_OK) { /* Best-effort public key export: when the serialized * public key fits in the response body, return it so the @@ -5313,14 +5496,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; @@ -5329,7 +5504,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 = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); } /* Verify the exported key matches the requested level */ @@ -5374,7 +5552,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 */ @@ -5421,14 +5599,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; @@ -5444,7 +5614,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 = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); } /* Verify the exported key matches the requested level */ @@ -5484,7 +5657,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 */ @@ -6556,26 +6729,28 @@ 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 == 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); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ #ifdef WOLFSSL_MLDSA_PUBLIC_KEY /* Stream the public key back through the client's DMA * buffer so it gets the pubkey without a separate @@ -6608,7 +6783,7 @@ static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, ret = rc; } if (ret != 0) { - (void)wh_Server_KeystoreEvictKey(ctx, keyId); + _CryptoEvictKeyLocked(ctx, keyId); } } #endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ @@ -6700,7 +6875,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 = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, + key); if (ret == 0) { /* Process client message buffer address */ ret = wh_Server_DmaProcessClientAddress( @@ -6745,17 +6924,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; @@ -6834,8 +7012,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 = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, + key); if (ret == 0) { /* Process client signature buffer address */ ret = wh_Server_DmaProcessClientAddress( @@ -6877,12 +7058,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) { @@ -7034,14 +7215,21 @@ 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) { + ret = wh_Server_MlKemKeyCacheImport( + ctx, key, keyId, req.flags, req.labelSize, + req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ /* Stream the public key back through the client's DMA * buffer so it gets the pubkey without a separate * ExportPublicKey call. A freshly generated key must @@ -7075,7 +7263,7 @@ static int _HandleMlKemKeyGenDma(whServerContext* ctx, uint16_t magic, } } if (ret != WH_ERROR_OK) { - (void)wh_Server_KeystoreEvictKey(ctx, keyId); + _CryptoEvictKeyLocked(ctx, keyId); } } if (ret == WH_ERROR_OK) { @@ -7143,14 +7331,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; @@ -7159,7 +7339,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 = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); } /* Verify the exported key matches the requested level */ @@ -7227,7 +7410,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( @@ -7280,14 +7463,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; @@ -7296,7 +7471,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 = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); } /* Verify the exported key matches the requested level */ @@ -7355,7 +7533,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( @@ -7816,7 +7994,7 @@ static int _HandleLmsVerifyDma(whServerContext* ctx, uint16_t magic, int devId, if (ret == WH_ERROR_OK) { ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); (void)WH_SERVER_NVM_UNLOCK(ctx); - } + } /* WH_SERVER_NVM_LOCK() */ } if (ret == WH_ERROR_OK) { /* Deserialize leaves the key in PARMSET; wc_LmsKey_Verify needs @@ -7866,7 +8044,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( @@ -7922,7 +8100,7 @@ static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, if (ret == WH_ERROR_OK) { ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); (void)WH_SERVER_NVM_UNLOCK(ctx); - } + } /* WH_SERVER_NVM_LOCK() */ } if (ret == WH_ERROR_OK) { res.sigsLeft = (uint32_t)wc_LmsKey_SigsLeft(key); @@ -8308,7 +8486,7 @@ static int _HandleXmssVerifyDma(whServerContext* ctx, uint16_t magic, if (ret == WH_ERROR_OK) { ret = wh_Server_XmssKeyCacheExport(ctx, keyId, key); (void)WH_SERVER_NVM_UNLOCK(ctx); - } + } /* WH_SERVER_NVM_LOCK() */ } if (ret == WH_ERROR_OK) { /* Deserialize leaves the key in PARMSET; wc_XmssKey_Verify needs @@ -8358,7 +8536,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( @@ -8717,6 +8895,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 b08143c67..5a5cdfdbf 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -1133,6 +1133,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 */ + wh_Utils_ForceZero(out, *outSz); + } + } + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} + int wh_Server_KeystoreEvictKey(whServerContext* server, whNvmId keyId) { int ret = 0; @@ -3603,26 +3640,4 @@ int wh_Server_KeystoreEnforceKeyUsage(const whNvmMetadata* meta, return WH_ERROR_USAGE; } -int wh_Server_KeystoreFindEnforceKeyUsage(whServerContext* server, - whKeyId keyId, - whNvmFlags requiredUsage) -{ - int ret; - whNvmMetadata* meta = NULL; - - /* Validate input parameters */ - if (server == NULL) { - 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; - } - - /* Enforce the usage policy with the obtained metadata */ - return wh_Server_KeystoreEnforceKeyUsage(meta, requiredUsage); -} - #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 9c35e6519..636962c2a 100644 --- a/test-refactor/client-server/wh_test_crypto_mldsa.c +++ b/test-refactor/client-server/wh_test_crypto_mldsa.c @@ -1114,9 +1114,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 e26ec57f5..87228e584 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 @@ -294,10 +306,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..9c88032bf --- /dev/null +++ b/test-refactor/posix/wh_test_keygen_unique_id.c @@ -0,0 +1,695 @@ +/* + * 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. + * + * Under TSAN (TSAN=1) unserialized cache accesses are additionally + * reported as data races. + */ + +#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: TSAN can't see the mem transport's notify-counter + * handshake, so annotate the send/recv pairs to keep the analysis on + * keystore/NVM locking. */ +#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 + * WOLFHSM_CFG_SERVER_KEYCACHE_COUNT but can over-subscribe the big-key + * cache; that's 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 + +#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..88fd57dcf --- /dev/null +++ b/test-refactor/posix/wh_test_keyread_race.c @@ -0,0 +1,635 @@ +/* + * 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. + * KR_NUM_CLIENTS client/server pairs share one locked NVM, seeded with more + * committed global AES keys than the shared cache has slots, so reads churn + * the cache. Barrier-aligned clients have their server AES-ECB encrypt a + * fixed block under a rotating key id and compare the result against a + * software-AES oracle; a mismatch means the handler read the wrong key + * material. Companion to wh_test_keygen_unique_id.c, which covers the write + * side. Under TSAN (TSAN=1) unserialized cache accesses are additionally + * reported as data races. + */ + +#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: TSAN can't see the mem transport's notify-counter + * handshake, so annotate the send/recv pairs to keep the analysis on + * keystore/NVM locking. */ +#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 WOLFHSM_CFG_SERVER_KEYCACHE_COUNT slots, so reads race to + * claim and reload slots, but within WOLFHSM_CFG_NVM_OBJECT_COUNT. */ +#define KR_NUM_KEYS 24 + +/* AES-128; each key is a distinct constant byte, so any wrong key byte + * changes the whole ciphertext block. */ +#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 + +#define KR_BUFFER_SIZE 4096 + +/* Distinct nonzero constant byte per key */ +#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 and its per-key software-AES ciphertext oracle */ + 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; + + /* Per-client outcome counters */ + 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 explicit client API is used instead of the + * cryptocb, so the Aes struct only carries 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; + + 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; + } + + 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 before aborting. */ + 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 d36f6fcd2..da9eb4c22 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 @@ -317,6 +319,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 02f7bd814..c9f44534e 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13532,9 +13532,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_keystore.h b/wolfhsm/wh_server_keystore.h index d34588e7c..864b3b1cd 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 * @@ -277,25 +311,4 @@ int wh_Server_KeystoreExportKeyDmaChecked(whServerContext* server, int wh_Server_KeystoreEnforceKeyUsage(const whNvmMetadata* meta, whNvmFlags requiredUsage); -/** - * Validates that a key has the required usage policy flags set - * - * 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. - * - * @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., - * WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT) - * - * @return WH_ERROR_OK if the key has all required usage flags set - * @return WH_ERROR_USAGE if the key does not have the required flags - * @return Other error codes if key metadata cannot be retrieved - */ -int wh_Server_KeystoreFindEnforceKeyUsage(whServerContext* server, - whKeyId keyId, - whNvmFlags requiredUsage); - #endif /* !WOLFHSM_WH_SERVER_KEYSTORE_H_ */ 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 \