diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index d11e64609..93310d504 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -81,7 +81,7 @@ * and the blocking wrappers wh_Client_EccMakeCacheKey/EccMakeExportKey. */ static int _EccMakeKeyRequest(whClientContext* ctx, int size, int curveId, whKeyId key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label); + uint16_t label_len, const uint8_t* label); static int _EccMakeKeyResponse(whClientContext* ctx, whKeyId* out_key_id, ecc_key* out_key); #endif /* HAVE_ECC */ @@ -98,7 +98,7 @@ static int _Curve25519MakeKey(whClientContext* ctx, uint16_t size, /* Shared async halves used by the RsaMakeCacheKey/RsaMakeExportKey wrappers. */ static int _RsaMakeKeyRequest(whClientContext* ctx, uint32_t size, uint32_t e, whKeyId key_id, whNvmFlags flags, - uint32_t label_len, uint8_t* label); + uint32_t label_len, const uint8_t* label); static int _RsaMakeKeyResponse(whClientContext* ctx, whKeyId* out_key_id, RsaKey* out_rsa); #endif @@ -127,23 +127,23 @@ static int _CmacKdfMakeKey(whClientContext* ctx, whKeyId saltKeyId, /* Make a ML-DSA key on the server based on the flags */ static int _MlDsaMakeKey(whClientContext* ctx, int size, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, wc_MlDsaKey* key); + uint16_t label_len, const uint8_t* label, wc_MlDsaKey* key); #ifdef WOLFHSM_CFG_DMA static int _MlDsaMakeKeyDma(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, wc_MlDsaKey* key); + uint16_t label_len, const uint8_t* label, wc_MlDsaKey* key); #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_HAVE_MLDSA */ #ifdef WOLFSSL_HAVE_MLKEM static int _MlKemMakeKey(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, MlKemKey* key); + uint16_t label_len, const uint8_t* label, MlKemKey* key); #ifdef WOLFHSM_CFG_DMA static int _MlKemMakeKeyDma(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, MlKemKey* key); + uint16_t label_len, const uint8_t* label, MlKemKey* key); #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_HAVE_MLKEM */ @@ -1931,7 +1931,7 @@ int wh_Client_EccExportPublicKey(whClientContext* ctx, whKeyId keyId, * blocking poll) to consume the reply. */ static int _EccMakeKeyRequest(whClientContext* ctx, int size, int curveId, whKeyId key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label) + uint16_t label_len, const uint8_t* label) { whMessageCrypto_EccKeyGenRequest* req = NULL; uint8_t* dataPtr = NULL; @@ -2021,9 +2021,10 @@ static int _EccMakeKeyResponse(whClientContext* ctx, whKeyId* out_key_id, *out_key_id = key_id; } - /* If a DER blob is present (ephemeral/export keygen), deserialize it - * into the supplied key context. When called from the cache wrapper, - * out_key is NULL and the server returns an empty body. */ + /* A DER blob is present for both ephemeral/export keygen and the + * cache-and-export path. When key material was requested (out_key != + * NULL) but the server returned an empty body, treat it as an error so + * a caller of ...AndExportPublic never receives an unpopulated key. */ if (out_key != NULL) { if (res->len > 0) { uint8_t* key_der = (uint8_t*)(res + 1); @@ -2036,8 +2037,7 @@ static int _EccMakeKeyResponse(whClientContext* ctx, whKeyId* out_key_id, der_size); } else { - /* Cached key — stamp the assigned keyId */ - wh_Client_EccSetKeyId(out_key, key_id); + ret = WH_ERROR_ABORTED; } } } @@ -2105,6 +2105,53 @@ int wh_Client_EccMakeCacheKey(whClientContext* ctx, int size, int curveId, return ret; } +int wh_Client_EccMakeCacheKeyAndExportPublic(whClientContext* ctx, int size, + int curveId, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + ecc_key* pub) +{ + int ret; + whKeyId in_keyId; + whKeyId key_id = WH_KEYID_ERASED; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export pair, not the cache pair. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _EccMakeKeyRequest(ctx, size, curveId, in_keyId, flags, + label_len, label); + if (ret == WH_ERROR_OK) { + do { + ret = _EccMakeKeyResponse(ctx, &key_id, pub); + } while (ret == WH_ERROR_NOTREADY); + if (ret >= 0) { + *inout_key_id = key_id; + /* Associate the returned key with the cached keyId and stamp the + * client's HSM devId so pub is immediately usable both as the + * exported public key and as a handle to the cached private key, + * without the caller re-initializing it. */ + wh_Client_EccSetKeyId(pub, key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(key_id)) { + /* The server auto-assigned and committed a key but the export + * failed. Roll back so the operation is atomic and no cache slot is + * orphaned. */ + (void)wh_Client_KeyEvict(ctx, key_id); + } + } + return ret; +} + int wh_Client_EccMakeExportKey(whClientContext* ctx, int size, int curveId, ecc_key* key) { @@ -3021,13 +3068,26 @@ static int _Curve25519MakeKey(whClientContext* ctx, uint16_t size, /* Update the context if provided */ if (key != NULL) { - uint16_t der_size = (uint16_t)(res->len); - uint8_t* key_der = (uint8_t*)(res + 1); - /* Set the key_id. Should be ERASED if EPHEMERAL */ + uint16_t der_size = (uint16_t)(res->len); + uint8_t* key_der = (uint8_t*)(res + 1); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Set the key_id. ERASED for EPHEMERAL, cached id otherwise. */ wh_Client_Curve25519SetKeyId(key, key_id); - if (flags & WH_NVM_FLAGS_EPHEMERAL) { - /* Response has the exported key */ + /* Response carries the exported key (EPHEMERAL) or the public + * key (cached keygen). An empty body means the caller requested + * key material the server did not return; also reject a length + * that does not fit the received frame before deserializing. */ + if (der_size == 0) { + ret = WH_ERROR_ABORTED; + } + else if ((data_len < hdr_sz) || + (res->len > (data_len - hdr_sz))) { + ret = WH_ERROR_ABORTED; + } + else { ret = wh_Crypto_Curve25519DeserializeKey(key_der, der_size, key); WH_DEBUG_VERBOSE_HEXDUMP("[client] KeyGen export:", key_der, @@ -3051,6 +3111,44 @@ int wh_Client_Curve25519MakeCacheKey(whClientContext* ctx, uint16_t size, NULL); } +int wh_Client_Curve25519MakeCacheKeyAndExportPublic( + whClientContext* ctx, uint16_t size, whKeyId* inout_key_id, + whNvmFlags flags, const uint8_t* label, uint16_t label_len, + curve25519_key* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _Curve25519MakeKey(ctx, size, inout_key_id, flags, label, label_len, + pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here (not only inside _Curve25519MakeKey) because a public-key + * deserialize that retries parameter sets can re-init pub and clear + * it. */ + wh_Client_Curve25519SetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + int wh_Client_Curve25519MakeExportKey(whClientContext* ctx, uint16_t size, curve25519_key* key) { @@ -3404,8 +3502,8 @@ int wh_Client_Ed25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, } static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, - whNvmFlags flags, uint16_t label_len, uint8_t* label, - ed25519_key* key) + whNvmFlags flags, uint16_t label_len, + const uint8_t* label, ed25519_key* key) { int ret = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; @@ -3476,7 +3574,10 @@ static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, } if (key != NULL) { wh_Client_Ed25519SetKeyId(key, key_id); - if (flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Response carries the exported key (EPHEMERAL) or the public key + * (cached keygen). An empty body means the caller requested key + * material the server did not return. */ + if (res->outSz > 0) { uint8_t* out = (uint8_t*)(res + 1); uint16_t outSz = (uint16_t)res->outSz; if (outSz + sizeof(whMessageCrypto_GenericResponseHeader) + @@ -3486,6 +3587,9 @@ static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, } ret = wh_Crypto_Ed25519DeserializeKeyDer(out, outSz, key); } + else { + ret = WH_ERROR_ABORTED; + } } } @@ -3512,6 +3616,45 @@ int wh_Client_Ed25519MakeCacheKey(whClientContext* ctx, whKeyId* inout_key_id, return _Ed25519MakeKey(ctx, inout_key_id, flags, label_len, label, NULL); } +int wh_Client_Ed25519MakeCacheKeyAndExportPublic(whClientContext* ctx, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + ed25519_key* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _Ed25519MakeKey(ctx, inout_key_id, flags, label_len, label, pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here (not only inside _Ed25519MakeKey) because a public-key + * deserialize that retries parameter sets can re-init pub and clear + * it. */ + wh_Client_Ed25519SetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + int wh_Client_Ed25519Sign(whClientContext* ctx, ed25519_key* key, const uint8_t* msg, uint32_t msgLen, uint8_t type, const uint8_t* context, uint32_t contextLen, @@ -4191,7 +4334,7 @@ int wh_Client_RsaExportPublicKey(whClientContext* ctx, whKeyId keyId, static int _RsaMakeKeyRequest(whClientContext* ctx, uint32_t size, uint32_t e, whKeyId key_id, whNvmFlags flags, - uint32_t label_len, uint8_t* label) + uint32_t label_len, const uint8_t* label) { whMessageCrypto_RsaKeyGenRequest* req = NULL; uint8_t* dataPtr = NULL; @@ -4352,6 +4495,53 @@ int wh_Client_RsaMakeCacheKey(whClientContext* ctx, uint32_t size, uint32_t e, return ret; } +int wh_Client_RsaMakeCacheKeyAndExportPublic(whClientContext* ctx, + uint32_t size, uint32_t e, + whKeyId* inout_key_id, + whNvmFlags flags, + uint32_t label_len, + const uint8_t* label, + RsaKey* pub) +{ + int ret; + whKeyId in_keyId; + whKeyId key_id = WH_KEYID_ERASED; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export pair, not the cache pair. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _RsaMakeKeyRequest(ctx, size, e, in_keyId, flags, label_len, + label); + if (ret == WH_ERROR_OK) { + do { + ret = _RsaMakeKeyResponse(ctx, &key_id, pub); + } while (ret == WH_ERROR_NOTREADY); + if (ret >= 0) { + *inout_key_id = key_id; + /* Associate the returned key with the cached keyId and stamp the + * client's HSM devId so pub is immediately usable both as the + * exported public key and as a handle to the cached private key, + * without the caller re-initializing it. */ + wh_Client_RsaSetKeyId(pub, key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(key_id)) { + /* The server auto-assigned and committed a key but the export + * failed. Roll back so the operation is atomic and no cache slot is + * orphaned. */ + (void)wh_Client_KeyEvict(ctx, key_id); + } + } + return ret; +} + int wh_Client_RsaMakeExportKey(whClientContext* ctx, uint32_t size, uint32_t e, RsaKey* rsa) { @@ -9401,7 +9591,7 @@ int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, static int _MlDsaMakeKey(whClientContext* ctx, int size, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, wc_MlDsaKey* key) + uint16_t label_len, const uint8_t* label, wc_MlDsaKey* key) { int ret = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; @@ -9484,13 +9674,28 @@ static int _MlDsaMakeKey(whClientContext* ctx, int size, int level, /* Update the context if provided */ if (key != NULL) { - uint16_t der_size = (uint16_t)(res->len); - /* Set the key_id. Should be ERASED if EPHEMERAL */ + uint16_t der_size = (uint16_t)(res->len); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Set the key_id. ERASED for EPHEMERAL, cached id + * otherwise. */ wh_Client_MlDsaSetKeyId(key, key_id); - if (flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Response carries the exported key (EPHEMERAL) or + * the public key (cached keygen). An empty body + * means the caller requested key material the server + * did not return; also reject a length that does not + * fit the received frame before deserializing. */ + if (der_size == 0) { + ret = WH_ERROR_ABORTED; + } + else if ((res_len < hdr_sz) || + (res->len > (res_len - hdr_sz))) { + ret = WH_ERROR_ABORTED; + } + else { uint8_t* key_der = (uint8_t*)(res + 1); - /* Response has the exported key */ ret = wh_Crypto_MlDsaDeserializeKeyDer( key_der, der_size, key); WH_DEBUG_VERBOSE_HEXDUMP( @@ -9521,6 +9726,47 @@ int wh_Client_MlDsaMakeCacheKey(whClientContext* ctx, int size, int level, label, NULL); } +int wh_Client_MlDsaMakeCacheKeyAndExportPublic(whClientContext* ctx, int size, + int level, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + wc_MlDsaKey* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _MlDsaMakeKey(ctx, size, level, inout_key_id, flags, label_len, label, + pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here (not only inside _MlDsaMakeKey) because a public-key + * deserialize that retries parameter sets can re-init pub and clear + * it. */ + wh_Client_MlDsaSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + int wh_Client_MlDsaMakeExportKey(whClientContext* ctx, int level, int size, wc_MlDsaKey* key) { @@ -9907,7 +10153,7 @@ int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, static int _MlDsaMakeKeyDma(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, wc_MlDsaKey* key) + uint16_t label_len, const uint8_t* label, wc_MlDsaKey* key) { int ret = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; @@ -10012,20 +10258,25 @@ static int _MlDsaMakeKeyDma(whClientContext* ctx, int level, /* Update the context if provided */ if (key != NULL) { - /* Set the key_id. Should be ERASED if EPHEMERAL */ + /* Set the key_id. ERASED for EPHEMERAL, cached id + * otherwise. */ wh_Client_MlDsaSetKeyId(key, key_id); - if (flags & WH_NVM_FLAGS_EPHEMERAL) { - /* Bound the server-reported key size to the DMA - * buffer capacity before deserializing */ - if (res->keySize > sizeof(buffer)) { - ret = WH_ERROR_ABORTED; - } - else { - /* Response has the exported key */ - ret = wh_Crypto_MlDsaDeserializeKeyDer( - buffer, res->keySize, key); - } + /* buffer holds the exported key (EPHEMERAL) or the public + * key (cached keygen); keySize bounds the DMA write. An + * empty result means the caller requested key material the + * server did not return. */ + if (res->keySize == 0) { + ret = WH_ERROR_ABORTED; + } + /* Bound the server-reported key size to the DMA buffer + * capacity before deserializing */ + else if (res->keySize > sizeof(buffer)) { + ret = WH_ERROR_ABORTED; + } + else { + ret = wh_Crypto_MlDsaDeserializeKeyDer( + buffer, res->keySize, key); } } } @@ -10049,6 +10300,44 @@ int wh_Client_MlDsaMakeExportKeyDma(whClientContext* ctx, int level, key); } +int wh_Client_MlDsaMakeCacheKeyDma(whClientContext* ctx, int level, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + wc_MlDsaKey* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _MlDsaMakeKeyDma(ctx, level, inout_key_id, flags, label_len, label, + pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here (not only inside _MlDsaMakeKeyDma) because a public-key + * deserialize that retries parameter sets can re-init pub and clear + * it. */ + wh_Client_MlDsaSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + int wh_Client_MlDsaSignDma(whClientContext* ctx, const byte* in, word32 in_len, byte* out, word32* out_len, wc_MlDsaKey* key, @@ -10470,7 +10759,7 @@ int wh_Client_MlKemExportPublicKey(whClientContext* ctx, whKeyId keyId, static int _MlKemMakeKey(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, MlKemKey* key) + uint16_t label_len, const uint8_t* label, MlKemKey* key) { int ret = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; @@ -10547,7 +10836,10 @@ static int _MlKemMakeKey(whClientContext* ctx, int level, } if (key != NULL) { wh_Client_MlKemSetKeyId(key, key_id); - if ((flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + /* Response carries the exported key (EPHEMERAL) or the public key + * (cached keygen). An empty body means the caller requested key + * material the server did not return. */ + if (res->len > 0) { uint8_t* key_raw = (uint8_t*)(res + 1); const size_t hdr_sz = sizeof(whMessageCrypto_GenericResponseHeader) + @@ -10560,6 +10852,9 @@ static int _MlKemMakeKey(whClientContext* ctx, int level, key_raw, (uint16_t)res->len, key); } } + else { + ret = WH_ERROR_ABORTED; + } } } return ret; @@ -10577,6 +10872,45 @@ int wh_Client_MlKemMakeCacheKey(whClientContext* ctx, int level, label, NULL); } +int wh_Client_MlKemMakeCacheKeyAndExportPublic(whClientContext* ctx, int level, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + MlKemKey* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _MlKemMakeKey(ctx, level, inout_key_id, flags, label_len, label, pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here (not only inside _MlKemMakeKey) because a public-key + * deserialize that retries parameter sets can re-init pub and clear + * it. */ + wh_Client_MlKemSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + int wh_Client_MlKemMakeExportKey(whClientContext* ctx, int level, MlKemKey* key) { @@ -10921,7 +11255,7 @@ int wh_Client_MlKemExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, static int _MlKemMakeKeyDma(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, MlKemKey* key) + uint16_t label_len, const uint8_t* label, MlKemKey* key) { int ret = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; @@ -11006,16 +11340,22 @@ static int _MlKemMakeKeyDma(whClientContext* ctx, int level, } if (key != NULL) { wh_Client_MlKemSetKeyId(key, key_id); - if ((flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { - if (res->keySize > buffer_len) { - ret = WH_ERROR_BADARGS; - } - else { - ret = wh_Crypto_MlKemDeserializeKey( - buffer, (uint16_t)res->keySize, key); - } + /* buffer holds the exported key (EPHEMERAL) or the public key + * (cached keygen); keySize bounds the DMA write. An empty result + * means the caller requested key material the server did not + * return. */ + if (res->keySize == 0) { + ret = WH_ERROR_ABORTED; + } + else if (res->keySize > buffer_len) { + ret = WH_ERROR_BADARGS; + } + else { + ret = wh_Crypto_MlKemDeserializeKey( + buffer, (uint16_t)res->keySize, key); } } + } } @@ -11034,6 +11374,44 @@ int wh_Client_MlKemMakeExportKeyDma(whClientContext* ctx, int level, key); } +int wh_Client_MlKemMakeCacheKeyDma(whClientContext* ctx, int level, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + MlKemKey* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _MlKemMakeKeyDma(ctx, level, inout_key_id, flags, label_len, label, + pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here (not only inside _MlKemMakeKeyDma) because a public-key + * deserialize that retries parameter sets can re-init pub and clear + * it. */ + wh_Client_MlKemSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + int wh_Client_MlKemEncapsulateDma(whClientContext* ctx, MlKemKey* key, uint8_t* ct, uint32_t* inout_ct_len, uint8_t* ss, uint32_t* inout_ss_len) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index b35e8e4a0..840290601 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -375,9 +375,25 @@ static int _HandleRsaKeyGen(whServerContext* ctx, uint16_t magic, int devId, label_size, label); } WH_DEBUG_SERVER_VERBOSE("RsaKeyGen CacheKeyRsa: keyId:%u, ret:%d\n", key_id, ret); + if (ret == 0) { + /* Export the public key into the response body so the + * client gets it without a separate ExportPublicKey call. + * A freshly generated key must serialize, so treat a + * failure as fatal: evict the just-committed key and + * propagate the error rather than returning a keyId with no + * public key. */ + int pub_ret = wc_RsaKeyToPublicDer(rsa, out, max_size); + if (pub_ret > 0) { + der_size = (uint16_t)pub_ret; + } + else { + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; + } + } if (ret == 0) { res.keyId = wh_KeyId_TranslateToClient(key_id); - res.len = 0; + res.len = der_size; } } } @@ -1201,13 +1217,6 @@ static int _HandleEccKeyGen(whServerContext* ctx, uint16_t magic, int devId, key_id = WH_KEYID_ERASED; ret = wh_Crypto_EccSerializeKeyDer(key, max_size, res_out, &res_size); - /* TODO: RSA has the following, should we do the same? */ - /* - if (ret == 0) { - res.keyId = 0; - res.len = res_size; - } - */ } else { /* Must import the key into the cache and return keyid @@ -1228,11 +1237,23 @@ static int _HandleEccKeyGen(whServerContext* ctx, uint16_t magic, int devId, label_size, label); } WH_DEBUG_SERVER("CacheImport: keyId:%u, ret:%d\n", key_id, ret); - /* TODO: RSA has the following, should we do the same? */ - /* - res.keyId = WH_KEYID_ID(key_id); - res.len = 0; - */ + if (ret == 0) { + /* Export the public key into the response body so the + * client gets it without a separate ExportPublicKey call. + * A freshly generated key must serialize, so treat a + * failure as fatal: evict the just-committed key and + * propagate the error rather than returning a keyId with no + * public key. */ + int pub_ret = + wc_EccPublicKeyToDer(key, res_out, max_size, 1); + if (pub_ret > 0) { + res_size = (uint16_t)pub_ret; + } + else { + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; + } + } } } wc_ecc_free(key); @@ -2061,6 +2082,9 @@ static int _HandleCurve25519KeyGen(whServerContext* ctx, uint16_t magic, ret = wh_Crypto_Curve25519SerializeKey(key, out, &ser_size); } else { + uint16_t max_size = + (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - + (out - (uint8_t*)cryptoDataOut)); ser_size = 0; /* Must import the key into the cache and return keyid */ if (WH_KEYID_ISERASED(key_id)) { @@ -2081,6 +2105,23 @@ static int _HandleCurve25519KeyGen(whServerContext* ctx, uint16_t magic, } WH_DEBUG_SERVER_VERBOSE("CacheImport: keyId:%u, ret:%d\n", key_id, ret); + if (ret == 0) { + /* Export the public key into the response body so the + * client gets it without a separate ExportPublicKey call. + * A freshly generated key must serialize, so treat a + * failure as fatal: evict the just-committed key and + * propagate the error rather than returning a keyId with no + * public key. */ + int pub_ret = + wc_Curve25519PublicKeyToDer(key, out, max_size, 1); + if (pub_ret > 0) { + ser_size = (uint16_t)pub_ret; + } + else { + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; + } + } } } wc_curve25519_free(key); @@ -2297,6 +2338,23 @@ static int _HandleEd25519KeyGen(whServerContext* ctx, uint16_t magic, int devId, ret = wh_Server_CacheImportEd25519Key( ctx, key, key_id, flags, label_size, label); } + if (ret == 0) { + /* Export the public key into the response body so the + * client gets it without a separate ExportPublicKey call. + * A freshly generated key must serialize, so treat a + * failure as fatal: evict the just-committed key and + * propagate the error rather than returning a keyId with no + * public key. */ + int pub_ret = + wc_Ed25519PublicKeyToDer(key, res_out, max_size, 1); + if (pub_ret > 0) { + ser_size = (uint16_t)pub_ret; + } + else { + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; + } + } } } wc_ed25519_free(key); @@ -4801,6 +4859,25 @@ static int _HandleMlDsaKeyGen(whServerContext* ctx, uint16_t magic, int devId, } WH_DEBUG_SERVER("CacheImport: keyId:%u, ret:%d\n", key_id, ret); +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + if (ret == 0) { + /* Export the public key into the response body so + * the client gets it without a separate + * ExportPublicKey call. A freshly generated key must + * serialize, so treat a failure as fatal: evict the + * just-committed key and propagate the error rather + * than returning a keyId with no public key. */ + int pub_ret = wc_MlDsaKey_PublicKeyToDer( + key, res_out, max_size, 1); + if (pub_ret > 0) { + res_size = (uint16_t)pub_ret; + } + else { + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; + } + } +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ } } } @@ -5140,6 +5217,25 @@ static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, req.flags, label_size, req.label); } + if (ret == WH_ERROR_OK) { + /* Export the public key into the response body so the + * client gets it without a separate ExportPublicKey call. + * A freshly generated key must serialize, so treat a + * failure as fatal: evict the just-committed key and + * propagate the error rather than returning a keyId with no + * public key. */ + word32 pubSize = 0; + if ((wc_MlKemKey_PublicKeySize(key, &pubSize) == 0) && + ((uint32_t)pubSize <= (uint32_t)max_size) && + (wc_MlKemKey_EncodePublicKey(key, res_out, pubSize) == + 0)) { + res_size = (uint16_t)pubSize; + } + else { + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + ret = WH_ERROR_ABORTED; + } + } } } wc_MlKemKey_Free(key); @@ -6384,6 +6480,8 @@ static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, whMessageCrypto_MlDsaKeyGenDmaRequest req; whMessageCrypto_MlDsaKeyGenDmaResponse res; + memset(&res, 0, sizeof(res)); + if (inSize < sizeof(whMessageCrypto_MlDsaKeyGenDmaRequest)) { return WH_ERROR_BADARGS; } @@ -6458,11 +6556,47 @@ static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, req.label); WH_DEBUG_SERVER("CacheImport: keyId:%u, ret:%d\n", keyId, ret); - if (ret == 0) { - res.keyId = wh_KeyId_TranslateToClient(keyId); - res.keySize = keySize; + } +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + /* 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 + * serialize, so treat a failure as fatal: evict the + * just-committed key and propagate the error rather + * than returning a keyId with no public key. */ + if (ret == 0) { + int rc = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, + (whServerDmaFlags){0}); + if (rc == 0) { + int pub_ret = wc_MlDsaKey_PublicKeyToDer( + key, (byte*)clientOutAddr, + (word32)req.key.sz, 1); + if (pub_ret > 0) { + keySize = (uint16_t)pub_ret; + } + else { + ret = (pub_ret < 0) ? pub_ret + : WH_ERROR_ABORTED; + } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + else { + ret = rc; + } + if (ret != 0) { + (void)wh_Server_KeystoreEvictKey(ctx, keyId); } } +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ + if (ret == 0) { + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.keySize = keySize; + } } } } @@ -6888,10 +7022,46 @@ static int _HandleMlKemKeyGenDma(whServerContext* ctx, uint16_t magic, ret = wh_Server_MlKemKeyCacheImport( ctx, key, keyId, req.flags, req.labelSize, req.label); - if (ret == WH_ERROR_OK) { - res.keyId = wh_KeyId_TranslateToClient(keyId); - res.keySize = keySize; + } + /* 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 + * serialize, so treat a failure as fatal: evict the + * just-committed key and propagate the error rather than + * returning a keyId with no public key. */ + if (ret == WH_ERROR_OK) { + word32 pubSize = 0; + if ((wc_MlKemKey_PublicKeySize(key, &pubSize) != 0) || + ((uint64_t)pubSize > req.key.sz)) { + ret = WH_ERROR_ABORTED; } + else { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, pubSize, + WH_DMA_OPER_CLIENT_WRITE_PRE, + (whServerDmaFlags){0}); + if (ret == WH_ERROR_OK) { + if (wc_MlKemKey_EncodePublicKey( + key, (uint8_t*)clientOutAddr, pubSize) == + 0) { + keySize = (uint16_t)pubSize; + } + else { + ret = WH_ERROR_ABORTED; + } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + } + if (ret != WH_ERROR_OK) { + (void)wh_Server_KeystoreEvictKey(ctx, keyId); + } + } + if (ret == WH_ERROR_OK) { + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.keySize = keySize; } } } diff --git a/test-refactor/client-server/wh_test_crypto_curve25519.c b/test-refactor/client-server/wh_test_crypto_curve25519.c index a72a8a4c0..0bd8ea6eb 100644 --- a/test-refactor/client-server/wh_test_crypto_curve25519.c +++ b/test-refactor/client-server/wh_test_crypto_curve25519.c @@ -355,10 +355,123 @@ static int _whTest_CryptoCurve25519ExportPublicKey(whClientContext* ctx) return ret; } +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_Curve25519ExportPublicKey and + * that an X25519 shared secret round-trips against the cached private key. */ +static int _whTest_CryptoCurve25519CacheKeyAndExportPublic(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret = 0; + WC_RNG rng[1]; + curve25519_key genPub[1] = {0}; + curve25519_key refPub[1] = {0}; + curve25519_key localKey[1] = {0}; + uint8_t genRaw[CURVE25519_KEYSIZE] = {0}; + uint8_t refRaw[CURVE25519_KEYSIZE] = {0}; + word32 genRawLen = sizeof(genRaw); + word32 refRawLen = sizeof(refRaw); + uint8_t sharedHsm[CURVE25519_KEYSIZE] = {0}; + uint8_t sharedLocal[CURVE25519_KEYSIZE] = {0}; + word32 secLen = 0; + whKeyId keyId = WH_KEYID_ERASED; + + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = wc_curve25519_init_ex(genPub, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_curve25519_init_ex %d\n", ret); + (void)wc_FreeRng(rng); + return ret; + } + + ret = wh_Client_Curve25519MakeCacheKeyAndExportPublic( + ctx, (uint16_t)CURVE25519_KEYSIZE, &keyId, WH_NVM_FLAGS_USAGE_DERIVE, + NULL, 0, genPub); + if (ret != 0) { + WH_ERROR_PRINT("Curve25519MakeCacheKeyAndExportPublic failed %d\n", ret); + } + + /* Cross-check against a separate public export of the same keyId. */ + if (ret == 0) { + ret = wc_curve25519_init_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Curve25519ExportPublicKey(ctx, keyId, refPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_Curve25519ExportPublicKey failed %d\n", ret); + } + } + } + if (ret == 0) { + ret = wc_curve25519_export_public(genPub, genRaw, &genRawLen); + if (ret == 0) { + ret = wc_curve25519_export_public(refPub, refRaw, &refRawLen); + } + if (ret != 0) { + WH_ERROR_PRINT("Curve25519 export_public failed %d\n", ret); + } + else if ((genRawLen != refRawLen) || + (memcmp(genRaw, refRaw, genRawLen) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs export\n"); + ret = -1; + } + } + + /* Shared-secret round-trip using genPub directly as the HSM private-key + * handle (no separate key object): our local private key * genPub's + * exported public key (computed locally) must equal genPub's HSM private + * key * our local public key (computed on the server). */ + if (ret == 0) { + ret = wc_curve25519_init_ex(localKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, localKey); + } + } + if (ret == 0) { + secLen = sizeof(sharedLocal); + ret = wc_curve25519_shared_secret(localKey, genPub, sharedLocal, + &secLen); + if (ret != 0) { + WH_ERROR_PRINT("Local Curve25519 shared secret failed %d\n", ret); + } + } + if (ret == 0) { + secLen = sizeof(sharedHsm); + ret = wc_curve25519_shared_secret(genPub, localKey, sharedHsm, &secLen); + if (ret != 0) { + WH_ERROR_PRINT("HSM Curve25519 shared secret failed %d\n", ret); + } + } + if (ret == 0 && memcmp(sharedHsm, sharedLocal, secLen) != 0) { + WH_ERROR_PRINT("Curve25519 keygen-pub shared secret mismatch\n"); + ret = -1; + } + + wc_curve25519_free(localKey); + wc_curve25519_free(refPub); + wc_curve25519_free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + (void)wc_FreeRng(rng); + + if (ret == 0) { + WH_TEST_PRINT("CURVE25519 CACHE-AND-EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", + devId); + } + return ret; +} + int whTest_Crypto_Curve25519(whClientContext* ctx) { WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519ExportPublicKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoCurve25519CacheKeyAndExportPublic(ctx)); return 0; } #endif /* HAVE_CURVE25519 */ diff --git a/test-refactor/client-server/wh_test_crypto_ecc.c b/test-refactor/client-server/wh_test_crypto_ecc.c index 28962a60b..7be8fa082 100644 --- a/test-refactor/client-server/wh_test_crypto_ecc.c +++ b/test-refactor/client-server/wh_test_crypto_ecc.c @@ -38,6 +38,7 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/ecc.h" +#include "wolfssl/wolfcrypt/asn.h" #include "wolfssl/wolfcrypt/random.h" #include "wolfhsm/wh_error.h" @@ -547,6 +548,106 @@ static int _whTest_CryptoEccExportPublicKey(whClientContext* ctx) return ret; } +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_EccExportPublicKey and that + * it verifies a signature made by the cached private key. */ +static int _whTest_CryptoEccCacheKeyAndExportPublic(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret = 0; + WC_RNG rng[1]; + ecc_key genPub[1]; + ecc_key refPub[1] = {0}; + whKeyId keyId = WH_KEYID_ERASED; + uint8_t hash[TEST_ECC_KEYSIZE] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + uint8_t sig[ECC_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verify = 0; + byte genDer[256]; + byte refDer[256]; + int genDerSz = 0; + int refDerSz = 0; + + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = wc_ecc_init_ex(genPub, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_ecc_init_ex %d\n", ret); + (void)wc_FreeRng(rng); + return ret; + } + + ret = wh_Client_EccMakeCacheKeyAndExportPublic( + ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL, genPub); + if (ret != 0) { + WH_ERROR_PRINT("EccMakeCacheKeyAndExportPublic failed %d\n", ret); + } + + /* Cross-check against a separate public export of the same keyId. */ + if (ret == 0) { + ret = wc_ecc_init_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_EccExportPublicKey(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_EccExportPublicKey failed %d\n", ret); + } + else { + genDerSz = + wc_EccPublicKeyToDer(genPub, genDer, sizeof(genDer), 1); + refDerSz = + wc_EccPublicKeyToDer(refPub, refDer, sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs export\n"); + ret = -1; + } + } + } + } + + /* Sign on the server using genPub directly as the HSM private-key handle + * (no separate key object), then verify locally with the independently + * exported public key (refPub) the client holds. */ + if (ret == 0) { + ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng, genPub); + if (ret != 0) { + WH_ERROR_PRINT("HSM ECC sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &verify, + refPub); + if ((ret != 0) || (verify != 1)) { + WH_ERROR_PRINT("verify with keygen pub failed ret=%d verify=%d\n", + ret, verify); + if (ret == 0) { + ret = -1; + } + } + } + + wc_ecc_free(refPub); + wc_ecc_free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + (void)wc_FreeRng(rng); + + if (ret == 0) { + WH_TEST_PRINT("ECC CACHE-AND-EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId); + } + return ret; +} + #if !defined(WOLF_CRYPTO_CB_ONLY_ECC) /* Curve sizes used by the cross-verify and async test families. */ @@ -1742,6 +1843,7 @@ int whTest_Crypto_Ecc(whClientContext* ctx) WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEcc(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCacheDuplicate(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccExportPublicKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCacheKeyAndExportPublic(ctx)); #if !defined(WOLF_CRYPTO_CB_ONLY_ECC) WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCrossVerify(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccAsync(ctx)); diff --git a/test-refactor/client-server/wh_test_crypto_ed25519.c b/test-refactor/client-server/wh_test_crypto_ed25519.c index 63ab0937a..a3eb56921 100644 --- a/test-refactor/client-server/wh_test_crypto_ed25519.c +++ b/test-refactor/client-server/wh_test_crypto_ed25519.c @@ -37,6 +37,7 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/ed25519.h" +#include "wolfssl/wolfcrypt/asn.h" #include "wolfssl/wolfcrypt/random.h" #include "wolfhsm/wh_error.h" @@ -561,6 +562,96 @@ static int _whTest_CryptoEd25519ExportPublicKey(whClientContext* ctx) return ret; } +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_Ed25519ExportPublicKey and + * that it verifies a signature made by the cached private key. */ +static int _whTest_CryptoEd25519CacheKeyAndExportPublic(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret = 0; + ed25519_key genPub[1] = {0}; + ed25519_key refPub[1] = {0}; + whKeyId keyId = WH_KEYID_ERASED; + byte msg[] = "Ed25519 cache-export-public message"; + byte sig[ED25519_SIG_SIZE]; + uint32_t sigSz = sizeof(sig); + int verified = 0; + byte genDer[128]; + byte refDer[128]; + int genDerSz = 0; + int refDerSz = 0; + + ret = wc_ed25519_init_ex(genPub, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_ed25519_init_ex %d\n", ret); + return ret; + } + + ret = wh_Client_Ed25519MakeCacheKeyAndExportPublic( + ctx, &keyId, WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, + NULL, genPub); + if (ret != 0) { + WH_ERROR_PRINT("Ed25519MakeCacheKeyAndExportPublic failed %d\n", ret); + } + + /* Cross-check against a separate public export of the same keyId. */ + if (ret == 0) { + ret = wc_ed25519_init_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Ed25519ExportPublicKey(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_Ed25519ExportPublicKey failed %d\n", + ret); + } + else { + genDerSz = + wc_Ed25519PublicKeyToDer(genPub, genDer, sizeof(genDer), 1); + refDerSz = + wc_Ed25519PublicKeyToDer(refPub, refDer, sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs export\n"); + ret = -1; + } + } + } + } + + /* Sign on the server using genPub directly as the HSM private-key handle + * (no separate key object), then verify locally with the independently + * exported public key (refPub) the client holds. */ + if (ret == 0) { + ret = wh_Client_Ed25519Sign(ctx, genPub, msg, (uint32_t)sizeof(msg), + (uint8_t)Ed25519, NULL, 0, sig, &sigSz); + if (ret != 0) { + WH_ERROR_PRINT("HSM Ed25519 sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wc_ed25519_verify_msg(sig, sigSz, msg, (word32)sizeof(msg), + &verified, refPub); + if ((ret != 0) || (verified != 1)) { + WH_ERROR_PRINT("verify with keygen pub failed ret=%d verify=%d\n", + ret, verified); + if (ret == 0) { + ret = -1; + } + } + } + + wc_ed25519_free(refPub); + wc_ed25519_free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("Ed25519 CACHE-AND-EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", + devId); + } + return ret; +} + /* Exercises wh_Client_Ed25519Sign with an undersized output buffer: must * return WH_ERROR_BUFFER_SIZE and report the required signature length. */ @@ -644,6 +735,7 @@ int whTest_Crypto_Ed25519(whClientContext* ctx) WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519Dma(ctx)); #endif WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519ExportPublicKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519CacheKeyAndExportPublic(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEd25519BufferTooSmall(ctx)); return 0; } diff --git a/test-refactor/client-server/wh_test_crypto_mldsa.c b/test-refactor/client-server/wh_test_crypto_mldsa.c index 4b69738bd..9c35e6519 100644 --- a/test-refactor/client-server/wh_test_crypto_mldsa.c +++ b/test-refactor/client-server/wh_test_crypto_mldsa.c @@ -237,6 +237,104 @@ static int _whTest_CryptoMlDsaExportPublicKey(whClientContext* ctx) return ret; } +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_MlDsaExportPublicKey and that + * it verifies a signature made by the cached private key. */ +static int _whTest_CryptoMlDsaCacheKeyAndExportPublic(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret = 0; + MlDsaKey genPub[1] = {0}; + MlDsaKey refPub[1] = {0}; + whKeyId keyId = WH_KEYID_ERASED; + byte msg[] = "ML-DSA cache-export-public message"; + byte sig[DILITHIUM_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verified = 0; + byte genDer[DILITHIUM_MAX_PUB_KEY_DER_SIZE]; + byte refDer[DILITHIUM_MAX_PUB_KEY_DER_SIZE]; + int genDerSz = 0; + int refDerSz = 0; + + ret = wc_MlDsaKey_Init(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(genPub, WC_ML_DSA_44); + } + if (ret != 0) { + WH_ERROR_PRINT("Failed to init/setparams ML-DSA key %d\n", ret); + return ret; + } + + ret = wh_Client_MlDsaMakeCacheKeyAndExportPublic( + ctx, 0, WC_ML_DSA_44, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL, genPub); + if (ret != 0) { + WH_ERROR_PRINT("MlDsaMakeCacheKeyAndExportPublic failed %d\n", ret); + } + + /* Cross-check against a separate public export of the same keyId. */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(refPub, WC_ML_DSA_44); + } + if (ret == 0) { + ret = wh_Client_MlDsaExportPublicKey(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_MlDsaExportPublicKey failed %d\n", + ret); + } + else { + genDerSz = wc_MlDsaKey_PublicKeyToDer(genPub, genDer, + sizeof(genDer), 1); + refDerSz = wc_MlDsaKey_PublicKeyToDer(refPub, refDer, + sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs export\n"); + ret = -1; + } + } + wc_MlDsaKey_Free(refPub); + } + } + + /* Sign on the server using genPub directly as the HSM private-key handle + * (no separate key object), then verify with its exported public key. */ + if (ret == 0) { + ret = wh_Client_MlDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, genPub, + NULL, 0, WC_HASH_TYPE_NONE); + if (ret != 0) { + WH_ERROR_PRINT("HSM ML-DSA sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wh_Client_MlDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, genPub, NULL, 0, + WC_HASH_TYPE_NONE); + if ((ret != 0) || (verified != 1)) { + WH_ERROR_PRINT("verify with keygen pub failed ret=%d verify=%d\n", + ret, verified); + if (ret == 0) { + ret = -1; + } + } + } + + wc_MlDsaKey_Free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ML-DSA CACHE-AND-EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", + devId); + } + return ret; +} +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ + #ifdef WOLFHSM_CFG_DMA static int _whTest_CryptoMlDsaDmaClient(whClientContext* ctx) { @@ -506,6 +604,106 @@ static int _whTest_CryptoMlDsaExportPublicKeyDma(whClientContext* ctx) } return ret; } + +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY +/* DMA variant: one keygen call caches the private key and streams the public + * key back through the client's DMA buffer. Verify it byte-matches + * wh_Client_MlDsaExportPublicKeyDma and that it verifies an HSM signature. */ +static int _whTest_CryptoMlDsaCacheKeyAndExportPublicDma(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret = 0; + MlDsaKey genPub[1] = {0}; + MlDsaKey refPub[1] = {0}; + whKeyId keyId = WH_KEYID_ERASED; + byte msg[] = "ML-DSA DMA cache-export-public message"; + byte sig[DILITHIUM_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verified = 0; + byte genDer[DILITHIUM_MAX_PUB_KEY_DER_SIZE]; + byte refDer[DILITHIUM_MAX_PUB_KEY_DER_SIZE]; + int genDerSz = 0; + int refDerSz = 0; + + ret = wc_MlDsaKey_Init(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(genPub, WC_ML_DSA_44); + } + if (ret != 0) { + WH_ERROR_PRINT("Failed to init/setparams ML-DSA key %d\n", ret); + return ret; + } + + ret = wh_Client_MlDsaMakeCacheKeyDma( + ctx, WC_ML_DSA_44, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL, genPub); + if (ret != 0) { + WH_ERROR_PRINT("MlDsaMakeCacheKeyDma failed %d\n", ret); + } + + /* Cross-check against a separate public export of the same keyId. */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(refPub, WC_ML_DSA_44); + } + if (ret == 0) { + ret = wh_Client_MlDsaExportPublicKeyDma(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_MlDsaExportPublicKeyDma failed %d\n", + ret); + } + else { + genDerSz = wc_MlDsaKey_PublicKeyToDer(genPub, genDer, + sizeof(genDer), 1); + refDerSz = wc_MlDsaKey_PublicKeyToDer(refPub, refDer, + sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey (DMA) mismatch vs export\n"); + ret = -1; + } + } + wc_MlDsaKey_Free(refPub); + } + } + + /* Sign on the server (DMA) using genPub directly as the HSM private-key + * handle (no separate key object), then verify with its exported public + * key. */ + if (ret == 0) { + ret = wh_Client_MlDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, + genPub, NULL, 0, WC_HASH_TYPE_NONE); + if (ret != 0) { + WH_ERROR_PRINT("HSM ML-DSA DMA sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wh_Client_MlDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), + &verified, genPub, NULL, 0, + WC_HASH_TYPE_NONE); + if ((ret != 0) || (verified != 1)) { + WH_ERROR_PRINT( + "DMA verify with keygen pub failed ret=%d verify=%d\n", ret, + verified); + if (ret == 0) { + ret = -1; + } + } + } + + wc_MlDsaKey_Free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ML-DSA CACHE-AND-EXPORT-PUBLIC DMA DEVID=0x%X SUCCESS\n", + devId); + } + return ret; +} +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ #endif /* WOLFHSM_CFG_DMA */ #endif /* !WOLFSSL_DILITHIUM_NO_VERIFY && !WOLFSSL_DILITHIUM_NO_SIGN && \ @@ -1141,10 +1339,16 @@ int whTest_Crypto_MlDsa(whClientContext* ctx) WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaClient(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaExportPublicKey(ctx)); +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaCacheKeyAndExportPublic(ctx)); +#endif WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaBufferTooSmall(ctx)); #ifdef WOLFHSM_CFG_DMA WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaDmaClient(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaExportPublicKeyDma(ctx)); +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoMlDsaCacheKeyAndExportPublicDma(ctx)); +#endif #endif #endif #if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \ diff --git a/test-refactor/client-server/wh_test_crypto_rsa.c b/test-refactor/client-server/wh_test_crypto_rsa.c index e859e7f7c..cab778202 100644 --- a/test-refactor/client-server/wh_test_crypto_rsa.c +++ b/test-refactor/client-server/wh_test_crypto_rsa.c @@ -34,6 +34,7 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/rsa.h" +#include "wolfssl/wolfcrypt/asn.h" #include "wolfssl/wolfcrypt/random.h" #include "wolfhsm/wh_error.h" @@ -322,6 +323,114 @@ static int _whTest_CryptoRsaExportPublicKey(whClientContext* ctx) return ret; } +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches a separate wh_Client_RsaExportPublicKey + * and that it round-trips against the cached private key. */ +static int _whTest_CryptoRsaCacheKeyAndExportPublic(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret = WH_ERROR_OK; + WC_RNG rng[1]; + RsaKey genPub[1]; + RsaKey refPub[1] = {0}; + char plainText[sizeof(WH_TEST_RSA_PLAINTEXT)] = WH_TEST_RSA_PLAINTEXT; + char cipherText[RSA_KEY_BYTES]; + char finalText[RSA_KEY_BYTES]; + whKeyId keyId = WH_KEYID_ERASED; + byte genDer[2048]; + byte refDer[2048]; + int genDerSz = 0; + int refDerSz = 0; + int encLen = 0; + int decLen; + + memset(cipherText, 0, sizeof(cipherText)); + memset(finalText, 0, sizeof(finalText)); + + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = wc_InitRsaKey_ex(genPub, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRsaKey_ex %d\n", ret); + (void)wc_FreeRng(rng); + return ret; + } + + ret = wh_Client_RsaMakeCacheKeyAndExportPublic( + ctx, RSA_KEY_BITS, RSA_EXPONENT, &keyId, + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, 0, NULL, + genPub); + if (ret != 0) { + WH_ERROR_PRINT("RsaMakeCacheKeyAndExportPublic failed %d\n", ret); + } + + /* Cross-check against a separate public export of the same keyId. */ + if (ret == 0) { + ret = wc_InitRsaKey_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_RsaExportPublicKey(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_RsaExportPublicKey failed %d\n", ret); + } + else { + genDerSz = + wc_RsaKeyToPublicDer(genPub, genDer, sizeof(genDer)); + refDerSz = + wc_RsaKeyToPublicDer(refPub, refDer, sizeof(refDer)); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs export\n"); + ret = -1; + } + } + } + } + + /* Encrypt locally with the independently exported public key (refPub) the + * client holds. */ + if (ret == 0) { + encLen = wc_RsaPublicEncrypt((byte*)plainText, sizeof(plainText), + (byte*)cipherText, sizeof(cipherText), + refPub, rng); + if (encLen < 0) { + WH_ERROR_PRINT("PublicEncrypt with keygen pub failed %d\n", encLen); + ret = encLen; + } + } + + /* Decrypt on the HSM using genPub directly as the HSM private-key handle + * (no separate key object). */ + if (ret == 0) { + decLen = wc_RsaPrivateDecrypt((byte*)cipherText, encLen, + (byte*)finalText, sizeof(finalText), + genPub); + if (decLen < 0) { + WH_ERROR_PRINT("HSM PrivateDecrypt failed %d\n", decLen); + ret = decLen; + } + else if (memcmp(plainText, finalText, sizeof(plainText)) != 0) { + WH_ERROR_PRINT("RSA keygen-pub round-trip mismatch\n"); + ret = -1; + } + } + + (void)wc_FreeRsaKey(refPub); + (void)wc_FreeRsaKey(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + (void)wc_FreeRng(rng); + + if (ret == 0) { + WH_TEST_PRINT("RSA CACHE-AND-EXPORT-PUBLIC DEVID=0x%X SUCCESS\n", devId); + } + return ret; +} + /* Exercises wh_Client_RsaFunction with an undersized output buffer. */ static int _whTest_CryptoRsaBufferTooSmall(whClientContext* ctx) { @@ -389,6 +498,7 @@ int whTest_Crypto_Rsa(whClientContext* ctx) { WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsa(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsaExportPublicKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsaCacheKeyAndExportPublic(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoRsaBufferTooSmall(ctx)); return 0; } diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 3ef889e7e..f4cecc100 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -721,6 +721,100 @@ static int whTest_CryptoRsa(whClientContext* ctx, int devId, WC_RNG* rng) } } + /* Cache-and-export-public: a single keygen call returns the public key */ + if (ret == 0) { + RsaKey genPub[1]; + RsaKey refPub[1]; + whKeyId cacheId = WH_KEYID_ERASED; + byte genDer[2048]; + byte refDer[2048]; + int genDerSz = 0; + int refDerSz = 0; + int genInit = 0; + int refInit = 0; + + memset(cipherText, 0, sizeof(cipherText)); + memset(finalText, 0, sizeof(finalText)); + + ret = wc_InitRsaKey_ex(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + genInit = 1; + ret = wh_Client_RsaMakeCacheKeyAndExportPublic( + ctx, RSA_KEY_BITS, RSA_EXPONENT, &cacheId, + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, 0, + NULL, genPub); + if (ret != 0) { + WH_ERROR_PRINT("RsaMakeCacheKeyAndExportPublic failed %d\n", + ret); + } + } + + /* Cross-check the keygen-returned public key against a separate + * ExportPublicKey call on the same cached keyId. */ + if (ret == 0) { + ret = wc_InitRsaKey_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + refInit = 1; + ret = wh_Client_RsaExportPublicKey(ctx, cacheId, refPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT("RsaExportPublicKey failed %d\n", ret); + } + } + } + if (ret == 0) { + genDerSz = wc_RsaKeyToPublicDer(genPub, genDer, sizeof(genDer)); + refDerSz = wc_RsaKeyToPublicDer(refPub, refDer, sizeof(refDer)); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs ExportPublicKey\n"); + ret = -1; + } + } + + /* Prove the returned public key is usable: encrypt locally with the + * exported public key (refPub) the client holds, then decrypt on the + * HSM using genPub directly as the private-key handle (no separate key + * object). */ + if (ret == 0) { + int encLen = wc_RsaPublicEncrypt( + (byte*)plainText, sizeof(plainText), (byte*)cipherText, + sizeof(cipherText), refPub, rng); + if (encLen < 0) { + WH_ERROR_PRINT("PublicEncrypt with keygen pub failed %d\n", + encLen); + ret = encLen; + } + else { + int decLen = wc_RsaPrivateDecrypt( + (byte*)cipherText, encLen, (byte*)finalText, + sizeof(finalText), genPub); + if (decLen < 0) { + WH_ERROR_PRINT("HSM PrivateDecrypt failed %d\n", decLen); + ret = decLen; + } + else if (memcmp(plainText, finalText, sizeof(plainText)) != 0) { + WH_ERROR_PRINT("keygen-pub round-trip mismatch\n"); + ret = -1; + } + } + } + + if (genInit != 0) { + (void)wc_FreeRsaKey(genPub); + } + if (refInit != 0) { + (void)wc_FreeRsaKey(refPub); + } + if (!WH_KEYID_ISERASED(cacheId)) { + (void)wh_Client_KeyEvict(ctx, cacheId); + } + + if (ret == 0) { + WH_TEST_PRINT("RSA CACHE-AND-EXPORT-PUBLIC SUCCESS\n"); + } + } + if (ret == 0) { WH_TEST_PRINT("RSA SUCCESS\n"); } @@ -1482,6 +1576,103 @@ static int whTest_CryptoEcc(whClientContext* ctx, int devId, WC_RNG* rng) } } + /* Cache-and-export-public: a single keygen call returns the public key */ + if (ret == 0) { + whKeyId cacheId = WH_KEYID_ERASED; + ecc_key genPub[1]; + ecc_key refPub[1]; + byte genDer[256]; + byte refDer[256]; + int genDerSz = 0; + int refDerSz = 0; + int genInit = 0; + int refInit = 0; + uint8_t sig[ECC_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verify = 0; + uint8_t label[] = "ecc-cache-export"; + uint8_t readLabel[WH_NVM_LABEL_LEN] = {0}; + + ret = wc_ecc_init_ex(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + genInit = 1; + /* Exercise label forwarding on the cache-and-export path. */ + ret = wh_Client_EccMakeCacheKeyAndExportPublic( + ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, &cacheId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, + sizeof(label), label, genPub); + if (ret != 0) { + WH_ERROR_PRINT("EccMakeCacheKeyAndExportPublic failed %d\n", + ret); + } + } + + /* Cross-check the keygen-returned public key against ExportPublicKey, + * and confirm the label was stored with the cached key. */ + if (ret == 0) { + ret = wc_ecc_init_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + refInit = 1; + ret = wh_Client_EccExportPublicKey( + ctx, cacheId, refPub, sizeof(readLabel), readLabel); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_EccExportPublicKey failed %d\n", + ret); + } + else if (memcmp(readLabel, label, sizeof(label)) != 0) { + WH_ERROR_PRINT("keygen label not stored with cached key\n"); + ret = -1; + } + } + } + if (ret == 0) { + genDerSz = wc_EccPublicKeyToDer(genPub, genDer, sizeof(genDer), 1); + refDerSz = wc_EccPublicKeyToDer(refPub, refDer, sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs ExportPublicKey\n"); + ret = -1; + } + } + + /* Prove usability: sign on the HSM using genPub directly as the + * private-key handle (no separate key object), then verify locally with + * the exported public key (refPub) the client holds. */ + if (ret == 0) { + ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng, + genPub); + if (ret != 0) { + WH_ERROR_PRINT("HSM ECC sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &verify, + refPub); + if ((ret != 0) || (verify != 1)) { + WH_ERROR_PRINT( + "verify with keygen pub failed ret=%d verify=%d\n", ret, + verify); + if (ret == 0) { + ret = -1; + } + } + } + + if (genInit != 0) { + wc_ecc_free(genPub); + } + if (refInit != 0) { + wc_ecc_free(refPub); + } + if (!WH_KEYID_ISERASED(cacheId)) { + (void)wh_Client_KeyEvict(ctx, cacheId); + } + + if (ret == 0) { + WH_TEST_PRINT("ECC CACHE-AND-EXPORT-PUBLIC SUCCESS\n"); + } + } + if (ret == 0) { WH_TEST_PRINT("ECC SUCCESS\n"); } @@ -3519,6 +3710,93 @@ static int whTest_CryptoEd25519ExportPublic(whClientContext* ctx, int devId, return ret; } +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_Ed25519ExportPublicKey and + * that it verifies a signature made by the cached private key. */ +static int whTest_CryptoEd25519CacheKeyAndExportPublic(whClientContext* ctx, + int devId, WC_RNG* rng) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + ed25519_key genPub[1] = {0}; + ed25519_key refPub[1] = {0}; + byte msg[] = "Ed25519 cache-export-public message"; + byte sig[ED25519_SIG_SIZE]; + uint32_t sigSz = sizeof(sig); + int verified = 0; + byte genDer[128]; + byte refDer[128]; + int genDerSz = 0; + int refDerSz = 0; + (void)devId; + + ret = wc_ed25519_init_ex(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Ed25519MakeCacheKeyAndExportPublic( + ctx, &keyId, WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, + NULL, genPub); + if (ret != 0) { + WH_ERROR_PRINT("Ed25519MakeCacheKeyAndExportPublic failed %d\n", + ret); + } + } + + /* Cross-check the keygen-returned public key against ExportPublicKey. */ + if (ret == 0) { + ret = wc_ed25519_init_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Ed25519ExportPublicKey(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_Ed25519ExportPublicKey failed %d\n", + ret); + } + } + } + if (ret == 0) { + genDerSz = wc_Ed25519PublicKeyToDer(genPub, genDer, sizeof(genDer), 1); + refDerSz = wc_Ed25519PublicKeyToDer(refPub, refDer, sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs ExportPublicKey\n"); + ret = -1; + } + } + + /* Prove usability: sign on the HSM using genPub directly as the private-key + * handle (no separate key object), then verify locally with the exported + * public key (refPub) the client holds. */ + if (ret == 0) { + ret = wh_Client_Ed25519Sign(ctx, genPub, msg, (uint32_t)sizeof(msg), + (uint8_t)Ed25519, NULL, 0, sig, &sigSz); + if (ret != 0) { + WH_ERROR_PRINT("HSM Ed25519 sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wc_ed25519_verify_msg(sig, sigSz, msg, (word32)sizeof(msg), + &verified, refPub); + if ((ret != 0) || (verified != 1)) { + WH_ERROR_PRINT("verify with keygen pub failed ret=%d verify=%d\n", + ret, verified); + if (ret == 0) { + ret = -1; + } + } + } + + wc_ed25519_free(refPub); + wc_ed25519_free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + (void)rng; + if (ret == 0) { + WH_TEST_PRINT("Ed25519 CACHE-AND-EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} + #ifdef WOLFHSM_CFG_DMA static int whTest_CryptoEd25519Dma(whClientContext* ctx, int devId, WC_RNG* rng) { @@ -4218,6 +4496,109 @@ static int whTest_CryptoCurve25519ExportPublic(whClientContext* ctx, int devId, } return ret; } + +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_Curve25519ExportPublicKey and + * that an X25519 shared secret round-trips against the cached private key. */ +static int whTest_CryptoCurve25519CacheKeyAndExportPublic(whClientContext* ctx, + int devId, + WC_RNG* rng) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + curve25519_key genPub[1] = {0}; + curve25519_key refPub[1] = {0}; + curve25519_key localKey[1] = {0}; + uint8_t genRaw[CURVE25519_KEYSIZE] = {0}; + uint8_t refRaw[CURVE25519_KEYSIZE] = {0}; + word32 genRawLen = sizeof(genRaw); + word32 refRawLen = sizeof(refRaw); + uint8_t shared_hsm[CURVE25519_KEYSIZE] = {0}; + uint8_t shared_local[CURVE25519_KEYSIZE] = {0}; + word32 len = 0; + (void)devId; + + ret = wc_curve25519_init_ex(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Curve25519MakeCacheKeyAndExportPublic( + ctx, (uint16_t)CURVE25519_KEYSIZE, &keyId, + WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0, genPub); + if (ret != 0) { + WH_ERROR_PRINT( + "Curve25519MakeCacheKeyAndExportPublic failed %d\n", ret); + } + } + + /* Cross-check the keygen-returned public key against ExportPublicKey. */ + if (ret == 0) { + ret = wc_curve25519_init_ex(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Curve25519ExportPublicKey(ctx, keyId, refPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_Curve25519ExportPublicKey failed %d\n", ret); + } + } + } + if (ret == 0) { + ret = wc_curve25519_export_public(genPub, genRaw, &genRawLen); + if (ret == 0) { + ret = wc_curve25519_export_public(refPub, refRaw, &refRawLen); + } + if (ret != 0) { + WH_ERROR_PRINT("Curve25519 export_public failed %d\n", ret); + } + else if ((genRawLen != refRawLen) || + (memcmp(genRaw, refRaw, genRawLen) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs ExportPublicKey\n"); + ret = -1; + } + } + + /* Shared-secret round-trip using genPub directly as the HSM private-key + * handle (no separate key object): our local private key * genPub's + * exported public key (computed locally) must equal genPub's HSM private + * key * our local public key (computed on the server). */ + if (ret == 0) { + ret = wc_curve25519_init_ex(localKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, localKey); + } + } + if (ret == 0) { + len = sizeof(shared_local); + ret = wc_curve25519_shared_secret(localKey, genPub, shared_local, &len); + if (ret != 0) { + WH_ERROR_PRINT("Local Curve25519 shared secret failed %d\n", ret); + } + } + if (ret == 0) { + len = sizeof(shared_hsm); + ret = wc_curve25519_shared_secret(genPub, localKey, shared_hsm, &len); + if (ret != 0) { + WH_ERROR_PRINT("HSM Curve25519 shared secret failed %d\n", ret); + } + } + if (ret == 0) { + if (memcmp(shared_hsm, shared_local, len) != 0) { + WH_ERROR_PRINT("Curve25519 keygen-pub shared secret mismatch\n"); + ret = -1; + } + } + + wc_curve25519_free(localKey); + wc_curve25519_free(refPub); + wc_curve25519_free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("CURVE25519 CACHE-AND-EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} #endif /* HAVE_CURVE25519 */ #ifndef NO_SHA256 @@ -12211,6 +12592,101 @@ static int whTestCrypto_MlDsaExportPublic(whClientContext* ctx, int devId, } return ret; } + +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_MlDsaExportPublicKey and that + * it verifies a signature made by the cached private key. */ +static int whTestCrypto_MlDsaCacheKeyAndExportPublic(whClientContext* ctx, + int devId, WC_RNG* rng, + int level) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + wc_MlDsaKey genPub[1] = {0}; + wc_MlDsaKey refPub[1] = {0}; + byte msg[] = "ML-DSA cache-export-public message"; + byte sig[MLDSA_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verified = 0; + byte genDer[MLDSA_MAX_BOTH_KEY_DER_SIZE]; + byte refDer[MLDSA_MAX_BOTH_KEY_DER_SIZE]; + int genDerSz = 0; + int refDerSz = 0; + (void)devId; + (void)rng; + + ret = wc_MlDsaKey_Init(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(genPub, level); + } + if (ret == 0) { + ret = wh_Client_MlDsaMakeCacheKeyAndExportPublic( + ctx, 0, level, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL, + genPub); + if (ret != 0) { + WH_ERROR_PRINT("MlDsaMakeCacheKeyAndExportPublic failed %d\n", ret); + } + } + + /* Cross-check the keygen-returned public key against ExportPublicKey. */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(refPub, level); + } + if (ret == 0) { + ret = wh_Client_MlDsaExportPublicKey(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_MlDsaExportPublicKey failed %d\n", + ret); + } + } + } + if (ret == 0) { + genDerSz = wc_MlDsaKey_PublicKeyToDer(genPub, genDer, sizeof(genDer), 1); + refDerSz = wc_MlDsaKey_PublicKeyToDer(refPub, refDer, sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey mismatch vs ExportPublicKey\n"); + ret = -1; + } + } + + /* Prove usability: sign on the HSM using genPub directly as the private-key + * handle (no separate key object), then verify with its exported public + * key. */ + if (ret == 0) { + ret = wh_Client_MlDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, genPub, + NULL, 0, WC_HASH_TYPE_NONE); + if (ret != 0) { + WH_ERROR_PRINT("HSM ML-DSA sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wh_Client_MlDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, genPub, NULL, 0, + WC_HASH_TYPE_NONE); + if ((ret != 0) || (verified != 1)) { + WH_ERROR_PRINT("verify with keygen pub failed ret=%d verify=%d\n", + ret, verified); + if (ret == 0) { + ret = -1; + } + } + } + + wc_MlDsaKey_Free(refPub); + wc_MlDsaKey_Free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ML-DSA CACHE-AND-EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} #endif /* WOLFSSL_MLDSA_PUBLIC_KEY && ML_DSA_44 available */ #ifdef WOLFHSM_CFG_DMA @@ -12551,27 +13027,123 @@ static int whTestCrypto_MlDsaExportPublicDma(whClientContext* ctx, int devId, } return ret; } -#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ - -#endif /* WOLFHSM_CFG_DMA */ -#endif /* !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ - !defined(WOLFSSL_MLDSA_NO_SIGN) && \ - !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ - !defined(WOLFSSL_NO_ML_DSA_44) */ -#if !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ - !defined(WOLFSSL_NO_ML_DSA_44) && \ - defined(WOLFHSM_CFG_DMA) -int whTestCrypto_MlDsaVerifyOnlyDma(whClientContext* ctx, int devId, - WC_RNG* rng) +/* DMA variant: one keygen call caches the private key and streams the public + * key back through the client's DMA buffer. Verify it byte-matches + * wh_Client_MlDsaExportPublicKeyDma and that it verifies an HSM signature. */ +static int whTestCrypto_MlDsaCacheKeyAndExportPublicDma(whClientContext* ctx, + int devId, WC_RNG* rng, + int level) { + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + wc_MlDsaKey genPub[1] = {0}; + wc_MlDsaKey refPub[1] = {0}; + byte msg[] = "ML-DSA DMA cache-export-public message"; + byte sig[MLDSA_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verified = 0; + byte genDer[MLDSA_MAX_PUB_KEY_DER_SIZE]; + byte refDer[MLDSA_MAX_PUB_KEY_DER_SIZE]; + int genDerSz = 0; + int refDerSz = 0; + (void)devId; (void)rng; - /* Vectors from wolfCrypt test vectors, but decoupled for isolated usage */ - const byte ml_dsa_44_pub_key[] = { - 0xd8, 0xac, 0xaf, 0xd8, 0x2e, 0x14, 0x23, 0x78, 0xf7, 0x0d, 0x9a, 0x04, - 0x2b, 0x92, 0x48, 0x67, 0x60, 0x55, 0x34, 0xd9, 0xac, 0x0b, 0xc4, 0x1f, - 0x46, 0xe8, 0x85, 0xb9, 0x2e, 0x1b, 0x10, 0x3a, 0x75, 0x7a, 0xc2, 0xbc, + ret = wc_MlDsaKey_Init(genPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(genPub, level); + } + if (ret == 0) { + ret = wh_Client_MlDsaMakeCacheKeyDma( + ctx, level, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, 0, NULL, + genPub); + if (ret != 0) { + WH_ERROR_PRINT("MlDsaMakeCacheKeyDma failed %d\n", ret); + } + } + + /* Cross-check the keygen-returned public key against ExportPublicKeyDma. */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(refPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(refPub, level); + } + if (ret == 0) { + ret = wh_Client_MlDsaExportPublicKeyDma(ctx, keyId, refPub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_MlDsaExportPublicKeyDma failed %d\n", + ret); + } + } + } + if (ret == 0) { + genDerSz = wc_MlDsaKey_PublicKeyToDer(genPub, genDer, sizeof(genDer), 1); + refDerSz = wc_MlDsaKey_PublicKeyToDer(refPub, refDer, sizeof(refDer), 1); + if ((genDerSz <= 0) || (genDerSz != refDerSz) || + (memcmp(genDer, refDer, (size_t)genDerSz) != 0)) { + WH_ERROR_PRINT("keygen pubkey (DMA) mismatch vs export\n"); + ret = -1; + } + } + + /* Prove usability: sign on the HSM (DMA) using genPub directly as the + * private-key handle (no separate key object), then verify with its + * exported public key. */ + if (ret == 0) { + ret = wh_Client_MlDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, + genPub, NULL, 0, WC_HASH_TYPE_NONE); + if (ret != 0) { + WH_ERROR_PRINT("HSM ML-DSA DMA sign failed %d\n", ret); + } + } + if (ret == 0) { + ret = wh_Client_MlDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), + &verified, genPub, NULL, 0, + WC_HASH_TYPE_NONE); + if ((ret != 0) || (verified != 1)) { + WH_ERROR_PRINT( + "DMA verify with keygen pub failed ret=%d verify=%d\n", ret, + verified); + if (ret == 0) { + ret = -1; + } + } + } + + wc_MlDsaKey_Free(refPub); + wc_MlDsaKey_Free(genPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ML-DSA CACHE-AND-EXPORT-PUBLIC DMA SUCCESS\n"); + } + return ret; +} +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ + +#endif /* WOLFHSM_CFG_DMA */ +#endif /* !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_NO_ML_DSA_44) */ + +#if !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + !defined(WOLFSSL_NO_ML_DSA_44) && \ + defined(WOLFHSM_CFG_DMA) +int whTestCrypto_MlDsaVerifyOnlyDma(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + (void)rng; + + /* Vectors from wolfCrypt test vectors, but decoupled for isolated usage */ + const byte ml_dsa_44_pub_key[] = { + 0xd8, 0xac, 0xaf, 0xd8, 0x2e, 0x14, 0x23, 0x78, 0xf7, 0x0d, 0x9a, 0x04, + 0x2b, 0x92, 0x48, 0x67, 0x60, 0x55, 0x34, 0xd9, 0xac, 0x0b, 0xc4, 0x1f, + 0x46, 0xe8, 0x85, 0xb9, 0x2e, 0x1b, 0x10, 0x3a, 0x75, 0x7a, 0xc2, 0xbc, 0x76, 0xf0, 0x6d, 0x05, 0xa4, 0x78, 0x48, 0x84, 0x26, 0x69, 0xbd, 0x26, 0x1d, 0x73, 0x60, 0xaa, 0x57, 0x9d, 0x8c, 0x66, 0xb1, 0x19, 0xea, 0x11, 0xff, 0xbb, 0xf6, 0xeb, 0x26, 0x26, 0xac, 0x78, 0x74, 0x46, 0x6d, 0x51, @@ -13567,6 +14139,136 @@ static int whTestCrypto_MlKemExportPublic(whClientContext* ctx, int devId, return ret; } +/* One keygen call caches the private key and returns the public key. Verify + * the returned public key byte-matches wh_Client_MlKemExportPublicKey and that + * a KEM encapsulate/decapsulate round-trips against the cached private key. */ +static int whTestCrypto_MlKemCacheKeyAndExportPublic(whClientContext* ctx, + int devId, WC_RNG* rng) +{ + int ret = 0; + int levels[3]; + int levelCnt = 0; + int i; + + levelCnt = whTestCrypto_MlKemGetLevels( + levels, (int)(sizeof(levels) / sizeof(levels[0]))); + + for (i = 0; (ret == 0) && (i < levelCnt); i++) { + whKeyId keyId = WH_KEYID_ERASED; + MlKemKey genPub[1] = {0}; + MlKemKey refPub[1] = {0}; + int genInited = 0; + int refInited = 0; + byte genRaw[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + byte refRaw[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + word32 genRawSz = 0; + word32 refRawSz = 0; + byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ssEnc[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + word32 ctLen = sizeof(ct); + word32 ssEncLen = sizeof(ssEnc); + word32 ssDecLen = sizeof(ssDec); + (void)devId; + + ret = wc_MlKemKey_Init(genPub, levels[i], NULL, INVALID_DEVID); + if (ret == 0) { + genInited = 1; + ret = wh_Client_MlKemMakeCacheKeyAndExportPublic( + ctx, levels[i], &keyId, WH_NVM_FLAGS_USAGE_DERIVE, 0, NULL, + genPub); + if (ret != 0) { + WH_ERROR_PRINT( + "MlKemMakeCacheKeyAndExportPublic failed level=%d %d\n", + levels[i], ret); + } + } + + /* Cross-check the keygen-returned public key against ExportPublicKey. */ + if (ret == 0) { + ret = wc_MlKemKey_Init(refPub, levels[i], NULL, INVALID_DEVID); + if (ret == 0) { + refInited = 1; + ret = wh_Client_MlKemExportPublicKey(ctx, keyId, refPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_MlKemExportPublicKey failed level=%d %d\n", + levels[i], ret); + } + } + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(genPub, &genRawSz); + if (ret == 0) { + ret = wc_MlKemKey_EncodePublicKey(genPub, genRaw, genRawSz); + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(refPub, &refRawSz); + } + if (ret == 0) { + ret = wc_MlKemKey_EncodePublicKey(refPub, refRaw, refRawSz); + } + if ((ret == 0) && ((genRawSz != refRawSz) || + (memcmp(genRaw, refRaw, genRawSz) != 0))) { + WH_ERROR_PRINT( + "keygen pubkey mismatch vs ExportPublicKey level=%d\n", + levels[i]); + ret = -1; + } + } + + /* Roundtrip: encapsulate locally with the exported public key (refPub) + * the client holds, decapsulate on the HSM using genPub directly as the + * private-key handle (no separate key object). */ + if (ret == 0) { + ret = wc_MlKemKey_CipherTextSize(refPub, &ctLen); + if (ret == 0) { + ret = wc_MlKemKey_SharedSecretSize(refPub, &ssEncLen); + } + if (ret == 0) { + ssDecLen = ssEncLen; + ret = wc_MlKemKey_Encapsulate(refPub, ct, ssEnc, rng); + if (ret != 0) { + WH_ERROR_PRINT( + "Encapsulate against keygen pub failed level=%d %d\n", + levels[i], ret); + } + } + } + if (ret == 0) { + ret = wh_Client_MlKemDecapsulate(ctx, genPub, ct, ctLen, ssDec, + &ssDecLen); + if (ret != 0) { + WH_ERROR_PRINT("Server decapsulate failed level=%d %d\n", + levels[i], ret); + } + else if ((ssEncLen != ssDecLen) || + (memcmp(ssEnc, ssDec, ssEncLen) != 0)) { + WH_ERROR_PRINT( + "ML-KEM keygen-pub roundtrip ss mismatch level=%d\n", + levels[i]); + ret = -1; + } + } + + if (refInited) { + wc_MlKemKey_Free(refPub); + } + if (genInited) { + wc_MlKemKey_Free(genPub); + } + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + } + + if (ret == 0) { + WH_TEST_PRINT("ML-KEM CACHE-AND-EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} + #ifdef WOLFHSM_CFG_DMA static int whTestCrypto_MlKemExportPublicDma(whClientContext* ctx, int devId, WC_RNG* rng) @@ -13747,6 +14449,136 @@ static int whTestCrypto_MlKemExportPublicDma(whClientContext* ctx, int devId, return ret; } +/* DMA variant: one keygen call caches the private key and streams the public + * key back through the client's DMA buffer. Verify it byte-matches + * wh_Client_MlKemExportPublicKeyDma and that a KEM round-trips against the + * cached private key. */ +static int whTestCrypto_MlKemCacheKeyAndExportPublicDma(whClientContext* ctx, + int devId, WC_RNG* rng) +{ + int ret = 0; + int levels[3]; + int levelCnt = 0; + int i; + + levelCnt = whTestCrypto_MlKemGetLevels( + levels, (int)(sizeof(levels) / sizeof(levels[0]))); + + for (i = 0; (ret == 0) && (i < levelCnt); i++) { + whKeyId keyId = WH_KEYID_ERASED; + MlKemKey genPub[1] = {0}; + MlKemKey refPub[1] = {0}; + int genInited = 0; + int refInited = 0; + byte genRaw[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + byte refRaw[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + word32 genRawSz = 0; + word32 refRawSz = 0; + byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ssEnc[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + word32 ctLen = sizeof(ct); + word32 ssEncLen = sizeof(ssEnc); + word32 ssDecLen = sizeof(ssDec); + (void)devId; + + ret = wc_MlKemKey_Init(genPub, levels[i], NULL, INVALID_DEVID); + if (ret == 0) { + genInited = 1; + ret = wh_Client_MlKemMakeCacheKeyDma( + ctx, levels[i], &keyId, WH_NVM_FLAGS_USAGE_DERIVE, 0, NULL, + genPub); + if (ret != 0) { + WH_ERROR_PRINT("MlKemMakeCacheKeyDma failed level=%d %d\n", + levels[i], ret); + } + } + + /* Cross-check against a separate public DMA export of the same keyId. */ + if (ret == 0) { + ret = wc_MlKemKey_Init(refPub, levels[i], NULL, INVALID_DEVID); + if (ret == 0) { + refInited = 1; + ret = wh_Client_MlKemExportPublicKeyDma(ctx, keyId, refPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_MlKemExportPublicKeyDma failed level=%d %d\n", + levels[i], ret); + } + } + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(genPub, &genRawSz); + if (ret == 0) { + ret = wc_MlKemKey_EncodePublicKey(genPub, genRaw, genRawSz); + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(refPub, &refRawSz); + } + if (ret == 0) { + ret = wc_MlKemKey_EncodePublicKey(refPub, refRaw, refRawSz); + } + if ((ret == 0) && ((genRawSz != refRawSz) || + (memcmp(genRaw, refRaw, genRawSz) != 0))) { + WH_ERROR_PRINT( + "keygen pubkey (DMA) mismatch vs export level=%d\n", + levels[i]); + ret = -1; + } + } + + /* Roundtrip: encapsulate locally with the exported public key (refPub) + * the client holds, decapsulate on the HSM using genPub directly as the + * private-key handle (no separate key object). */ + if (ret == 0) { + ret = wc_MlKemKey_CipherTextSize(refPub, &ctLen); + if (ret == 0) { + ret = wc_MlKemKey_SharedSecretSize(refPub, &ssEncLen); + } + if (ret == 0) { + ssDecLen = ssEncLen; + ret = wc_MlKemKey_Encapsulate(refPub, ct, ssEnc, rng); + if (ret != 0) { + WH_ERROR_PRINT( + "Encapsulate against keygen pub (DMA) failed level=%d " + "%d\n", levels[i], ret); + } + } + } + if (ret == 0) { + ret = wh_Client_MlKemDecapsulate(ctx, genPub, ct, ctLen, ssDec, + &ssDecLen); + if (ret != 0) { + WH_ERROR_PRINT("Server decapsulate (DMA) failed level=%d %d\n", + levels[i], ret); + } + else if ((ssEncLen != ssDecLen) || + (memcmp(ssEnc, ssDec, ssEncLen) != 0)) { + WH_ERROR_PRINT( + "ML-KEM DMA keygen-pub roundtrip ss mismatch level=%d\n", + levels[i]); + ret = -1; + } + } + + if (refInited) { + wc_MlKemKey_Free(refPub); + } + if (genInited) { + wc_MlKemKey_Free(genPub); + } + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + } + + if (ret == 0) { + WH_TEST_PRINT("ML-KEM CACHE-AND-EXPORT-PUBLIC DMA SUCCESS\n"); + } + return ret; +} + static int whTestCrypto_MlKemDmaClient(whClientContext* ctx, int devId, WC_RNG* rng) { @@ -16021,6 +16853,159 @@ int whTest_CryptoKeyRevocationAesCbc(whClientContext* client, WC_RNG* rng) #endif /* !NO_AES && HAVE_AES_CBC && \ WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS */ +/* Negative tests: every cache-and-export keygen function must reject + * WH_NVM_FLAGS_EPHEMERAL, a NULL inout_key_id, and a NULL pub with + * WH_ERROR_BADARGS, before contacting the server. level is passed as 0 for the + * PQC calls since the argument guards run before any level validation. */ +static int whTest_CryptoMakeCacheKeyExportPublicArgs(whClientContext* ctx) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + { + RsaKey rsa[1] = {0}; + if (wh_Client_RsaMakeCacheKeyAndExportPublic( + ctx, 2048, WC_RSA_EXPONENT, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, + NULL, rsa) != WH_ERROR_BADARGS || + wh_Client_RsaMakeCacheKeyAndExportPublic( + NULL, 2048, WC_RSA_EXPONENT, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, + rsa) != WH_ERROR_BADARGS || + wh_Client_RsaMakeCacheKeyAndExportPublic( + ctx, 2048, WC_RSA_EXPONENT, NULL, WH_NVM_FLAGS_NONE, 0, NULL, + rsa) != WH_ERROR_BADARGS || + wh_Client_RsaMakeCacheKeyAndExportPublic( + ctx, 2048, WC_RSA_EXPONENT, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, + NULL) != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("RSA cache-export arg validation failed\n"); + ret = -1; + } + } +#endif +#ifdef HAVE_ECC + if (ret == 0) { + ecc_key ecc[1] = {0}; + if (wh_Client_EccMakeCacheKeyAndExportPublic( + ctx, 32, ECC_SECP256R1, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, + ecc) != WH_ERROR_BADARGS || + wh_Client_EccMakeCacheKeyAndExportPublic( + NULL, 32, ECC_SECP256R1, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, + ecc) != WH_ERROR_BADARGS || + wh_Client_EccMakeCacheKeyAndExportPublic( + ctx, 32, ECC_SECP256R1, NULL, WH_NVM_FLAGS_NONE, 0, NULL, + ecc) != WH_ERROR_BADARGS || + wh_Client_EccMakeCacheKeyAndExportPublic( + ctx, 32, ECC_SECP256R1, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, + NULL) != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("ECC cache-export arg validation failed\n"); + ret = -1; + } + } +#endif +#ifdef HAVE_CURVE25519 + if (ret == 0) { + curve25519_key cv[1] = {0}; + if (wh_Client_Curve25519MakeCacheKeyAndExportPublic( + ctx, CURVE25519_KEYSIZE, &keyId, WH_NVM_FLAGS_EPHEMERAL, NULL, 0, + cv) != WH_ERROR_BADARGS || + wh_Client_Curve25519MakeCacheKeyAndExportPublic( + ctx, CURVE25519_KEYSIZE, NULL, WH_NVM_FLAGS_NONE, NULL, 0, + cv) != WH_ERROR_BADARGS || + wh_Client_Curve25519MakeCacheKeyAndExportPublic( + ctx, CURVE25519_KEYSIZE, &keyId, WH_NVM_FLAGS_NONE, NULL, 0, + NULL) != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Curve25519 cache-export arg validation failed\n"); + ret = -1; + } + } +#endif +#ifdef HAVE_ED25519 + if (ret == 0) { + ed25519_key ed[1] = {0}; + if (wh_Client_Ed25519MakeCacheKeyAndExportPublic( + ctx, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, ed) != + WH_ERROR_BADARGS || + wh_Client_Ed25519MakeCacheKeyAndExportPublic( + ctx, NULL, WH_NVM_FLAGS_NONE, 0, NULL, ed) != WH_ERROR_BADARGS || + wh_Client_Ed25519MakeCacheKeyAndExportPublic( + ctx, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, NULL) != + WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Ed25519 cache-export arg validation failed\n"); + ret = -1; + } + } +#endif +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + if (ret == 0) { + wc_MlDsaKey mldsa[1] = {0}; + if (wh_Client_MlDsaMakeCacheKeyAndExportPublic( + ctx, 0, 0, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, mldsa) != + WH_ERROR_BADARGS || + wh_Client_MlDsaMakeCacheKeyAndExportPublic( + ctx, 0, 0, NULL, WH_NVM_FLAGS_NONE, 0, NULL, mldsa) != + WH_ERROR_BADARGS || + wh_Client_MlDsaMakeCacheKeyAndExportPublic( + ctx, 0, 0, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, NULL) != + WH_ERROR_BADARGS) { + WH_ERROR_PRINT("ML-DSA cache-export arg validation failed\n"); + ret = -1; + } +#ifdef WOLFHSM_CFG_DMA + if (ret == 0 && + (wh_Client_MlDsaMakeCacheKeyDma( + ctx, 0, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, mldsa) != + WH_ERROR_BADARGS || + wh_Client_MlDsaMakeCacheKeyDma( + ctx, 0, NULL, WH_NVM_FLAGS_NONE, 0, NULL, mldsa) != + WH_ERROR_BADARGS || + wh_Client_MlDsaMakeCacheKeyDma( + ctx, 0, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, NULL) != + WH_ERROR_BADARGS)) { + WH_ERROR_PRINT("ML-DSA DMA cache-export arg validation failed\n"); + ret = -1; + } +#endif /* WOLFHSM_CFG_DMA */ + } +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ +#ifdef WOLFSSL_HAVE_MLKEM + if (ret == 0) { + MlKemKey mlkem[1] = {0}; + if (wh_Client_MlKemMakeCacheKeyAndExportPublic( + ctx, 0, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, mlkem) != + WH_ERROR_BADARGS || + wh_Client_MlKemMakeCacheKeyAndExportPublic( + ctx, 0, NULL, WH_NVM_FLAGS_NONE, 0, NULL, mlkem) != + WH_ERROR_BADARGS || + wh_Client_MlKemMakeCacheKeyAndExportPublic( + ctx, 0, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, NULL) != + WH_ERROR_BADARGS) { + WH_ERROR_PRINT("ML-KEM cache-export arg validation failed\n"); + ret = -1; + } +#ifdef WOLFHSM_CFG_DMA + if (ret == 0 && + (wh_Client_MlKemMakeCacheKeyDma( + ctx, 0, &keyId, WH_NVM_FLAGS_EPHEMERAL, 0, NULL, mlkem) != + WH_ERROR_BADARGS || + wh_Client_MlKemMakeCacheKeyDma( + ctx, 0, NULL, WH_NVM_FLAGS_NONE, 0, NULL, mlkem) != + WH_ERROR_BADARGS || + wh_Client_MlKemMakeCacheKeyDma( + ctx, 0, &keyId, WH_NVM_FLAGS_NONE, 0, NULL, NULL) != + WH_ERROR_BADARGS)) { + WH_ERROR_PRINT("ML-KEM DMA cache-export arg validation failed\n"); + ret = -1; + } +#endif /* WOLFHSM_CFG_DMA */ + } +#endif /* WOLFSSL_HAVE_MLKEM */ + + if (ret == 0) { + WH_TEST_PRINT("KEYGEN-EXPORT-PUBLIC ARG VALIDATION SUCCESS\n"); + } + return ret; +} + /* WH_TEST_DMA_MODE_CNT (number of cryptoCb dispatch modes to exercise) is * provided by wh_test_common.h */ @@ -16197,9 +17182,13 @@ int whTest_CryptoClientConfig(whClientConfig* config) #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_CMAC && !NO_AES && WOLFSSL_AES_DIRECT */ -#ifndef NO_RSA /* Once-run public-key tests use the std (non-DMA) dispatch mode. */ (void)wh_Client_SetDmaMode(client, 0); + if (ret == 0) { + ret = whTest_CryptoMakeCacheKeyExportPublicArgs(client); + } + +#ifndef NO_RSA if (ret == 0) { ret = whTest_CryptoRsa(client, WH_CLIENT_DEVID(client), rng); } @@ -16264,6 +17253,14 @@ int whTest_CryptoClientConfig(whClientConfig* config) WH_ERROR_PRINT("Ed25519 export-public test failed: %d\n", ret); } } + if (ret == 0) { + ret = whTest_CryptoEd25519CacheKeyAndExportPublic( + client, WH_CLIENT_DEVID(client), rng); + if (ret != 0) { + WH_ERROR_PRINT( + "Ed25519 cache-and-export-public test failed: %d\n", ret); + } + } #ifdef WOLFHSM_CFG_DMA (void)wh_Client_SetDmaMode(client, 1); if (ret == 0) { @@ -16296,6 +17293,14 @@ int whTest_CryptoClientConfig(whClientConfig* config) WH_ERROR_PRINT("Curve25519 export-public test failed: %d\n", ret); } } + if (ret == 0) { + ret = whTest_CryptoCurve25519CacheKeyAndExportPublic( + client, WH_CLIENT_DEVID(client), rng); + if (ret != 0) { + WH_ERROR_PRINT( + "Curve25519 cache-and-export-public test failed: %d\n", ret); + } + } #endif /* HAVE_CURVE25519 */ #ifndef NO_SHA256 @@ -16489,6 +17494,16 @@ int whTest_CryptoClientConfig(whClientConfig* config) level, ret); } } + if (ret == 0) { + ret = whTestCrypto_MlDsaCacheKeyAndExportPublic( + client, WH_CLIENT_DEVID(client), rng, level); + if (ret != 0) { + WH_ERROR_PRINT( + "ML-DSA cache-and-export-public test failed " + "(level %d): %d\n", + level, ret); + } + } #endif #ifdef WOLFHSM_CFG_DMA @@ -16508,6 +17523,16 @@ int whTest_CryptoClientConfig(whClientConfig* config) level, ret); } } + if (ret == 0) { + ret = whTestCrypto_MlDsaCacheKeyAndExportPublicDma( + client, WH_CLIENT_DEVID(client), rng, level); + if (ret != 0) { + WH_ERROR_PRINT( + "ML-DSA cache-and-export-public DMA test failed " + "(level %d): %d\n", + level, ret); + } + } #endif #endif /* WOLFHSM_CFG_DMA*/ } @@ -16563,6 +17588,14 @@ int whTest_CryptoClientConfig(whClientConfig* config) WH_ERROR_PRINT("ML-KEM export-public test failed: %d\n", ret); } } + if (ret == 0) { + ret = whTestCrypto_MlKemCacheKeyAndExportPublic( + client, WH_CLIENT_DEVID(client), rng); + if (ret != 0) { + WH_ERROR_PRINT( + "ML-KEM cache-and-export-public test failed: %d\n", ret); + } + } #ifdef WOLFHSM_CFG_DMA (void)wh_Client_SetDmaMode(client, 1); @@ -16576,6 +17609,14 @@ int whTest_CryptoClientConfig(whClientConfig* config) WH_ERROR_PRINT("ML-KEM export-public DMA test failed: %d\n", ret); } } + if (ret == 0) { + ret = whTestCrypto_MlKemCacheKeyAndExportPublicDma( + client, WH_CLIENT_DEVID(client), rng); + if (ret != 0) { + WH_ERROR_PRINT( + "ML-KEM cache-and-export-public DMA test failed: %d\n", ret); + } + } #endif /* WOLFHSM_CFG_DMA */ #endif /* !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 7698b877b..f213f5e8d 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -280,6 +280,40 @@ int wh_Client_Curve25519MakeCacheKey(whClientContext* ctx, whKeyId *inout_key_id, whNvmFlags flags, const uint8_t* label, uint16_t label_len); +/** + * @brief Generate a Curve25519 key in the server key cache and return its + * public key in one round-trip. + * + * Combines a cache keygen and a public-key export so the client avoids a + * separate wh_Client_Curve25519ExportPublicKey call. On success inout_key_id + * holds the cached keyId and pub is populated with the public key, associated + * with that keyId, and stamped with the client's HSM devId, so it is + * immediately usable both as the exported public key and as a handle to the + * cached private key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] size Size of the key to generate in bytes, normally set to + * CURVE25519_KEY_SIZE. + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + */ +int wh_Client_Curve25519MakeCacheKeyAndExportPublic(whClientContext* ctx, + uint16_t size, + whKeyId *inout_key_id, whNvmFlags flags, + const uint8_t* label, uint16_t label_len, curve25519_key* pub); + /** * @brief Generate a Curve25519 key by the server and export to the client * @@ -511,6 +545,39 @@ int wh_Client_EccMakeCacheKey(whClientContext* ctx, whKeyId *inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); +/** + * @brief Generate an ECC key pair in the server key cache and return its public + * key in one round-trip. + * + * Combines a cache keygen and a public-key export so the client avoids a + * separate wh_Client_EccExportPublicKey call. On success inout_key_id holds the + * cached keyId and pub is populated with the public key, associated with that + * keyId, and stamped with the client's HSM devId, so it is immediately usable + * both as the exported public key and as a handle to the cached private key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] size Size of the key to generate in bytes (e.g. 32 for P-256). + * @param[in] curveId wolfCrypt curve identifier (e.g. ECC_SECP256R1). + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. Must not be NULL. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + */ +int wh_Client_EccMakeCacheKeyAndExportPublic(whClientContext* ctx, + int size, int curveId, + whKeyId *inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, ecc_key* pub); + /** * @brief Compute an ECDH shared secret using a public and private ECC key. * @@ -938,6 +1005,40 @@ int wh_Client_Ed25519MakeCacheKey(whClientContext* ctx, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); +/** + * @brief Create a new Ed25519 key in the server key cache and return its public + * key in one round-trip. + * + * Combines a cache keygen and a public-key export so the client avoids a + * separate wh_Client_Ed25519ExportPublicKey call. On success inout_key_id holds + * the cached keyId and pub is populated with the public key, associated with + * that keyId, and stamped with the client's HSM devId, so it is immediately + * usable both as the exported public key and as a handle to the cached private + * key. + * + * @param[in] ctx Pointer to the client context. + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + */ +int wh_Client_Ed25519MakeCacheKeyAndExportPublic(whClientContext* ctx, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + ed25519_key* pub); + /** * @brief Sign a message using an Ed25519 key on the server. */ @@ -1082,6 +1183,39 @@ int wh_Client_RsaMakeCacheKey(whClientContext* ctx, whKeyId* inout_key_id, whNvmFlags flags, uint32_t label_len, uint8_t* label); +/** + * @brief Generate an RSA key in the server key cache and return its public key + * in one round-trip. + * + * Combines a cache keygen and a public-key export so the client avoids a + * separate wh_Client_RsaExportPublicKey call. On success inout_key_id holds the + * cached keyId and pub is populated with the public key, associated with that + * keyId, and stamped with the client's HSM devId, so it is immediately usable + * both as the exported public key and as a handle to the cached private key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] size Size of the key to generate in bits (e.g. 2048). + * @param[in] e Public exponent to use (e.g. WC_RSA_EXPONENT / 65537). + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + */ +int wh_Client_RsaMakeCacheKeyAndExportPublic(whClientContext* ctx, + uint32_t size, uint32_t e, + whKeyId* inout_key_id, whNvmFlags flags, + uint32_t label_len, const uint8_t* label, RsaKey* pub); + /* TODO: Request server to perform the RSA function */ int wh_Client_RsaFunction(whClientContext* ctx, RsaKey* key, int rsa_type, @@ -2878,6 +3012,45 @@ int wh_Client_MlDsaMakeExportKey(whClientContext* ctx, int level, int size, int wh_Client_MlDsaMakeCacheKey(whClientContext* ctx, int size, int level, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); + +/** + * @brief Generate an ML-DSA key in the server key cache and return its public + * key in one round-trip. + * + * Combines a cache keygen and a public-key export so the client avoids a + * separate wh_Client_MlDsaExportPublicKey call. On success inout_key_id holds + * the cached keyId and pub is populated with the public key, associated with + * that keyId, and stamped with the client's HSM devId, so it is immediately + * usable both as the exported public key and as a handle to the cached private + * key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] size Size of the key to generate. + * @param[in] level ML-DSA security level of the key to generate. + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + * @note The server only serves this request when WOLFSSL_MLDSA_PUBLIC_KEY is + * compiled in; otherwise the call returns an error. + */ +int wh_Client_MlDsaMakeCacheKeyAndExportPublic(whClientContext* ctx, int size, + int level, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + wc_MlDsaKey* pub); /** * @brief Sign a message using a ML-DSA private key. * @@ -3016,6 +3189,41 @@ int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, int wh_Client_MlDsaMakeExportKeyDma(whClientContext* ctx, int level, wc_MlDsaKey* key); +/** + * @brief DMA variant: generate an ML-DSA key in the server key cache and return + * its public key in one round-trip. + * + * Streams the public key back through the client's DMA buffer so the client + * avoids a separate wh_Client_MlDsaExportPublicKeyDma call. On success + * inout_key_id holds the cached keyId and pub is populated with the public key, + * associated with that keyId, and stamped with the client's HSM devId, so it is + * immediately usable both as the exported public key and as a handle to the + * cached private key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] level ML-DSA security level of the key to generate. + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + * @note The server only serves this request when WOLFSSL_MLDSA_PUBLIC_KEY is + * compiled in; otherwise the call returns an error. + */ +int wh_Client_MlDsaMakeCacheKeyDma(whClientContext* ctx, int level, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + wc_MlDsaKey* pub); + /** * @brief Sign a message using ML-DSA with DMA. @@ -3193,6 +3401,41 @@ int wh_Client_MlKemMakeCacheKey(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); +/** + * @brief Generate an ML-KEM key in the server key cache and return its public + * key in one round-trip. + * + * Combines a cache keygen and a public-key export so the client avoids a + * separate wh_Client_MlKemExportPublicKey call. On success inout_key_id holds + * the cached keyId and pub is populated with the public key, associated with + * that keyId, and stamped with the client's HSM devId, so it is immediately + * usable both as the exported public key and as a handle to the cached private + * key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] level ML-KEM security level (WC_ML_KEM_512/768/1024). + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + */ +int wh_Client_MlKemMakeCacheKeyAndExportPublic(whClientContext* ctx, int level, + whKeyId* inout_key_id, + whNvmFlags flags, + uint16_t label_len, + const uint8_t* label, + MlKemKey* pub); + /** * @brief Perform ML-KEM encapsulation using a server-cached public key. * @@ -3300,6 +3543,39 @@ int wh_Client_MlKemExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, int wh_Client_MlKemMakeExportKeyDma(whClientContext* ctx, int level, MlKemKey* key); +/** + * @brief DMA variant: generate an ML-KEM key in the server key cache and return + * its public key in one round-trip. + * + * Streams the public key back through the client's DMA buffer so the client + * avoids a separate wh_Client_MlKemExportPublicKeyDma call. On success + * inout_key_id holds the cached keyId and pub is populated with the public key, + * associated with that keyId, and stamped with the client's HSM devId, so it is + * immediately usable both as the exported public key and as a handle to the + * cached private key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] level ML-KEM security level (WC_ML_KEM_512/768/1024). + * @param[in,out] inout_key_id Set to WH_KEYID_ERASED to have the server select + * a unique id for this key. + * @param[in] flags Optional flags to associate with the key. Must not include + * WH_NVM_FLAGS_EPHEMERAL (returns WH_ERROR_BADARGS). + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to associate with the key. Set to NULL if not + * used. + * @param[out] pub Key struct populated with the returned public key. + * @return int Returns 0 on success or a negative error code on failure. + * @note pub is stamped with the HSM devId, so follow-on wolfCrypt operations + * route to the server. Its public-key material is populated for local + * encoding (e.g. wc_*PublicKeyToDer); to use pub for a purely-local + * public-key operation, reset pub->devId = INVALID_DEVID first. + */ +int wh_Client_MlKemMakeCacheKeyDma(whClientContext* ctx, int level, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + MlKemKey* pub); + /** * @brief Perform ML-KEM encapsulation using DMA. *