From 4c0fda5c359d754cf18bbb2f317542b72f8ce77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 24 Apr 2026 12:08:51 +0200 Subject: [PATCH 1/3] Add ExportPublicKey API for cached public keys Introduces a new keystore action WH_KEY_EXPORT_PUBLIC that re-emits only the public portion of a cached public-key object, so callers that need a public key for a client-side operation (signature verification, key transport, etc.) no longer have to pull private material out of the HSM. A new WH_KS_OP_EXPORT_PUBLIC policy branch gates the path and intentionally bypasses NONEXPORTABLE since public material is non-sensitive. Wired end-to-end for RSA, ECC, Ed25519, Curve25519, and ML-DSA, with per-algorithm client wrappers (wh_Client_ExportPublicKey) and smoke tests that round-trip real operations (sign/verify, ECDH) against the exported public keys, plus a negative test for unknown keyId. Also adds a DMA variant (WH_KEY_EXPORT_PUBLIC_DMA) with a generic client transport and an ML-DSA-specific wrapper, byte-identity cross-validation against the non-DMA path, and a NOSPACE bounds-check test. Documentation added to docs/src/chapter05.md and docs/src-ja/chapter05.md. New message structs registered in the padding-check test. --- docs/src-ja/chapter05.md | 61 +++ docs/src/chapter05.md | 90 ++++ src/wh_client.c | 176 +++++++ src/wh_client_crypto.c | 133 +++++ src/wh_message_keystore.c | 64 +++ src/wh_server_keystore.c | 423 +++++++++++++++ test/wh_test_check_struct_padding.c | 12 +- test/wh_test_crypto.c | 771 ++++++++++++++++++++++++++++ wolfhsm/wh_client.h | 106 ++++ wolfhsm/wh_client_crypto.h | 167 ++++++ wolfhsm/wh_common.h | 12 + wolfhsm/wh_message.h | 2 + wolfhsm/wh_message_keystore.h | 62 +++ 13 files changed, 2075 insertions(+), 4 deletions(-) diff --git a/docs/src-ja/chapter05.md b/docs/src-ja/chapter05.md index b5f4477da..e0c8db9c7 100644 --- a/docs/src-ja/chapter05.md +++ b/docs/src-ja/chapter05.md @@ -301,6 +301,67 @@ wolfHSMのキャッシュスロットの数は`WOLFHSM_NUM_RAMKEYS`で設定さ `wh_Client_KeyErase`は、指定された鍵をキャッシュから削除し、NVMからも消去します。 +## キャッシュされた鍵の公開鍵のみのエクスポート + +`wh_Client_KeyExport`は、キャッシュされている鍵のバイト列をそのまま返します。公開鍵ペアがキャッシュされている場合、秘密鍵もクライアント側に送信されてしまうため、クライアント側で公開鍵のみを必要とする用途(署名検証、証明書作成など)では、HSM内に鍵を保持するセキュリティ上の利点が失われます。 + +wolfHSMはこの用途のために、キャッシュされた公開鍵ペアのキーIDを指定すると、サーバー側で公開鍵部分のみをDER形式で再生成し、クライアント側でwolfCryptの鍵オブジェクトにデシリアライズする専用のパスを提供します。秘密鍵はHSM内に残ります。 + +アルゴリズムごとのヘルパー関数がトランスポート層をラップしています: + +```c +int wh_Client_RsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + RsaKey* key, uint32_t label_len, uint8_t* label); +int wh_Client_EccExportPublicKey(whClientContext* ctx, whKeyId keyId, + ecc_key* key, uint16_t label_len, uint8_t* label); +int wh_Client_Ed25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + ed25519_key* key, uint16_t label_len, uint8_t* label); +int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + curve25519_key* key, uint16_t label_len, uint8_t* label); +int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, uint8_t* label); +``` + +例: HSM上でRSAの鍵ペアを生成し秘密鍵を`NONEXPORTABLE`でマークしたうえで、クライアント側で公開鍵を取得し、HSMがキャッシュされた秘密鍵で生成した署名を検証する。 + +```c +whKeyId keyId = WH_KEYID_ERASED; +RsaKey pub; + +/* 1. HSM内で鍵ペアを生成。NONEXPORTABLEはフルエクスポートをブロックする。 */ +wh_Client_RsaMakeCacheKey(&clientCtx, 2048, 0x10001, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, 0, NULL); + +/* 2. 公開鍵のみを取得する。NONEXPORTABLEはこのパスをブロックしない。 */ +wc_InitRsaKey_ex(&pub, NULL, INVALID_DEVID); +wh_Client_RsaExportPublicKey(&clientCtx, keyId, &pub, 0, NULL); +/* pub.type == RSA_PUBLIC; クライアント側の検証に使用可能 */ +``` + +### `WH_NVM_FLAGS_NONEXPORTABLE`との関係 + +`NONEXPORTABLE`は`wh_Client_KeyExport`および各アルゴリズムのフルエクスポート関数をブロックしますが、公開鍵のみのエクスポートパスは**ブロックしません**。公開鍵は機密性を持たない情報であり、もし公開鍵の取り出しも禁止してしまうと、キャッシュされた鍵を外部での検証や鍵配送に使えなくなるためです。公開鍵の取り出しも禁止したい場合は、そもそも鍵を生成・インポートしないか、用途が終わった段階で`wh_Client_KeyErase`で削除してください。 + +### DMAバリアント + +`WOLFHSM_CFG_DMA`が有効な場合、サーバーが公開鍵のDERをクライアント提供のバッファに直接DMAで書き込む並列APIが利用できます。これにより通信バッファ経由のコピーを回避できます。セマンティクス(`NONEXPORTABLE`の取り扱い、アルゴリズムセレクタ、エラー処理)は非DMAパスと同一です。 + +```c +int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, + uint16_t algo, const void* keyAddr, uint16_t keySz, + uint8_t* label, uint16_t labelSz, uint16_t* outSz); + +int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, uint8_t* label); +``` + +`wh_Client_KeyExportPublicDma`は汎用トランスポートで、呼び出し側は生の公開鍵DERを受け取って自身でデシリアライズします。`wh_Client_MlDsaExportPublicKeyDma`は既存の`wh_Client_MlDsaExportKeyDma`に対応するML-DSA専用ヘルパーで、ML-DSAの大きな公開鍵DERを通信バッファにコピーせずに済ませたい場合に使用できます。 + +### ワイヤプロトコル + +公開鍵のみのエクスポートは、専用のキーストアアクション`WH_KEY_EXPORT_PUBLIC`および`WH_KEY_EXPORT_PUBLIC_DMA`を使用します。キャッシュされた鍵は不透明なDERバイト列として保存されているため、サーバー側でどのように解釈すべきかをリクエストごとにアルゴリズムセレクタ(`wolfhsm/wh_common.h`の`WH_KEY_ALGO_*`)で指定します。独自トランスポートを実装する場合は、`WH_KEY_EXPORT`/`WH_KEY_EXPORT_DMA`と同様にこれらのアクションもルーティングしてください。 + ## 暗号操作 クライアントアプリケーションでwolfCryptを使用する場合、`devId`引数として`WOLFHSM_DEV_ID`を渡すことで、互換性のある暗号化操作をwolfHSMサーバーで実行できます。 diff --git a/docs/src/chapter05.md b/docs/src/chapter05.md index 2fbb449f7..53d917f15 100644 --- a/docs/src/chapter05.md +++ b/docs/src/chapter05.md @@ -287,6 +287,96 @@ wh_Client_KeyErase(clientCtx, keyId); `wh_Client_KeyExport` will read the key contents out of the HSM back to the client. `wh_Client_KeyErase` will remove the indicated key from cache and erase it from NVM. +## Exporting only the public half of a cached key + +`wh_Client_KeyExport` returns the raw cached key bytes. For a cached public-key +keypair this includes the private material, which defeats the security benefit +of leaving the key inside the HSM when the caller only needs the public half +(for example, to verify a signature on the client side or to package the key +into a certificate). + +wolfHSM provides a dedicated public-only export path that, given a key ID for +a cached public-key keypair, re-emits only the public portion on the server +and deserializes it into a wolfCrypt key object on the client. The private +key never leaves the HSM. + +Per-algorithm helpers wrap the transport layer: + +```c +int wh_Client_RsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + RsaKey* key, uint32_t label_len, uint8_t* label); +int wh_Client_EccExportPublicKey(whClientContext* ctx, whKeyId keyId, + ecc_key* key, uint16_t label_len, uint8_t* label); +int wh_Client_Ed25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + ed25519_key* key, uint16_t label_len, uint8_t* label); +int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + curve25519_key* key, uint16_t label_len, uint8_t* label); +int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, uint8_t* label); +``` + +Example: generate an RSA keypair on the HSM with the private key marked +`NONEXPORTABLE`, obtain the public key on the client, and verify a signature +the HSM produced using the cached private key. + +```c +whKeyId keyId = WH_KEYID_ERASED; +RsaKey pub; + +/* 1. Generate keypair inside the HSM. NONEXPORTABLE blocks full export. */ +wh_Client_RsaMakeCacheKey(&clientCtx, 2048, 0x10001, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, 0, NULL); + +/* 2. Fetch only the public half. NONEXPORTABLE does NOT block this. */ +wc_InitRsaKey_ex(&pub, NULL, INVALID_DEVID); +wh_Client_RsaExportPublicKey(&clientCtx, keyId, &pub, 0, NULL); +/* pub.type == RSA_PUBLIC; usable for client-side verification */ +``` + +### Interaction with `WH_NVM_FLAGS_NONEXPORTABLE` + +`NONEXPORTABLE` blocks `wh_Client_KeyExport` and the per-algorithm full-export +helpers. It does **not** block the public-only export path, because public key +material is non-sensitive — preventing it from ever leaving the HSM would make +the cached key unusable for any external verification or key-transport use +case. If you want to block even public extraction, do not generate or import +the key in the first place, or remove it with `wh_Client_KeyErase` after it +has served its purpose. + +### DMA variant + +For builds with `WOLFHSM_CFG_DMA` enabled, parallel APIs let the server +write the public DER directly into a client-provided buffer using the +existing DMA transport, avoiding the comm-buffer copy. The semantics +(NONEXPORTABLE carve-out, algo selector, error handling) are identical to +the non-DMA path. + +```c +int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, + uint16_t algo, const void* keyAddr, uint16_t keySz, + uint8_t* label, uint16_t labelSz, uint16_t* outSz); + +int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, uint8_t* label); +``` + +`wh_Client_KeyExportPublicDma` is the generic transport — callers receive +raw public DER and deserialize it themselves. `wh_Client_MlDsaExportPublicKeyDma` +mirrors the existing `wh_Client_MlDsaExportKeyDma` per-algorithm helper for +the case where ML-DSA's large public DER benefits most from skipping the +comm-buffer staging. + +### Wire protocol + +The public-only export uses dedicated keystore actions, +`WH_KEY_EXPORT_PUBLIC` and `WH_KEY_EXPORT_PUBLIC_DMA`, with a per-request +algorithm selector (`WH_KEY_ALGO_*` in `wolfhsm/wh_common.h`) because +cached keys are stored as opaque DER bytes and the server needs to know +how to decode them. Integrators implementing custom transports should +route these actions the same way they route `WH_KEY_EXPORT` and +`WH_KEY_EXPORT_DMA`. + ## Key Revocation Key revocation updates key metadata to prevent further cryptographic use without destroying storage. Revocation clears all `WH_NVM_FLAGS_USAGE_*` bits and sets `WH_NVM_FLAGS_NONMODIFIABLE`. The revoked state is persisted when the key is already committed to NVM. diff --git a/src/wh_client.c b/src/wh_client.c index 862dd4ee8..db8e85b8b 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -913,6 +913,94 @@ int wh_Client_KeyExport(whClientContext* c, whKeyId keyId, uint8_t* label, return ret; } +int wh_Client_KeyExportPublicRequest(whClientContext* c, whKeyId keyId, + uint16_t algo) +{ + whMessageKeystore_ExportPublicRequest* req = NULL; + + if (c == NULL || keyId == WH_KEYID_ERASED) { + return WH_ERROR_BADARGS; + } + + req = (whMessageKeystore_ExportPublicRequest*)wh_CommClient_GetDataPtr( + c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + req->id = keyId; + req->algo = algo; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, + WH_KEY_EXPORT_PUBLIC, sizeof(*req), + (uint8_t*)req); +} + +int wh_Client_KeyExportPublicResponse(whClientContext* c, uint8_t* label, + uint16_t labelSz, uint8_t* out, + uint16_t* outSz) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_ExportPublicResponse* resp = NULL; + uint8_t* packOut; + + if (c == NULL || outSz == NULL) { + return WH_ERROR_BADARGS; + } + + resp = (whMessageKeystore_ExportPublicResponse*)wh_CommClient_GetDataPtr( + c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + packOut = (uint8_t*)(resp + 1); + + ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp); + if (ret == WH_ERROR_OK) { + if (resp->rc != 0) { + ret = resp->rc; + } + else { + if (out == NULL) { + *outSz = resp->len; + } + else if (*outSz < resp->len) { + ret = WH_ERROR_ABORTED; + } + else { + memcpy(out, packOut, resp->len); + *outSz = resp->len; + } + if ((ret == WH_ERROR_OK) && (label != NULL)) { + if (labelSz > sizeof(resp->label)) { + memcpy(label, resp->label, WH_NVM_LABEL_LEN); + } + else { + memcpy(label, resp->label, labelSz); + } + } + } + } + return ret; +} + +int wh_Client_KeyExportPublic(whClientContext* c, whKeyId keyId, uint16_t algo, + uint8_t* label, uint16_t labelSz, uint8_t* out, + uint16_t* outSz) +{ + int ret; + ret = wh_Client_KeyExportPublicRequest(c, keyId, algo); + if (ret == 0) { + do { + ret = wh_Client_KeyExportPublicResponse(c, label, labelSz, out, + outSz); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + int wh_Client_KeyCommitRequest(whClientContext* c, whNvmId keyId) { whMessageKeystore_CommitRequest* req = NULL; @@ -1525,6 +1613,94 @@ int wh_Client_KeyExportDma(whClientContext* c, uint16_t keyId, } return ret; } + +int wh_Client_KeyExportPublicDmaRequest(whClientContext* c, whKeyId keyId, + uint16_t algo, const void* keyAddr, + uint16_t keySz) +{ + whMessageKeystore_ExportPublicDmaRequest* req = NULL; + + if (c == NULL || keyId == WH_KEYID_ERASED) { + return WH_ERROR_BADARGS; + } + + req = + (whMessageKeystore_ExportPublicDmaRequest*)wh_CommClient_GetDataPtr( + c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + req->id = keyId; + req->algo = algo; + req->key.addr = (uint64_t)((uintptr_t)keyAddr); + req->key.sz = keySz; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, + WH_KEY_EXPORT_PUBLIC_DMA, sizeof(*req), + (uint8_t*)req); +} + +int wh_Client_KeyExportPublicDmaResponse(whClientContext* c, uint8_t* label, + uint16_t labelSz, uint16_t* outSz) +{ + uint16_t resp_group; + uint16_t resp_action; + uint16_t resp_size; + int rc; + whMessageKeystore_ExportPublicDmaResponse* resp = NULL; + + if (c == NULL || outSz == NULL) { + return WH_ERROR_BADARGS; + } + + resp = + (whMessageKeystore_ExportPublicDmaResponse*)wh_CommClient_GetDataPtr( + c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + rc = wh_Client_RecvResponse(c, &resp_group, &resp_action, &resp_size, + (uint8_t*)resp); + if (rc == 0) { + if ((resp_group != WH_MESSAGE_GROUP_KEY) || + (resp_action != WH_KEY_EXPORT_PUBLIC_DMA) || + (resp_size != sizeof(*resp))) { + rc = WH_ERROR_ABORTED; + } + else { + if (resp->rc != 0) { + rc = resp->rc; + } + else { + *outSz = resp->len; + if (label != NULL) { + if (labelSz > WH_NVM_LABEL_LEN) { + labelSz = WH_NVM_LABEL_LEN; + } + memcpy(label, resp->label, labelSz); + } + } + } + } + return rc; +} + +int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, + uint16_t algo, const void* keyAddr, + uint16_t keySz, uint8_t* label, + uint16_t labelSz, uint16_t* outSz) +{ + int ret; + ret = wh_Client_KeyExportPublicDmaRequest(c, keyId, algo, keyAddr, keySz); + if (ret == 0) { + do { + ret = wh_Client_KeyExportPublicDmaResponse(c, label, labelSz, + outSz); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFHSM_CFG_ENABLE_CLIENT */ diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 9799c91d7..3f307ba9f 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -1539,6 +1539,26 @@ int wh_Client_EccExportKey(whClientContext* ctx, whKeyId keyId, ecc_key* key, return ret; } +int wh_Client_EccExportPublicKey(whClientContext* ctx, whKeyId keyId, + ecc_key* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + byte buffer[ECC_BUFSIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_ECC, label, + label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_EccDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + static int _EccMakeKey(whClientContext* ctx, int size, int curveId, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label, ecc_key* key) @@ -2223,6 +2243,26 @@ int wh_Client_Curve25519ExportKey(whClientContext* ctx, whKeyId keyId, return ret; } +int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + curve25519_key* key, + uint16_t label_len, uint8_t* label) +{ + int ret; + byte buffer[WOLFHSM_CFG_COMM_DATA_LEN] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_CURVE25519, label, + label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_Curve25519DeserializeKey(buffer, buffer_len, key); + } + return ret; +} + static int _Curve25519MakeKey(whClientContext* ctx, uint16_t size, whKeyId* inout_key_id, whNvmFlags flags, const uint8_t* label, uint16_t label_len, @@ -2551,6 +2591,26 @@ int wh_Client_Ed25519ExportKey(whClientContext* ctx, whKeyId keyId, return ret; } +int wh_Client_Ed25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + ed25519_key* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + uint8_t buffer[128] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_ED25519, label, + label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_Ed25519DeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + static int _Ed25519MakeKey(whClientContext* ctx, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label, ed25519_key* key) @@ -3298,6 +3358,38 @@ int wh_Client_RsaExportKey(whClientContext* ctx, whKeyId keyId, RsaKey* key, return ret; } +int wh_Client_RsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + RsaKey* key, uint32_t label_len, + uint8_t* label) +{ + int ret; + byte keyDer[WOLFHSM_CFG_COMM_DATA_LEN] = {0}; + uint16_t derSize = sizeof(keyDer); + uint8_t keyLabel[WH_NVM_LABEL_LEN] = {0}; + + if ((ctx == NULL) || (keyId == WH_KEYID_ERASED) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_RSA, keyLabel, + sizeof(keyLabel), keyDer, &derSize); + if (ret == WH_ERROR_OK) { + /* wh_Crypto_RsaDeserializeKeyDer falls back to public-key decode + * when the blob does not parse as a private key. */ + ret = wh_Crypto_RsaDeserializeKeyDer(derSize, keyDer, key); + if (ret == 0) { + if ((label_len > 0) && (label != NULL)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(label, keyLabel, label_len); + } + } + } + + return ret; +} + static int _RsaMakeKey(whClientContext* ctx, uint32_t size, uint32_t e, whNvmFlags flags, uint32_t label_len, uint8_t* label, whKeyId* inout_key_id, RsaKey* rsa) @@ -6725,6 +6817,26 @@ int wh_Client_MlDsaExportKey(whClientContext* ctx, whKeyId keyId, MlDsaKey* key, return ret; } +int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + byte buffer[DILITHIUM_MAX_BOTH_KEY_DER_SIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_MLDSA, label, + label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_MlDsaDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + static int _MlDsaMakeKey(whClientContext* ctx, int size, int level, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label, MlDsaKey* key) @@ -7196,6 +7308,27 @@ int wh_Client_MlDsaExportKeyDma(whClientContext* ctx, whKeyId keyId, return ret; } +int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + byte buffer[DILITHIUM_MAX_BOTH_KEY_DER_SIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_MLDSA, buffer, + buffer_len, label, label_len, + &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_MlDsaDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + static int _MlDsaMakeKeyDma(whClientContext* ctx, int level, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label, MlDsaKey* key) diff --git a/src/wh_message_keystore.c b/src/wh_message_keystore.c index 4c0369ff0..209ca5483 100644 --- a/src/wh_message_keystore.c +++ b/src/wh_message_keystore.c @@ -138,6 +138,36 @@ int wh_MessageKeystore_TranslateExportResponse( return 0; } +/* Key Export Public Request translation */ +int wh_MessageKeystore_TranslateExportPublicRequest( + uint16_t magic, const whMessageKeystore_ExportPublicRequest* src, + whMessageKeystore_ExportPublicRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, id); + WH_T16(magic, dest, src, algo); + return 0; +} + +/* Key Export Public Response translation */ +int wh_MessageKeystore_TranslateExportPublicResponse( + uint16_t magic, const whMessageKeystore_ExportPublicResponse* src, + whMessageKeystore_ExportPublicResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + WH_T32(magic, dest, src, len); + /* Label is just a byte array, no translation needed */ + if (src != dest) { + memcpy(dest->label, src->label, WH_NVM_LABEL_LEN); + } + return 0; +} + /* Key Erase Request translation */ int wh_MessageKeystore_TranslateEraseRequest( uint16_t magic, const whMessageKeystore_EraseRequest* src, @@ -260,6 +290,40 @@ int wh_MessageKeystore_TranslateExportDmaResponse( return 0; } +/* Key Export Public DMA Request translation */ +int wh_MessageKeystore_TranslateExportPublicDmaRequest( + uint16_t magic, const whMessageKeystore_ExportPublicDmaRequest* src, + whMessageKeystore_ExportPublicDmaRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T64(magic, dest, src, key.addr); + WH_T64(magic, dest, src, key.sz); + WH_T16(magic, dest, src, id); + WH_T16(magic, dest, src, algo); + return 0; +} + +/* Key Export Public DMA Response translation */ +int wh_MessageKeystore_TranslateExportPublicDmaResponse( + uint16_t magic, const whMessageKeystore_ExportPublicDmaResponse* src, + whMessageKeystore_ExportPublicDmaResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + WH_T64(magic, dest, src, dmaAddrStatus.badAddr.addr); + WH_T64(magic, dest, src, dmaAddrStatus.badAddr.sz); + WH_T32(magic, dest, src, len); + /* Label is just a byte array, no translation needed */ + if (src != dest) { + memcpy(dest->label, src->label, WH_NVM_LABEL_LEN); + } + return 0; +} + #endif /* WOLFHSM_CFG_DMA */ /* Key Wrap Request translation */ diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index e3724474e..0e448756c 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -34,6 +34,7 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/error-crypt.h" +#include "wolfssl/wolfcrypt/asn_public.h" #include "wolfhsm/wh_common.h" #include "wolfhsm/wh_error.h" @@ -48,6 +49,24 @@ #endif #include "wolfhsm/wh_server_keystore.h" +#include "wolfhsm/wh_server_crypto.h" +#include "wolfhsm/wh_crypto.h" + +#ifndef NO_RSA +#include "wolfssl/wolfcrypt/rsa.h" +#endif +#ifdef HAVE_ECC +#include "wolfssl/wolfcrypt/ecc.h" +#endif +#ifdef HAVE_ED25519 +#include "wolfssl/wolfcrypt/ed25519.h" +#endif +#ifdef HAVE_CURVE25519 +#include "wolfssl/wolfcrypt/curve25519.h" +#endif +#ifdef HAVE_DILITHIUM +#include "wolfssl/wolfcrypt/dilithium.h" +#endif static int _FindInCache(whServerContext* server, whKeyId keyId, int* out_index, int* out_big, uint8_t** out_buffer, @@ -89,6 +108,9 @@ typedef enum { WH_KS_OP_EVICT, WH_KS_OP_EXPORT, WH_KS_OP_REVOKE, + /* Exporting only the public half of a public-key object is considered + * non-sensitive and is intentionally not gated by NONEXPORTABLE. */ + WH_KS_OP_EXPORT_PUBLIC, } whKsOp; static int _KeyIsCommitted(whServerContext* server, whKeyId keyId) @@ -178,6 +200,11 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, } break; + case WH_KS_OP_EXPORT_PUBLIC: + /* Public material is non-sensitive; NONEXPORTABLE does not + * apply. The key still had to exist (checked above). */ + break; + case WH_KS_OP_COMMIT: case WH_KS_OP_REVOKE: /* Always allowed */ @@ -1820,6 +1847,205 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, *out_resp_size = sizeof(resp); } break; + + case WH_KEY_EXPORT_PUBLIC_DMA: { + whMessageKeystore_ExportPublicDmaRequest req; + whMessageKeystore_ExportPublicDmaResponse resp = {0}; + whKeyId serverKeyId; + uint8_t* cacheBuf = NULL; + whNvmMetadata* cacheMeta = NULL; + uint8_t* stage; + uint16_t stageMax; + uint16_t der_len = 0; + + /* translate request */ + (void)wh_MessageKeystore_TranslateExportPublicDmaRequest( + magic, + (whMessageKeystore_ExportPublicDmaRequest*)req_packet, &req); + + /* Reuse the tail of the response comm buffer as a server-local + * staging area for the public DER. The DMA response itself fits + * in the struct head, so everything after it is scratch. */ + stage = (uint8_t*)resp_packet + sizeof(resp); + stageMax = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - sizeof(resp)); + + serverKeyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, server->comm->client_id, req.id); + + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + /* Same policy carve-out as the non-DMA public export. */ + ret = _KeystoreCheckPolicy(server, WH_KS_OP_EXPORT_PUBLIC, + serverKeyId); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreFreshenKey(server, serverKeyId, + &cacheBuf, &cacheMeta); + } + (void)cacheBuf; + (void)stage; + (void)stageMax; + + if (ret == WH_ERROR_OK) { + switch (req.algo) { + #ifndef NO_RSA + case WH_KEY_ALGO_RSA: { + RsaKey rsa[1]; + int pub_ret; + ret = wc_InitRsaKey_ex(rsa, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_CacheExportRsaKey( + server, serverKeyId, rsa); + if (ret == 0) { + pub_ret = wc_RsaKeyToPublicDer( + rsa, stage, (word32)stageMax); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_FreeRsaKey(rsa); + } + } break; + #endif /* !NO_RSA */ + #ifdef HAVE_ECC + case WH_KEY_ALGO_ECC: { + ecc_key ecc[1]; + int pub_ret; + ret = wc_ecc_init_ex(ecc, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_EccKeyCacheExport( + server, serverKeyId, ecc); + if (ret == 0) { + pub_ret = wc_EccPublicKeyToDer( + ecc, stage, (word32)stageMax, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_ecc_free(ecc); + } + } break; + #endif /* HAVE_ECC */ + #ifdef HAVE_ED25519 + case WH_KEY_ALGO_ED25519: { + ed25519_key ed[1]; + int pub_ret; + ret = wc_ed25519_init_ex(ed, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_CacheExportEd25519Key( + server, serverKeyId, ed); + if (ret == 0) { + pub_ret = wc_Ed25519PublicKeyToDer( + ed, stage, (word32)stageMax, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_ed25519_free(ed); + } + } break; + #endif /* HAVE_ED25519 */ + #if defined(HAVE_DILITHIUM) && defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) + case WH_KEY_ALGO_MLDSA: { + MlDsaKey mldsa[1]; + int pub_ret; + ret = wc_MlDsaKey_Init(mldsa, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_MlDsaKeyCacheExport( + server, serverKeyId, mldsa); + if (ret == 0) { + pub_ret = wc_Dilithium_PublicKeyToDer( + mldsa, stage, (word32)stageMax, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_MlDsaKey_Free(mldsa); + } + } break; + #endif /* HAVE_DILITHIUM && WOLFSSL_DILITHIUM_PUBLIC_KEY */ + #ifdef HAVE_CURVE25519 + case WH_KEY_ALGO_CURVE25519: { + curve25519_key c25[1]; + int pub_ret; + ret = wc_curve25519_init_ex(c25, NULL, + INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_CacheExportCurve25519Key( + server, serverKeyId, c25); + if (ret == 0) { + pub_ret = wc_Curve25519PublicKeyToDer( + c25, stage, (word32)stageMax, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_curve25519_free(c25); + } + } break; + #endif /* HAVE_CURVE25519 */ + default: + ret = WH_ERROR_BADARGS; + break; + } + } + + /* Confirm client buffer is big enough, then DMA. */ + if (ret == WH_ERROR_OK) { + if ((uint64_t)der_len > req.key.sz) { + ret = WH_ERROR_NOSPACE; + } + else { + ret = whServerDma_CopyToClient( + server, req.key.addr, stage, der_len, + (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + resp.dmaAddrStatus.badAddr.addr = req.key.addr; + resp.dmaAddrStatus.badAddr.sz = req.key.sz; + } + } + } + + if (ret == WH_ERROR_OK && cacheMeta != NULL) { + memcpy(resp.label, cacheMeta->label, WH_NVM_LABEL_LEN); + } + + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + + resp.len = (ret == WH_ERROR_OK) ? der_len : 0; + resp.rc = ret; + + (void)wh_MessageKeystore_TranslateExportPublicDmaResponse( + magic, &resp, + (whMessageKeystore_ExportPublicDmaResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; #endif /* WOLFHSM_CFG_DMA */ case WH_KEY_EVICT: { @@ -1885,6 +2111,203 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, *out_resp_size = sizeof(resp) + resp.len; } break; + case WH_KEY_EXPORT_PUBLIC: { + whMessageKeystore_ExportPublicRequest req; + whMessageKeystore_ExportPublicResponse resp = {0}; + whKeyId serverKeyId; + uint8_t* cacheBuf = NULL; + whNvmMetadata* cacheMeta = NULL; + uint16_t der_len = 0; + uint16_t max_der; + + /* translate request */ + (void)wh_MessageKeystore_TranslateExportPublicRequest( + magic, + (whMessageKeystore_ExportPublicRequest*)req_packet, &req); + + /* out is after fixed size fields */ + out = (uint8_t*)resp_packet + sizeof(resp); + max_der = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - sizeof(resp)); + + serverKeyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, server->comm->client_id, req.id); + + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + /* Policy check: existence + the public-export carve-out. + * NONEXPORTABLE does not apply because public material is + * non-sensitive. */ + ret = _KeystoreCheckPolicy(server, WH_KS_OP_EXPORT_PUBLIC, + serverKeyId); + if (ret == WH_ERROR_OK) { + /* Load the cached key DER so we can deserialize directly + * below. cacheMeta also supplies the label for the + * response. */ + ret = wh_Server_KeystoreFreshenKey(server, serverKeyId, + &cacheBuf, &cacheMeta); + } + /* out/max_der may be unused if no PK algos are compiled in */ + (void)out; + (void)max_der; + + if (ret == WH_ERROR_OK) { + switch (req.algo) { + #ifndef NO_RSA + case WH_KEY_ALGO_RSA: { + RsaKey rsa[1]; + int pub_ret; + ret = wc_InitRsaKey_ex(rsa, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_RsaDeserializeKeyDer( + cacheMeta->len, cacheBuf, rsa); + if (ret == 0) { + pub_ret = wc_RsaKeyToPublicDer( + rsa, out, (word32)max_der); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_FreeRsaKey(rsa); + } + } break; + #endif /* !NO_RSA */ + #ifdef HAVE_ECC + case WH_KEY_ALGO_ECC: { + ecc_key ecc[1]; + int pub_ret; + ret = wc_ecc_init_ex(ecc, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_EccDeserializeKeyDer( + cacheBuf, cacheMeta->len, ecc); + if (ret == 0) { + /* Include curve parameters (last arg 1) + * so the client can deserialize without + * external context. */ + pub_ret = wc_EccPublicKeyToDer( + ecc, out, (word32)max_der, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_ecc_free(ecc); + } + } break; + #endif /* HAVE_ECC */ + #ifdef HAVE_ED25519 + case WH_KEY_ALGO_ED25519: { + ed25519_key ed[1]; + int pub_ret; + ret = wc_ed25519_init_ex(ed, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_Ed25519DeserializeKeyDer( + cacheBuf, cacheMeta->len, ed); + if (ret == 0) { + pub_ret = wc_Ed25519PublicKeyToDer( + ed, out, (word32)max_der, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_ed25519_free(ed); + } + } break; + #endif /* HAVE_ED25519 */ + #if defined(HAVE_DILITHIUM) && defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) + case WH_KEY_ALGO_MLDSA: { + MlDsaKey mldsa[1]; + int pub_ret; + ret = wc_MlDsaKey_Init(mldsa, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_MlDsaDeserializeKeyDer( + cacheBuf, cacheMeta->len, mldsa); + if (ret == 0) { + pub_ret = wc_Dilithium_PublicKeyToDer( + mldsa, out, (word32)max_der, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_MlDsaKey_Free(mldsa); + } + } break; + #endif /* HAVE_DILITHIUM && WOLFSSL_DILITHIUM_PUBLIC_KEY */ + #ifdef HAVE_CURVE25519 + case WH_KEY_ALGO_CURVE25519: { + curve25519_key c25[1]; + int pub_ret; + + ret = wc_curve25519_init_ex(c25, NULL, + INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_Curve25519DeserializeKey( + cacheBuf, cacheMeta->len, c25); + if (ret == 0) { + /* withAlg=1 wraps in SubjectPublicKeyInfo + * so the client's + * wh_Crypto_Curve25519DeserializeKey can + * parse the result. */ + pub_ret = wc_Curve25519PublicKeyToDer( + c25, out, (word32)max_der, 1); + if (pub_ret > 0) { + der_len = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) + ? WH_ERROR_ABORTED + : pub_ret; + } + } + wc_curve25519_free(c25); + } + } break; + #endif /* HAVE_CURVE25519 */ + default: + ret = WH_ERROR_BADARGS; + break; + } + } + + /* Only populate the label on full success. On any failure + * resp.label stays zeroed (from resp = {0}) so clients cannot + * observe partial metadata for a key whose public DER could + * not be produced. */ + if (ret == WH_ERROR_OK && cacheMeta != NULL) { + memcpy(resp.label, cacheMeta->label, WH_NVM_LABEL_LEN); + } + + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + + resp.len = der_len; + resp.rc = ret; + + (void)wh_MessageKeystore_TranslateExportPublicResponse( + magic, &resp, + (whMessageKeystore_ExportPublicResponse*)resp_packet); + + *out_resp_size = sizeof(resp) + resp.len; + } break; + case WH_KEY_COMMIT: { whMessageKeystore_CommitRequest req; whMessageKeystore_CommitResponse resp; diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index dfc6d4913..075b0640e 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -68,6 +68,8 @@ whMessageKeystore_EvictResponse keyEvictRes; whMessageKeystore_CommitResponse keyCommitRes; whMessageKeystore_ExportResponse keyExportRes; whMessageKeystore_EraseResponse keyEraseRes; +whMessageKeystore_ExportPublicRequest keyExportPublicReq; +whMessageKeystore_ExportPublicResponse keyExportPublicRes; /* Include counter message header for new counter message structures */ #include "wolfhsm/wh_message_counter.h" @@ -81,10 +83,12 @@ whMessageCounter_ReadResponse counterReadRes; whMessageCounter_DestroyResponse counterDestroyRes; /* DMA keystore messages */ -whMessageKeystore_CacheDmaRequest keyCacheDmaReq; -whMessageKeystore_CacheDmaResponse keyCacheDmaRes; -whMessageKeystore_ExportDmaRequest keyExportDmaReq; -whMessageKeystore_ExportDmaResponse keyExportDmaRes; +whMessageKeystore_CacheDmaRequest keyCacheDmaReq; +whMessageKeystore_CacheDmaResponse keyCacheDmaRes; +whMessageKeystore_ExportDmaRequest keyExportDmaReq; +whMessageKeystore_ExportDmaResponse keyExportDmaRes; +whMessageKeystore_ExportPublicDmaRequest keyExportPublicDmaReq; +whMessageKeystore_ExportPublicDmaResponse keyExportPublicDmaRes; #ifndef WOLFHSM_CFG_NO_CRYPTO /* Include crypto message header for new crypto message structures */ diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 8484ba7f1..917902f98 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -48,6 +48,7 @@ #ifdef WOLFHSM_CFG_ENABLE_CLIENT #include "wolfhsm/wh_client.h" #include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_crypto.h" /* Pull in client keywrap tests to run against server */ #include "wh_test_keywrap.h" #endif @@ -299,6 +300,145 @@ static int whTest_CryptoRsa(whClientContext* ctx, int devId, WC_RNG* rng) } (void)wh_Client_KeyEvict(ctx, keyId); } + + /* Export-public-only test on a NONEXPORTABLE cached key */ + if (ret == 0) { + RsaKey rsaFull[1]; + RsaKey rsaPub[1]; + /* Large enough to hold a real RSA key DER so the access-control + * assertion below doesn't get masked by a buffer-too-small + * (WH_ERROR_ABORTED) failure if the policy/read order ever changes. */ + char exportBuf[2048]; + uint16_t exportLen; + whKeyId pubOnlyId = WH_KEYID_ERASED; + + memset(cipherText, 0, sizeof(cipherText)); + memset(finalText, 0, sizeof(finalText)); + + ret = wh_Client_RsaMakeCacheKey( + ctx, RSA_KEY_BITS, RSA_EXPONENT, &pubOnlyId, + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT | + WH_NVM_FLAGS_NONEXPORTABLE, + 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached key %d\n", ret); + } + + /* Full export must be denied */ + if (ret == 0) { + exportLen = sizeof(exportBuf); + int denyRet = wh_Client_KeyExport(ctx, pubOnlyId, NULL, 0, + (uint8_t*)exportBuf, &exportLen); + if (denyRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "NONEXPORTABLE key was not denied on full export: %d\n", + denyRet); + ret = -1; + } + } + + /* Public export must succeed and yield a usable public key */ + if (ret == 0) { + ret = wc_InitRsaKey_ex(rsaPub, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init rsaPub %d\n", ret); + } + else { + ret = wh_Client_RsaExportPublicKey(ctx, pubOnlyId, rsaPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_RsaExportPublicKey failed %d\n", + ret); + } + else if (rsaPub->type != RSA_PUBLIC) { + WH_ERROR_PRINT( + "Exported RSA key is not public-only (type=%d)\n", + rsaPub->type); + ret = -1; + } + else { + /* Encrypt with the client-side public-only key, then + * decrypt on the HSM using the cached private key. */ + int encLen = wc_RsaPublicEncrypt( + (byte*)plainText, sizeof(plainText), + (byte*)cipherText, sizeof(cipherText), rsaPub, rng); + if (encLen < 0) { + WH_ERROR_PRINT( + "PublicEncrypt with exported pub failed %d\n", + encLen); + ret = encLen; + } + else { + ret = wc_InitRsaKey_ex(rsaFull, NULL, WH_DEV_ID); + if (ret == 0) { + ret = wh_Client_RsaSetKeyId(rsaFull, pubOnlyId); + } + if (ret == 0) { + int decLen = wc_RsaPrivateDecrypt( + (byte*)cipherText, encLen, (byte*)finalText, + sizeof(finalText), rsaFull); + if (decLen < 0) { + WH_ERROR_PRINT( + "HSM PrivateDecrypt failed %d\n", decLen); + ret = decLen; + } + else if (memcmp(plainText, finalText, + sizeof(plainText)) != 0) { + WH_ERROR_PRINT( + "Round-trip plaintext mismatch\n"); + ret = -1; + } + else { + ret = 0; + } + } + (void)wc_FreeRsaKey(rsaFull); + } + } + (void)wc_FreeRsaKey(rsaPub); + } + } + + /* Wrong algo selector must be rejected */ + if (ret == 0) { + byte dummy[8]; + uint16_t dummySz = sizeof(dummy); + int negRet = wh_Client_KeyExportPublic( + ctx, pubOnlyId, WH_KEY_ALGO_ECC, NULL, 0, dummy, &dummySz); + if (negRet == 0) { + WH_ERROR_PRINT( + "ExportPublic succeeded with wrong algo selector\n"); + ret = -1; + } + } + + /* Unknown keyId must be rejected with NOTFOUND */ + if (ret == 0) { + byte dummy[8]; + uint16_t dummySz = sizeof(dummy); + /* Pick a client-side keyId that is unlikely to exist. + * 0xBADE is well clear of auto-assigned IDs starting near 1. */ + whKeyId missing = (whKeyId)0xBADE; + int negRet = wh_Client_KeyExportPublic( + ctx, missing, WH_KEY_ALGO_RSA, NULL, 0, dummy, &dummySz); + if (negRet != WH_ERROR_NOTFOUND) { + WH_ERROR_PRINT( + "ExportPublic on missing keyId returned %d, " + "expected WH_ERROR_NOTFOUND\n", + negRet); + ret = -1; + } + } + + if (!WH_KEYID_ISERASED(pubOnlyId)) { + (void)wh_Client_KeyEvict(ctx, pubOnlyId); + } + + if (ret == 0) { + WH_TEST_PRINT("RSA EXPORT-PUBLIC SUCCESS\n"); + } + } + if (ret == 0) { WH_TEST_PRINT("RSA SUCCESS\n"); } @@ -634,12 +774,200 @@ static int whTest_CryptoEcc(whClientContext* ctx, int devId, WC_RNG* rng) } } + /* Export-public-only test on a NONEXPORTABLE cached ECC key. Generate + * a signature on the HSM and verify it client-side using the exported + * public key. */ + if (ret == 0) { + whKeyId pubOnlyId = WH_KEYID_ERASED; + ecc_key pubKey[1]; + uint8_t sig[ECC_MAX_SIG_SIZE]; + word32 sigLen = sizeof(sig); + int verify = 0; + /* Large enough to hold an ECC key DER so the access-control assertion + * doesn't get masked by a buffer-too-small failure. */ + uint8_t denyBuf[256]; + uint16_t denyLen = sizeof(denyBuf); + + ret = wh_Client_EccMakeCacheKey( + ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, &pubOnlyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, + 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to make NONEXPORTABLE cached ECC key %d\n", ret); + } + + /* Full export must be denied */ + if (ret == 0) { + int denyRet = wh_Client_KeyExport(ctx, pubOnlyId, NULL, 0, + denyBuf, &denyLen); + if (denyRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "NONEXPORTABLE ECC full export was not denied: %d\n", + denyRet); + ret = -1; + } + } + + /* Sign on HSM using the cached private key */ + if (ret == 0) { + ecc_key hsmKey[1]; + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, pubOnlyId); + } + if (ret == 0) { + ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng, + hsmKey); + if (ret != 0) { + WH_ERROR_PRINT("HSM ECC sign failed %d\n", ret); + } + } + wc_ecc_free(hsmKey); + } + + /* Export the public key, then verify client-side */ + if (ret == 0) { + ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_EccExportPublicKey(ctx, pubOnlyId, pubKey, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_EccExportPublicKey failed %d\n", ret); + } + else if (pubKey->type != ECC_PUBLICKEY) { + WH_ERROR_PRINT( + "Exported ECC key is not public-only (type=%d)\n", + pubKey->type); + ret = -1; + } + else { + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), + &verify, pubKey); + if (ret != 0 || verify != 1) { + WH_ERROR_PRINT( + "Client-side ECC verify failed ret=%d verify=%d\n", + ret, verify); + if (ret == 0) { + ret = -1; + } + } + } + wc_ecc_free(pubKey); + } + } + + if (!WH_KEYID_ISERASED(pubOnlyId)) { + (void)wh_Client_KeyEvict(ctx, pubOnlyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ECC EXPORT-PUBLIC SUCCESS\n"); + } + } + if (ret == 0) { WH_TEST_PRINT("ECC SUCCESS\n"); } return ret; } +#ifdef WOLFHSM_CFG_DMA +/* Generic-transport DMA smoke test: cache an ECC keypair, export the + * public half via wh_Client_KeyExportPublicDma (no per-algo wrapper), + * deserialize, and verify a HSM-side signature. The HSM sign step uses + * the regular (non-DMA) ECC cryptoCb because ECC sign over DMA is not in + * scope for this test - the DMA bit being exercised is the public-key + * export only. */ +static int whTest_CryptoEccExportPublicDma(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + ecc_key pubKey[1] = {0}; + uint8_t hash[TEST_ECC_KEYSIZE] = {0}; + uint8_t sig[ECC_MAX_SIG_SIZE] = {0}; + word32 sigLen = sizeof(sig); + int verified = 0; + byte derBuf[ECC_BUFSIZE]; + uint16_t derSz = sizeof(derBuf); + (void)devId; + + ret = wh_Client_EccMakeCacheKey( + ctx, TEST_ECC_KEYSIZE, TEST_ECC_CURVE_ID, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, + 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to make NONEXPORTABLE ECC cached key (DMA test) %d\n", + ret); + return ret; + } + + /* Sign on HSM with the cached private key (non-DMA cryptoCb). */ + if (ret == 0) { + ecc_key hsmKey[1]; + ret = wc_ecc_init_ex(hsmKey, NULL, WH_DEV_ID); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, keyId); + } + if (ret == 0) { + ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, rng, + hsmKey); + } + wc_ecc_free(hsmKey); + } + + /* Pull the public half out of the HSM via the generic DMA transport. */ + if (ret == 0) { + ret = wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_ECC, + derBuf, derSz, NULL, 0, &derSz); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_KeyExportPublicDma(ECC) failed %d\n", ret); + } + } + + if (ret == 0) { + ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_EccDeserializeKeyDer(derBuf, derSz, pubKey); + } + if (ret == 0 && pubKey->type != ECC_PUBLICKEY) { + WH_ERROR_PRINT( + "Exported ECC key (DMA) is not public-only (type=%d)\n", + pubKey->type); + ret = -1; + } + if (ret == 0) { + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), + &verified, pubKey); + if (ret != 0 || verified != 1) { + WH_ERROR_PRINT( + "Client-side ECC verify (DMA) failed ret=%d verify=%d\n", + ret, verified); + if (ret == 0) { + ret = -1; + } + } + } + wc_ecc_free(pubKey); + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ECC EXPORT-PUBLIC DMA SUCCESS\n"); + } + return ret; +} +#endif /* WOLFHSM_CFG_DMA */ + static int whTest_CryptoEccCacheDuplicate(whClientContext* client) { int ret = WH_ERROR_OK; @@ -1212,6 +1540,105 @@ static int whTest_CryptoEd25519ServerKey(whClientContext* ctx, int devId, return ret; } +static int whTest_CryptoEd25519ExportPublic(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + ed25519_key hsmKey[1] = {0}; + ed25519_key pubKey[1] = {0}; + byte msg[] = "Ed25519 export-public message"; + byte sig[ED25519_SIG_SIZE]; + uint32_t sigSz = sizeof(sig); + int verified = 0; + /* Large enough to hold an Ed25519 key DER so the access-control assertion + * doesn't get masked by a buffer-too-small failure. */ + uint8_t denyBuf[256]; + uint16_t denyLen = sizeof(denyBuf); + (void)devId; + + ret = wh_Client_Ed25519MakeCacheKey(ctx, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | + WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, + 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to make NONEXPORTABLE cached Ed25519 key %d\n", ret); + return ret; + } + + /* Full export must be denied */ + { + int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf, + &denyLen); + if (denyRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "NONEXPORTABLE Ed25519 full export was not denied: %d\n", + denyRet); + ret = -1; + } + } + + /* Sign on HSM */ + if (ret == 0) { + ret = wc_ed25519_init_ex(hsmKey, NULL, WH_DEV_ID); + if (ret == 0) { + ret = wh_Client_Ed25519SetKeyId(hsmKey, keyId); + } + if (ret == 0) { + ret = wh_Client_Ed25519Sign(ctx, hsmKey, msg, (uint32_t)sizeof(msg), + (uint8_t)Ed25519, NULL, 0, sig, &sigSz); + if (ret != 0) { + WH_ERROR_PRINT("HSM Ed25519 sign failed %d\n", ret); + } + } + wc_ed25519_free(hsmKey); + } + + /* Export public, verify client-side */ + if (ret == 0) { + ret = wc_ed25519_init_ex(pubKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Ed25519ExportPublicKey(ctx, keyId, pubKey, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_Ed25519ExportPublicKey failed %d\n", + ret); + } + else if (pubKey->pubKeySet != 1 || pubKey->privKeySet != 0) { + WH_ERROR_PRINT( + "Exported Ed25519 key flags wrong: pub=%d priv=%d\n", + (int)pubKey->pubKeySet, (int)pubKey->privKeySet); + ret = -1; + } + else { + ret = wc_ed25519_verify_msg(sig, sigSz, msg, + (word32)sizeof(msg), &verified, + pubKey); + if (ret != 0 || verified != 1) { + WH_ERROR_PRINT( + "Client-side Ed25519 verify failed ret=%d verify=%d\n", + ret, verified); + if (ret == 0) { + ret = -1; + } + } + } + wc_ed25519_free(pubKey); + } + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + (void)rng; + if (ret == 0) { + WH_TEST_PRINT("Ed25519 EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} + #ifdef WOLFHSM_CFG_DMA static int whTest_CryptoEd25519Dma(whClientContext* ctx, int devId, WC_RNG* rng) { @@ -1480,6 +1907,114 @@ static int whTest_CryptoCurve25519(whClientContext* ctx, int devId, WC_RNG* rng) } return ret; } + +static int whTest_CryptoCurve25519ExportPublic(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + curve25519_key hsmPub[1] = {0}; /* exported public half */ + curve25519_key hsmPriv[1] = {0}; /* HSM-side priv, referenced by keyId */ + curve25519_key localKey[1] = {0}; /* client-side ephemeral keypair */ + uint8_t shared_hsm[CURVE25519_KEYSIZE] = {0}; + uint8_t shared_local[CURVE25519_KEYSIZE] = {0}; + word32 len = 0; + /* Large enough to hold a Curve25519 key DER so the access-control + * assertion doesn't get masked by a buffer-too-small failure. */ + uint8_t denyBuf[256]; + uint16_t denyLen = sizeof(denyBuf); + + /* Cache an X25519 keypair on the HSM with NONEXPORTABLE set. */ + ret = wh_Client_Curve25519MakeCacheKey( + ctx, (uint16_t)CURVE25519_KEYSIZE, &keyId, + WH_NVM_FLAGS_USAGE_DERIVE | WH_NVM_FLAGS_NONEXPORTABLE, NULL, 0); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to make NONEXPORTABLE cached Curve25519 key %d\n", ret); + return ret; + } + + /* Full export must be denied */ + { + int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf, + &denyLen); + if (denyRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "NONEXPORTABLE Curve25519 full export was not denied: %d\n", + denyRet); + ret = -1; + } + } + + /* Export public; assert it parses */ + if (ret == 0) { + ret = wc_curve25519_init_ex(hsmPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_Curve25519ExportPublicKey(ctx, keyId, hsmPub, 0, + NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_Curve25519ExportPublicKey failed %d\n", ret); + } + } + } + + /* Shared-secret round-trip: HSM-side private + local public, vs. + * local private + HSM-side public. Both sides must agree. */ + 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) { + /* Local side: localKey (priv) * hsmPub (pub) */ + len = sizeof(shared_local); + ret = wc_curve25519_shared_secret(localKey, hsmPub, shared_local, &len); + if (ret != 0) { + WH_ERROR_PRINT("Local Curve25519 shared secret failed %d\n", ret); + } + } + + if (ret == 0) { + /* HSM side: reference the cached private by keyId (dispatch through + * the wolfHSM cryptoCb), pair it with the client-side localKey as + * the public half. */ + ret = wc_curve25519_init_ex(hsmPriv, NULL, devId); + if (ret == 0) { + ret = wh_Client_Curve25519SetKeyId(hsmPriv, keyId); + } + if (ret == 0) { + len = sizeof(shared_hsm); + ret = wc_curve25519_shared_secret(hsmPriv, localKey, shared_hsm, + &len); + if (ret != 0) { + WH_ERROR_PRINT("HSM Curve25519 shared secret failed %d\n", ret); + } + } + wc_curve25519_free(hsmPriv); + } + + if (ret == 0) { + if (memcmp(shared_hsm, shared_local, len) != 0) { + WH_ERROR_PRINT( + "Curve25519 export-public shared secret mismatch\n"); + ret = -1; + } + } + + wc_curve25519_free(localKey); + wc_curve25519_free(hsmPub); + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("CURVE25519 EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} #endif /* HAVE_CURVE25519 */ #ifndef NO_SHA256 @@ -6003,6 +6538,80 @@ static int whTestCrypto_MlDsaClient(whClientContext* ctx, int devId, return ret; } +#if defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && !defined(WOLFSSL_NO_ML_DSA_44) +static int whTestCrypto_MlDsaExportPublic(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + (void)rng; + + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + MlDsaKey pub[1] = {0}; + /* Large enough to hold an ML-DSA key DER so the access-control assertion + * doesn't get masked by a buffer-too-small failure. */ + uint8_t denyBuf[DILITHIUM_MAX_BOTH_KEY_DER_SIZE]; + uint16_t denyLen = sizeof(denyBuf); + (void)devId; + + /* MakeCacheKey at the smallest supported level, with NONEXPORTABLE. */ + ret = wh_Client_MlDsaMakeCacheKey( + ctx, 0, WC_ML_DSA_44, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, + 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("Failed to make NONEXPORTABLE cached ML-DSA key %d\n", + ret); + return ret; + } + + /* Full export must be denied */ + { + int denyRet = wh_Client_KeyExport(ctx, keyId, NULL, 0, denyBuf, + &denyLen); + if (denyRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "NONEXPORTABLE ML-DSA full export was not denied: %d\n", + denyRet); + ret = -1; + } + } + + /* Public export must succeed and yield a public-only key struct */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(pub, NULL, INVALID_DEVID); + if (ret == 0) { + /* Must set params before the decoder can populate the key. */ + ret = wc_MlDsaKey_SetParams(pub, WC_ML_DSA_44); + } + if (ret == 0) { + ret = wh_Client_MlDsaExportPublicKey(ctx, keyId, pub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_MlDsaExportPublicKey failed %d\n", ret); + } + else if (pub->pubKeySet != 1 || pub->prvKeySet != 0) { + WH_ERROR_PRINT( + "Exported ML-DSA key flags wrong: pub=%d prv=%d\n", + (int)pub->pubKeySet, (int)pub->prvKeySet); + ret = -1; + } + } + wc_MlDsaKey_Free(pub); + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ML-DSA EXPORT-PUBLIC SUCCESS\n"); + } + return ret; +} +#endif /* WOLFSSL_DILITHIUM_PUBLIC_KEY && ML_DSA_44 available */ + #ifdef WOLFHSM_CFG_DMA static int whTestCrypto_MlDsaDmaClient(whClientContext* ctx, int devId, WC_RNG* rng) @@ -6220,6 +6829,129 @@ static int whTestCrypto_MlDsaDmaClient(whClientContext* ctx, int devId, wc_MlDsaKey_Free(imported_key); return ret; } + +#ifdef WOLFSSL_DILITHIUM_PUBLIC_KEY +static int whTestCrypto_MlDsaExportPublicDma(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + (void)rng; + (void)devId; + + int ret = 0; + whKeyId keyId = WH_KEYID_ERASED; + MlDsaKey pub[1] = {0}; + uint8_t denyBuf[1]; + uint16_t denyLen = sizeof(denyBuf); + + /* Cache an ML-DSA-44 keypair NONEXPORTABLE on the HSM. */ + ret = wh_Client_MlDsaMakeCacheKey( + ctx, 0, WC_ML_DSA_44, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | + WH_NVM_FLAGS_NONEXPORTABLE, + 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to make NONEXPORTABLE ML-DSA cached key (DMA test) %d\n", + ret); + return ret; + } + + /* Full DMA export must be denied */ + { + byte fullBuf[DILITHIUM_MAX_BOTH_KEY_DER_SIZE]; + uint16_t fullLen = sizeof(fullBuf); + int denyRet = wh_Client_KeyExportDma(ctx, keyId, fullBuf, fullLen, + NULL, 0, &denyLen); + if (denyRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "NONEXPORTABLE ML-DSA full DMA export was not denied: %d\n", + denyRet); + ret = -1; + } + } + + /* Public DMA export must succeed and yield a usable public-only key. */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(pub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(pub, WC_ML_DSA_44); + } + if (ret == 0) { + ret = wh_Client_MlDsaExportPublicKeyDma(ctx, keyId, pub, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "wh_Client_MlDsaExportPublicKeyDma failed %d\n", ret); + } + else if (pub->pubKeySet != 1 || pub->prvKeySet != 0) { + WH_ERROR_PRINT( + "Exported ML-DSA key (DMA) flags wrong: pub=%d prv=%d\n", + (int)pub->pubKeySet, (int)pub->prvKeySet); + ret = -1; + } + } + wc_MlDsaKey_Free(pub); + } + + /* Byte-identity check: the generic DMA transport and the non-DMA + * transport must emit the same public-DER bytes for the same cached + * key. This cross-validates that the DMA path isn't producing + * subtly different output. */ + if (ret == 0) { + byte dmaDer[DILITHIUM_MAX_PUB_KEY_DER_SIZE]; + byte nonDmaDer[DILITHIUM_MAX_PUB_KEY_DER_SIZE]; + uint16_t dmaSz = sizeof(dmaDer); + uint16_t nonDmaSz = sizeof(nonDmaDer); + + ret = wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_MLDSA, + dmaDer, dmaSz, NULL, 0, &dmaSz); + if (ret != 0) { + WH_ERROR_PRINT( + "Generic ML-DSA DMA export failed for re-encode check %d\n", + ret); + } + else { + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_MLDSA, + NULL, 0, nonDmaDer, &nonDmaSz); + if (ret != 0) { + WH_ERROR_PRINT( + "Non-DMA ML-DSA export failed for re-encode check %d\n", + ret); + } + else if (dmaSz != nonDmaSz || + memcmp(dmaDer, nonDmaDer, dmaSz) != 0) { + WH_ERROR_PRINT( + "ML-DSA DMA and non-DMA public DER differ " + "(dmaSz=%u nonDmaSz=%u)\n", + (unsigned)dmaSz, (unsigned)nonDmaSz); + ret = -1; + } + } + } + + /* Negative: a too-small client buffer must yield WH_ERROR_NOSPACE. */ + if (ret == 0) { + byte tinyBuf[8]; + uint16_t tinySz = sizeof(tinyBuf); + int negRet = wh_Client_KeyExportPublicDma( + ctx, keyId, WH_KEY_ALGO_MLDSA, tinyBuf, tinySz, NULL, 0, &tinySz); + if (negRet != WH_ERROR_NOSPACE) { + WH_ERROR_PRINT( + "Too-small DMA buffer did not return NOSPACE: %d\n", negRet); + ret = -1; + } + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + + if (ret == 0) { + WH_TEST_PRINT("ML-DSA EXPORT-PUBLIC DMA SUCCESS\n"); + } + return ret; +} +#endif /* WOLFSSL_DILITHIUM_PUBLIC_KEY */ + #endif /* WOLFHSM_CFG_DMA */ #endif /* !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \ @@ -7460,6 +8192,15 @@ int whTest_CryptoClientConfig(whClientConfig* config) ret = whTest_CryptoEccCrossVerify(client, rng); } #endif +#ifdef WOLFHSM_CFG_DMA + if (ret == 0) { + ret = whTest_CryptoEccExportPublicDma(client, WH_DEV_ID_DMA, rng); + if (ret != 0) { + WH_ERROR_PRINT( + "ECC export-public DMA test failed: %d\n", ret); + } + } +#endif #endif /* HAVE_ECC */ #ifdef HAVE_ED25519 @@ -7479,6 +8220,12 @@ int whTest_CryptoClientConfig(whClientConfig* config) WH_ERROR_PRINT("Ed25519 server key test failed: %d\n", ret); } } + if (ret == 0) { + ret = whTest_CryptoEd25519ExportPublic(client, WH_DEV_ID, rng); + if (ret != 0) { + WH_ERROR_PRINT("Ed25519 export-public test failed: %d\n", ret); + } + } #ifdef WOLFHSM_CFG_DMA if (ret == 0) { ret = whTest_CryptoEd25519Dma(client, WH_DEV_ID_DMA, rng); @@ -7494,6 +8241,12 @@ int whTest_CryptoClientConfig(whClientConfig* config) if (ret == 0) { ret = whTest_CryptoCurve25519(client, WH_DEV_ID, rng); } + if (ret == 0) { + ret = whTest_CryptoCurve25519ExportPublic(client, WH_DEV_ID, rng); + if (ret != 0) { + WH_ERROR_PRINT("Curve25519 export-public test failed: %d\n", ret); + } + } #endif /* HAVE_CURVE25519 */ #ifndef NO_SHA256 @@ -7621,10 +8374,28 @@ int whTest_CryptoClientConfig(whClientConfig* config) ret = whTestCrypto_MlDsaClient(client, WH_DEV_ID, rng); } +#ifdef WOLFSSL_DILITHIUM_PUBLIC_KEY + if (ret == 0) { + ret = whTestCrypto_MlDsaExportPublic(client, WH_DEV_ID, rng); + if (ret != 0) { + WH_ERROR_PRINT("ML-DSA export-public test failed: %d\n", ret); + } + } +#endif + #ifdef WOLFHSM_CFG_DMA if (ret == 0) { ret = whTestCrypto_MlDsaDmaClient(client, WH_DEV_ID_DMA, rng); } +#ifdef WOLFSSL_DILITHIUM_PUBLIC_KEY + if (ret == 0) { + ret = whTestCrypto_MlDsaExportPublicDma(client, WH_DEV_ID_DMA, rng); + if (ret != 0) { + WH_ERROR_PRINT( + "ML-DSA export-public DMA test failed: %d\n", ret); + } + } +#endif #endif /* WOLFHSM_CFG_DMA*/ #endif /* !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \ diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index fa1c03500..cb685b362 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -660,6 +660,62 @@ int wh_Client_KeyExportResponse(whClientContext* c, uint8_t* label, int wh_Client_KeyExport(whClientContext* c, uint16_t keyId, uint8_t* label, uint16_t labelSz, uint8_t* out, uint16_t* outSz); +/** + * @brief Sends a request to export only the public portion of a cached key. + * + * Unlike wh_Client_KeyExport(), which returns the raw cached DER (potentially + * including private material), this function instructs the server to re-emit + * only the public half of the cached public-key object. The private key stays + * inside the HSM. The algo selector identifies how the cached bytes should + * be interpreted (see WH_KEY_ALGO_ENUM in wh_common.h). + * + * Non-blocking: returns immediately after sending. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId Key ID to export the public key of. + * @param[in] algo Algorithm selector (WH_KEY_ALGO_*). + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_KeyExportPublicRequest(whClientContext* c, whKeyId keyId, + uint16_t algo); + +/** + * @brief Receives the public-key export response from the server. + * + * Non-blocking: may return WH_ERROR_NOTREADY; callers should retry in that + * case. On success, writes the public DER into out and the associated label + * (if any) into label. + * + * @param[in] c Pointer to the client context. + * @param[out] label Optional buffer to receive the cached key's label. + * @param[in] labelSz Size of label buffer. + * @param[out] out Buffer receiving the public-only DER. + * @param[in,out] outSz In: size of out. Out: bytes written. + * @return int Returns 0 on success, WH_ERROR_NOTREADY if no response is + * available, or a negative error code on failure. + */ +int wh_Client_KeyExportPublicResponse(whClientContext* c, uint8_t* label, + uint16_t labelSz, uint8_t* out, + uint16_t* outSz); + +/** + * @brief Sends a public-key export request and receives the response. + * + * Blocking wrapper around wh_Client_KeyExportPublicRequest/Response. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId Key ID to export the public key of. + * @param[in] algo Algorithm selector (WH_KEY_ALGO_*). + * @param[out] label Optional buffer to receive the cached key's label. + * @param[in] labelSz Size of label buffer. + * @param[out] out Buffer receiving the public-only DER. + * @param[in,out] outSz In: size of out. Out: bytes written. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_KeyExportPublic(whClientContext* c, whKeyId keyId, uint16_t algo, + uint8_t* label, uint16_t labelSz, uint8_t* out, + uint16_t* outSz); + /** * @brief Sends a key commit request to the server. * @@ -886,6 +942,56 @@ int wh_Client_KeyExportDmaResponse(whClientContext* c, uint8_t* label, int wh_Client_KeyExportDma(whClientContext* c, uint16_t keyId, const void* keyAddr, uint16_t keySz, uint8_t* label, uint16_t labelSz, uint16_t* outSz); + +/** + * @brief Sends a public-key export DMA request to the server. + * + * DMA counterpart to wh_Client_KeyExportPublicRequest. The server decodes + * the cached key according to the algo selector, emits the public-only + * portion as DER, and DMAs that DER into the client buffer at keyAddr. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId Server key ID whose public key should be exported. + * @param[in] algo Algorithm selector (WH_KEY_ALGO_*). + * @param[in] keyAddr Client buffer that will receive the public DER. + * @param[in] keySz Size of the keyAddr buffer in bytes. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_KeyExportPublicDmaRequest(whClientContext* c, whKeyId keyId, + uint16_t algo, const void* keyAddr, + uint16_t keySz); + +/** + * @brief Receives the public-key export DMA response from the server. + * + * @param[in] c Pointer to the client context. + * @param[out] label Optional buffer to receive the cached key's label. + * @param[in] labelSz Size of label buffer. + * @param[out] outSz Receives the number of public-DER bytes the server + * wrote into the client buffer. + * @return int Returns 0 on success, WH_ERROR_NOTREADY if the response is + * not yet available, or a negative error code on failure. + */ +int wh_Client_KeyExportPublicDmaResponse(whClientContext* c, uint8_t* label, + uint16_t labelSz, uint16_t* outSz); + +/** + * @brief Blocking wrapper around the public-key export DMA request/response. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId Server key ID whose public key should be exported. + * @param[in] algo Algorithm selector (WH_KEY_ALGO_*). + * @param[in] keyAddr Client buffer that will receive the public DER. + * @param[in] keySz Size of the keyAddr buffer in bytes. + * @param[out] label Optional buffer to receive the cached key's label. + * @param[in] labelSz Size of label buffer. + * @param[out] outSz Receives the number of public-DER bytes written. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, + uint16_t algo, const void* keyAddr, + uint16_t keySz, uint8_t* label, + uint16_t labelSz, uint16_t* outSz); #endif /* WOLFHSM_CFG_DMA */ /** diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index ea0c8156e..5837afc5a 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -150,6 +150,32 @@ int wh_Client_Curve25519ImportKey(whClientContext* ctx, curve25519_key* key, int wh_Client_Curve25519ExportKey(whClientContext* ctx, whKeyId keyId, curve25519_key* key, uint16_t label_len, uint8_t* label); +/** + * @brief Exports only the public part of a cached Curve25519 key. + * + * Instructs the server to emit only the public portion of a cached + * Curve25519 key as SubjectPublicKeyInfo DER. The private scalar stays + * inside the HSM. The decoded key will have only the public part set. + * + * The NONEXPORTABLE key flag does NOT block this call because public + * material is non-sensitive. The caller is responsible for initializing + * key (e.g. wc_curve25519_init_ex) prior to calling this function. + * + * @param[in] ctx Pointer to the wolfHSM client context. + * @param[in] keyId Server key ID whose public key should be exported. Must + * not be WH_KEYID_ERASED. + * @param[in,out] key Pointer to a caller-initialized curve25519_key. On + * success, the public portion is populated. + * @param[in] label_len Size of the optional label buffer in bytes. Values + * larger than WH_NVM_LABEL_LEN are truncated. Set to + * 0 if label is not needed. + * @param[out] label Optional buffer to receive the key's label. May be NULL. + * @return int Returns 0 on success or a negative wolfHSM/wolfCrypt error + * code on failure (e.g. WH_ERROR_NOTFOUND, WH_ERROR_BADARGS). + */ +int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + curve25519_key* key, uint16_t label_len, uint8_t* label); + /** * @brief Generate a Curve25519 key in the server key cache * @@ -248,6 +274,34 @@ int wh_Client_EccExportKey(whClientContext* ctx, whKeyId keyId, ecc_key* key, uint16_t label_len, uint8_t* label); +/** + * @brief Exports only the public part of a cached ECC key. + * + * Instructs the server to emit only the public portion (SubjectPublicKeyInfo + * DER with curve parameters) of a cached ECC key. The private scalar stays + * inside the HSM. The decoded key is written into the caller-initialized + * ecc_key struct and will report type == ECC_PUBLICKEY. + * + * The NONEXPORTABLE key flag does NOT block this call because public + * material is non-sensitive. The caller is responsible for initializing + * key (e.g. wc_ecc_init_ex) prior to calling this function. + * + * @param[in] ctx Pointer to the wolfHSM client context. + * @param[in] keyId Server key ID whose public key should be exported. Must + * not be WH_KEYID_ERASED. + * @param[in,out] key Pointer to a caller-initialized ecc_key. On success, + * the public point is populated and key->type is set + * to ECC_PUBLICKEY. + * @param[in] label_len Size of the optional label buffer in bytes. Values + * larger than WH_NVM_LABEL_LEN are truncated. Set to + * 0 if label is not needed. + * @param[out] label Optional buffer to receive the key's label. May be NULL. + * @return int Returns 0 on success or a negative wolfHSM/wolfCrypt error + * code on failure (e.g. WH_ERROR_NOTFOUND, WH_ERROR_BADARGS). + */ +int wh_Client_EccExportPublicKey(whClientContext* ctx, whKeyId keyId, + ecc_key* key, uint16_t label_len, uint8_t* label); + /* TODO: Server creates and exports a key, without caching */ int wh_Client_EccMakeExportKey(whClientContext* ctx, int size, int curveId, ecc_key* key); @@ -303,6 +357,34 @@ int wh_Client_Ed25519ExportKey(whClientContext* ctx, whKeyId keyId, ed25519_key* key, uint16_t label_len, uint8_t* label); +/** + * @brief Exports only the public part of a cached Ed25519 key. + * + * Instructs the server to emit only the public portion of a cached Ed25519 + * key as SubjectPublicKeyInfo DER. The private seed stays inside the HSM. + * The decoded key will have pubKeySet == 1 and privKeySet == 0. + * + * The NONEXPORTABLE key flag does NOT block this call because public + * material is non-sensitive. The caller is responsible for initializing + * key (e.g. wc_ed25519_init_ex) prior to calling this function. + * + * @param[in] ctx Pointer to the wolfHSM client context. + * @param[in] keyId Server key ID whose public key should be exported. Must + * not be WH_KEYID_ERASED. + * @param[in,out] key Pointer to a caller-initialized ed25519_key. On + * success, only the public half is populated + * (pubKeySet == 1, privKeySet == 0). + * @param[in] label_len Size of the optional label buffer in bytes. Values + * larger than WH_NVM_LABEL_LEN are truncated. Set to + * 0 if label is not needed. + * @param[out] label Optional buffer to receive the key's label. May be NULL. + * @return int Returns 0 on success or a negative wolfHSM/wolfCrypt error + * code on failure (e.g. WH_ERROR_NOTFOUND, WH_ERROR_BADARGS). + */ +int wh_Client_Ed25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, + ed25519_key* key, uint16_t label_len, + uint8_t* label); + /** * @brief Create a new Ed25519 key on the server and export it without caching. */ @@ -418,6 +500,37 @@ int wh_Client_RsaImportKey(whClientContext* ctx, const RsaKey* key, int wh_Client_RsaExportKey(whClientContext* ctx, whKeyId keyId, RsaKey* key, uint32_t label_len, uint8_t* label); +/** + * @brief Exports only the public part of a cached RSA key. + * + * Unlike wh_Client_RsaExportKey(), which returns the full cached key + * (including private material), this function instructs the server to emit + * only the public portion as a PKCS#1 DER blob. The private key stays + * inside the HSM. The decoded key is written into the caller-initialized + * RsaKey struct and will report type == RSA_PUBLIC. + * + * The NONEXPORTABLE key flag does NOT block this call because public + * material is non-sensitive. + * + * The caller is responsible for initializing key (e.g. wc_InitRsaKey_ex) + * prior to calling this function. + * + * @param[in] ctx Pointer to the wolfHSM client context. + * @param[in] keyId Server key ID whose public key should be exported. Must + * not be WH_KEYID_ERASED. + * @param[in,out] key Pointer to a caller-initialized RsaKey. On success, + * the modulus and public exponent are populated and + * key->type is set to RSA_PUBLIC. + * @param[in] label_len Size of the optional label buffer in bytes. Values + * larger than WH_NVM_LABEL_LEN are truncated. Set to + * 0 if label is not needed. + * @param[out] label Optional buffer to receive the key's label. May be NULL. + * @return int Returns 0 on success or a negative wolfHSM/wolfCrypt error + * code on failure (e.g. WH_ERROR_NOTFOUND, WH_ERROR_BADARGS). + */ +int wh_Client_RsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + RsaKey* key, uint32_t label_len, uint8_t* label); + /* Generate an RSA key on the server and export it inta an RSA struct */ int wh_Client_RsaMakeExportKey(whClientContext* ctx, uint32_t size, uint32_t e, RsaKey* rsa); @@ -1384,6 +1497,35 @@ int wh_Client_MlDsaImportKey(whClientContext* ctx, MlDsaKey* key, int wh_Client_MlDsaExportKey(whClientContext* ctx, whKeyId keyId, MlDsaKey* key, uint16_t label_len, uint8_t* label); +/** + * @brief Exports only the public part of a cached ML-DSA key. + * + * Instructs the server to emit only the public portion of a cached ML-DSA + * key as SubjectPublicKeyInfo DER. The private key stays inside the HSM. + * The decoded key will have pubKeySet == 1 and prvKeySet == 0. + * + * The NONEXPORTABLE key flag does NOT block this call because public + * material is non-sensitive. The caller is responsible for initializing + * key (e.g. wc_MlDsaKey_Init) and, if required, setting the ML-DSA + * parameter level via wc_MlDsaKey_SetParams prior to calling this function. + * + * @param[in] ctx Pointer to the wolfHSM client context. + * @param[in] keyId Server key ID whose public key should be exported. Must + * not be WH_KEYID_ERASED. + * @param[in,out] key Pointer to a caller-initialized MlDsaKey. On success, + * only the public half is populated + * (pubKeySet == 1, prvKeySet == 0). + * @param[in] label_len Size of the optional label buffer in bytes. Values + * larger than WH_NVM_LABEL_LEN are truncated. Set to + * 0 if label is not needed. + * @param[out] label Optional buffer to receive the key's label. May be NULL. + * @return int Returns 0 on success or a negative wolfHSM/wolfCrypt error + * code on failure (e.g. WH_ERROR_NOTFOUND, WH_ERROR_BADARGS). + */ +int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, + uint8_t* label); + /** * @brief Generate a new ML-DSA key pair and export the public key. * @@ -1512,6 +1654,31 @@ int wh_Client_MlDsaExportKeyDma(whClientContext* ctx, whKeyId keyId, MlDsaKey* key, uint16_t label_len, uint8_t* label); +/** + * @brief Export only the public part of a cached ML-DSA key using DMA. + * + * DMA counterpart to wh_Client_MlDsaExportPublicKey. The server emits the + * public-only DER and DMAs it directly into a client-side staging buffer; + * the wrapper then deserializes it into the caller-provided MlDsaKey. + * + * The NONEXPORTABLE key flag does NOT block this call because public + * material is non-sensitive. The caller is responsible for initializing + * key (e.g. wc_MlDsaKey_Init + wc_MlDsaKey_SetParams) prior to calling. + * + * @param[in] ctx Pointer to the wolfHSM client context. + * @param[in] keyId Server key ID whose public key should be exported. Must + * not be WH_KEYID_ERASED. + * @param[in,out] key Pointer to a caller-initialized MlDsaKey. On success, + * only the public half is populated + * (pubKeySet == 1, prvKeySet == 0). + * @param[in] label_len Size of the optional label buffer in bytes. + * @param[out] label Optional buffer to receive the key's label. May be NULL. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + MlDsaKey* key, uint16_t label_len, + uint8_t* label); + /** * @brief Generate a new ML-DSA key pair and export it using DMA. * diff --git a/wolfhsm/wh_common.h b/wolfhsm/wh_common.h index 2cfe3e5ea..dded25c6b 100644 --- a/wolfhsm/wh_common.h +++ b/wolfhsm/wh_common.h @@ -145,4 +145,16 @@ enum WH_CRYPTO_AFFINITY_ENUM { WH_CRYPTO_AFFINITY_SW = 1, }; +/* Public-key algorithm selector used by APIs that must interpret a cached + * key's DER contents (e.g. WH_KEY_EXPORT_PUBLIC), since NVM metadata does + * not carry an algorithm type. Values are on-wire, append-only. */ +enum WH_KEY_ALGO_ENUM { + WH_KEY_ALGO_NONE = 0, + WH_KEY_ALGO_RSA = 1, + WH_KEY_ALGO_ECC = 2, + WH_KEY_ALGO_CURVE25519 = 3, + WH_KEY_ALGO_ED25519 = 4, + WH_KEY_ALGO_MLDSA = 5, +}; + #endif /* !WOLFHSM_WH_COMMON_H_ */ diff --git a/wolfhsm/wh_message.h b/wolfhsm/wh_message.h index 26aeda9b7..c562c877f 100644 --- a/wolfhsm/wh_message.h +++ b/wolfhsm/wh_message.h @@ -70,6 +70,8 @@ enum WH_KEY_ENUM { WH_KEY_KEYUNWRAPCACHE, WH_KEY_DATAWRAP, WH_KEY_DATAUNWRAP, + WH_KEY_EXPORT_PUBLIC, + WH_KEY_EXPORT_PUBLIC_DMA, }; /* SHE actions */ diff --git a/wolfhsm/wh_message_keystore.h b/wolfhsm/wh_message_keystore.h index d598a2959..95aea456f 100644 --- a/wolfhsm/wh_message_keystore.h +++ b/wolfhsm/wh_message_keystore.h @@ -131,6 +131,38 @@ int wh_MessageKeystore_TranslateExportResponse( uint16_t magic, const whMessageKeystore_ExportResponse* src, whMessageKeystore_ExportResponse* dest); +/* Key Export Public Request + * + * Requests the server to re-emit only the public portion of a cached + * public-key object as DER. The algo field selects how to interpret the + * cached bytes (see WH_KEY_ALGO_ENUM in wh_common.h) since NVM metadata + * does not record an algorithm type. */ +typedef struct { + uint16_t id; + uint16_t algo; + uint8_t WH_PAD[4]; +} whMessageKeystore_ExportPublicRequest; + +/* Key Export Public Response */ +typedef struct { + uint32_t rc; + uint32_t len; + uint8_t label[WH_NVM_LABEL_LEN]; + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t out[len] (public-only DER) + */ +} whMessageKeystore_ExportPublicResponse; + +/* Key Export Public translation functions */ +int wh_MessageKeystore_TranslateExportPublicRequest( + uint16_t magic, const whMessageKeystore_ExportPublicRequest* src, + whMessageKeystore_ExportPublicRequest* dest); + +int wh_MessageKeystore_TranslateExportPublicResponse( + uint16_t magic, const whMessageKeystore_ExportPublicResponse* src, + whMessageKeystore_ExportPublicResponse* dest); + /* Key Erase Request */ typedef struct { uint16_t id; @@ -244,6 +276,36 @@ int wh_MessageKeystore_TranslateExportDmaResponse( uint16_t magic, const whMessageKeystore_ExportDmaResponse* src, whMessageKeystore_ExportDmaResponse* dest); +/* Key Export Public DMA Request + * + * DMA counterpart to whMessageKeystore_ExportPublicRequest: re-emits only + * the public portion of a cached public-key object and DMAs it into the + * client-provided buffer. The algo selector identifies how to decode the + * cached bytes (see WH_KEY_ALGO_ENUM in wh_common.h). */ +typedef struct { + whMessageKeystore_DmaBuffer key; /* Client DER output buffer */ + uint16_t id; + uint16_t algo; + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ +} whMessageKeystore_ExportPublicDmaRequest; + +/* Key Export Public DMA Response */ +typedef struct { + whMessageKeystore_DmaAddrStatus dmaAddrStatus; + uint32_t rc; + uint32_t len; + uint8_t label[WH_NVM_LABEL_LEN]; +} whMessageKeystore_ExportPublicDmaResponse; + +/* Key Export Public DMA translation functions */ +int wh_MessageKeystore_TranslateExportPublicDmaRequest( + uint16_t magic, const whMessageKeystore_ExportPublicDmaRequest* src, + whMessageKeystore_ExportPublicDmaRequest* dest); + +int wh_MessageKeystore_TranslateExportPublicDmaResponse( + uint16_t magic, const whMessageKeystore_ExportPublicDmaResponse* src, + whMessageKeystore_ExportPublicDmaResponse* dest); + /* Wrap Key Request */ typedef struct { uint16_t keySz; From 6459473beb88f65ff4e55688fbd5e4777f03104d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 28 Apr 2026 13:17:19 +0200 Subject: [PATCH 2/3] Increase Posix server big key cache buffer size for ML-DSA --- examples/posix/wh_posix_server/wolfhsm_cfg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/posix/wh_posix_server/wolfhsm_cfg.h b/examples/posix/wh_posix_server/wolfhsm_cfg.h index c1799d5b1..790d61779 100644 --- a/examples/posix/wh_posix_server/wolfhsm_cfg.h +++ b/examples/posix/wh_posix_server/wolfhsm_cfg.h @@ -40,7 +40,7 @@ #define WOLFHSM_CFG_NVM_OBJECT_COUNT 30 #define WOLFHSM_CFG_SERVER_KEYCACHE_COUNT 9 #define WOLFHSM_CFG_SERVER_KEYCACHE_SIZE 1024 -#define WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE 4096 +#define WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE 5120 #define WOLFHSM_CFG_SERVER_KEYCACHE_BIG_COUNT 5 #define WOLFHSM_CFG_SERVER_DMAADDR_COUNT 8 From 719669c61df83f0661b4ff736732611d0d8ef723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 28 Apr 2026 13:17:38 +0200 Subject: [PATCH 3/3] Address review comments --- src/wh_client.c | 16 +- src/wh_server_keystore.c | 426 +++++++++++++++++---------------------- wolfhsm/wh_client.h | 4 +- 3 files changed, 195 insertions(+), 251 deletions(-) diff --git a/src/wh_client.c b/src/wh_client.c index db8e85b8b..5c22f58ae 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -974,12 +974,10 @@ int wh_Client_KeyExportPublicResponse(whClientContext* c, uint8_t* label, *outSz = resp->len; } if ((ret == WH_ERROR_OK) && (label != NULL)) { - if (labelSz > sizeof(resp->label)) { - memcpy(label, resp->label, WH_NVM_LABEL_LEN); - } - else { - memcpy(label, resp->label, labelSz); + if (labelSz > WH_NVM_LABEL_LEN) { + labelSz = WH_NVM_LABEL_LEN; } + memcpy(label, resp->label, labelSz); } } } @@ -1615,7 +1613,7 @@ int wh_Client_KeyExportDma(whClientContext* c, uint16_t keyId, } int wh_Client_KeyExportPublicDmaRequest(whClientContext* c, whKeyId keyId, - uint16_t algo, const void* keyAddr, + uint16_t algo, void* keyAddr, uint16_t keySz) { whMessageKeystore_ExportPublicDmaRequest* req = NULL; @@ -1663,9 +1661,7 @@ int wh_Client_KeyExportPublicDmaResponse(whClientContext* c, uint8_t* label, rc = wh_Client_RecvResponse(c, &resp_group, &resp_action, &resp_size, (uint8_t*)resp); if (rc == 0) { - if ((resp_group != WH_MESSAGE_GROUP_KEY) || - (resp_action != WH_KEY_EXPORT_PUBLIC_DMA) || - (resp_size != sizeof(*resp))) { + if (resp_size != sizeof(*resp)) { rc = WH_ERROR_ABORTED; } else { @@ -1687,7 +1683,7 @@ int wh_Client_KeyExportPublicDmaResponse(whClientContext* c, uint8_t* label, } int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, - uint16_t algo, const void* keyAddr, + uint16_t algo, void* keyAddr, uint16_t keySz, uint8_t* label, uint16_t labelSz, uint16_t* outSz) { diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index 0e448756c..92bfceeab 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -410,6 +410,137 @@ static int _MarkKeyCommitted(whKeyCacheContext* ctx, whKeyId keyId, return ret; } +#ifndef NO_RSA +static int _ExportRsaPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + RsaKey key[1]; + int pub_ret; + + ret = wc_InitRsaKey_ex(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_CacheExportRsaKey(server, keyId, key); + if (ret == 0) { + pub_ret = wc_RsaKeyToPublicDer(key, out, (word32)*outSz); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_FreeRsaKey(key); + } + return ret; +} +#endif + +#ifdef HAVE_ECC + +static int _ExportEccPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + ecc_key key[1]; + int pub_ret; + + ret = wc_ecc_init_ex(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_EccKeyCacheExport(server, keyId, key); + if (ret == 0) { + pub_ret = wc_EccPublicKeyToDer(key, out, (word32)*outSz, 1); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_ecc_free(key); + } + return ret; +} +#endif + +#ifdef HAVE_ED25519 +static int _ExportEd25519PublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + ed25519_key key[1]; + int pub_ret; + + ret = wc_ed25519_init_ex(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_CacheExportEd25519Key(server, keyId, key); + if (ret == 0) { + pub_ret = wc_Ed25519PublicKeyToDer(key, out, (word32)*outSz, 1); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_ed25519_free(key); + } + return ret; +} +#endif + +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) +static int _ExportMldsaPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + MlDsaKey key[1]; + int pub_ret; + + ret = wc_MlDsaKey_Init(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_MlDsaKeyCacheExport(server, keyId, key); + if (ret == 0) { + pub_ret = wc_MlDsaKey_PublicKeyToDer(key, out, (word32)*outSz, 1); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_MlDsaKey_Free(key); + } + return ret; +} +#endif + +#ifdef HAVE_CURVE25519 +static int _ExportCurve25519PublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + curve25519_key key[1]; + int pub_ret; + + ret = wc_curve25519_init_ex(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_CacheExportCurve25519Key(server, keyId, key); + if (ret == 0) { + pub_ret = wc_Curve25519PublicKeyToDer(key, out, (word32)*outSz, 1); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_curve25519_free(key); + } + return ret; +} +#endif + int wh_Server_KeystoreGetUniqueId(whServerContext* server, whNvmId* inout_id) { int ret = WH_ERROR_OK; @@ -1264,8 +1395,8 @@ static int _HandleKeyWrapRequest(whServerContext* server, } /* Translate the server key id passed in from the client */ - serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, - server->comm->client_id, + serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + server->comm->client_id, req->serverKeyId); /* Store the wrapped key in the response data */ @@ -1331,8 +1462,8 @@ static int _HandleKeyUnwrapAndExportRequest( wrappedKey = reqData; /* Translate the server key id passed in from the client */ - serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, - server->comm->client_id, + serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + server->comm->client_id, req->serverKeyId); /* Ensure the cipher type in the response matches the request */ @@ -1451,8 +1582,8 @@ static int _HandleKeyUnwrapAndCacheRequest( wrappedKey = reqData; /* Translate the server key id passed in from the client */ - serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, - server->comm->client_id, + serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + server->comm->client_id, req->serverKeyId); /* Ensure the cipher type in the response matches the request */ @@ -1562,8 +1693,8 @@ static int _HandleDataWrapRequest(whServerContext* server, memcpy(data, reqData, req->dataSz); /* Translate the server key id passed in from the client */ - serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, - server->comm->client_id, + serverKeyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + server->comm->client_id, req->serverKeyId); /* Ensure the cipher type in the response matches the request */ @@ -1888,131 +2019,43 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, if (ret == WH_ERROR_OK) { switch (req.algo) { #ifndef NO_RSA - case WH_KEY_ALGO_RSA: { - RsaKey rsa[1]; - int pub_ret; - ret = wc_InitRsaKey_ex(rsa, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Server_CacheExportRsaKey( - server, serverKeyId, rsa); - if (ret == 0) { - pub_ret = wc_RsaKeyToPublicDer( - rsa, stage, (word32)stageMax); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_FreeRsaKey(rsa); - } - } break; + case WH_KEY_ALGO_RSA: + ret = _ExportRsaPublicKey(server, serverKeyId, + stage, &stageMax); + break; #endif /* !NO_RSA */ #ifdef HAVE_ECC - case WH_KEY_ALGO_ECC: { - ecc_key ecc[1]; - int pub_ret; - ret = wc_ecc_init_ex(ecc, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Server_EccKeyCacheExport( - server, serverKeyId, ecc); - if (ret == 0) { - pub_ret = wc_EccPublicKeyToDer( - ecc, stage, (word32)stageMax, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_ecc_free(ecc); - } - } break; + case WH_KEY_ALGO_ECC: + ret = _ExportEccPublicKey(server, serverKeyId, + stage, &stageMax); + break; #endif /* HAVE_ECC */ #ifdef HAVE_ED25519 - case WH_KEY_ALGO_ED25519: { - ed25519_key ed[1]; - int pub_ret; - ret = wc_ed25519_init_ex(ed, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Server_CacheExportEd25519Key( - server, serverKeyId, ed); - if (ret == 0) { - pub_ret = wc_Ed25519PublicKeyToDer( - ed, stage, (word32)stageMax, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_ed25519_free(ed); - } - } break; + case WH_KEY_ALGO_ED25519: + ret = _ExportEd25519PublicKey(server, serverKeyId, + stage, &stageMax); + break; #endif /* HAVE_ED25519 */ #if defined(HAVE_DILITHIUM) && defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) - case WH_KEY_ALGO_MLDSA: { - MlDsaKey mldsa[1]; - int pub_ret; - ret = wc_MlDsaKey_Init(mldsa, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Server_MlDsaKeyCacheExport( - server, serverKeyId, mldsa); - if (ret == 0) { - pub_ret = wc_Dilithium_PublicKeyToDer( - mldsa, stage, (word32)stageMax, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_MlDsaKey_Free(mldsa); - } - } break; + case WH_KEY_ALGO_MLDSA: + ret = _ExportMldsaPublicKey(server, serverKeyId, + stage, &stageMax); + break; #endif /* HAVE_DILITHIUM && WOLFSSL_DILITHIUM_PUBLIC_KEY */ #ifdef HAVE_CURVE25519 - case WH_KEY_ALGO_CURVE25519: { - curve25519_key c25[1]; - int pub_ret; - ret = wc_curve25519_init_ex(c25, NULL, - INVALID_DEVID); - if (ret == 0) { - ret = wh_Server_CacheExportCurve25519Key( - server, serverKeyId, c25); - if (ret == 0) { - pub_ret = wc_Curve25519PublicKeyToDer( - c25, stage, (word32)stageMax, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_curve25519_free(c25); - } - } break; + case WH_KEY_ALGO_CURVE25519: + ret = _ExportCurve25519PublicKey(server, serverKeyId, + stage, &stageMax); + break; #endif /* HAVE_CURVE25519 */ default: ret = WH_ERROR_BADARGS; break; } } + if (ret == WH_ERROR_OK) { + der_len = stageMax; + } /* Confirm client buffer is big enough, then DMA. */ if (ret == WH_ERROR_OK) { @@ -2153,139 +2196,44 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, if (ret == WH_ERROR_OK) { switch (req.algo) { #ifndef NO_RSA - case WH_KEY_ALGO_RSA: { - RsaKey rsa[1]; - int pub_ret; - ret = wc_InitRsaKey_ex(rsa, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Crypto_RsaDeserializeKeyDer( - cacheMeta->len, cacheBuf, rsa); - if (ret == 0) { - pub_ret = wc_RsaKeyToPublicDer( - rsa, out, (word32)max_der); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_FreeRsaKey(rsa); - } - } break; + case WH_KEY_ALGO_RSA: + ret = _ExportRsaPublicKey(server, serverKeyId, + out, &max_der); + break; #endif /* !NO_RSA */ #ifdef HAVE_ECC - case WH_KEY_ALGO_ECC: { - ecc_key ecc[1]; - int pub_ret; - ret = wc_ecc_init_ex(ecc, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Crypto_EccDeserializeKeyDer( - cacheBuf, cacheMeta->len, ecc); - if (ret == 0) { - /* Include curve parameters (last arg 1) - * so the client can deserialize without - * external context. */ - pub_ret = wc_EccPublicKeyToDer( - ecc, out, (word32)max_der, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_ecc_free(ecc); - } - } break; + case WH_KEY_ALGO_ECC: + ret = _ExportEccPublicKey(server, serverKeyId, + out, &max_der); + break; #endif /* HAVE_ECC */ #ifdef HAVE_ED25519 - case WH_KEY_ALGO_ED25519: { - ed25519_key ed[1]; - int pub_ret; - ret = wc_ed25519_init_ex(ed, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Crypto_Ed25519DeserializeKeyDer( - cacheBuf, cacheMeta->len, ed); - if (ret == 0) { - pub_ret = wc_Ed25519PublicKeyToDer( - ed, out, (word32)max_der, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_ed25519_free(ed); - } - } break; + case WH_KEY_ALGO_ED25519: + ret = _ExportEd25519PublicKey(server, serverKeyId, + out, &max_der); + break; #endif /* HAVE_ED25519 */ - #if defined(HAVE_DILITHIUM) && defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) - case WH_KEY_ALGO_MLDSA: { - MlDsaKey mldsa[1]; - int pub_ret; - ret = wc_MlDsaKey_Init(mldsa, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wh_Crypto_MlDsaDeserializeKeyDer( - cacheBuf, cacheMeta->len, mldsa); - if (ret == 0) { - pub_ret = wc_Dilithium_PublicKeyToDer( - mldsa, out, (word32)max_der, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_MlDsaKey_Free(mldsa); - } - } break; + #if defined(HAVE_DILITHIUM) && \ + defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) + case WH_KEY_ALGO_MLDSA: + ret = _ExportMldsaPublicKey(server, serverKeyId, + out, &max_der); + break; #endif /* HAVE_DILITHIUM && WOLFSSL_DILITHIUM_PUBLIC_KEY */ #ifdef HAVE_CURVE25519 - case WH_KEY_ALGO_CURVE25519: { - curve25519_key c25[1]; - int pub_ret; - - ret = wc_curve25519_init_ex(c25, NULL, - INVALID_DEVID); - if (ret == 0) { - ret = wh_Crypto_Curve25519DeserializeKey( - cacheBuf, cacheMeta->len, c25); - if (ret == 0) { - /* withAlg=1 wraps in SubjectPublicKeyInfo - * so the client's - * wh_Crypto_Curve25519DeserializeKey can - * parse the result. */ - pub_ret = wc_Curve25519PublicKeyToDer( - c25, out, (word32)max_der, 1); - if (pub_ret > 0) { - der_len = (uint16_t)pub_ret; - } - else { - ret = (pub_ret == 0) - ? WH_ERROR_ABORTED - : pub_ret; - } - } - wc_curve25519_free(c25); - } - } break; + case WH_KEY_ALGO_CURVE25519: + ret = _ExportCurve25519PublicKey(server, + serverKeyId, out, &max_der); + break; #endif /* HAVE_CURVE25519 */ default: ret = WH_ERROR_BADARGS; break; } } + if (ret == WH_ERROR_OK) { + der_len = max_der; + } /* Only populate the label on full success. On any failure * resp.label stays zeroed (from resp = {0}) so clients cannot diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index cb685b362..4c58abf98 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -958,7 +958,7 @@ int wh_Client_KeyExportDma(whClientContext* c, uint16_t keyId, * @return int Returns 0 on success, or a negative error code on failure. */ int wh_Client_KeyExportPublicDmaRequest(whClientContext* c, whKeyId keyId, - uint16_t algo, const void* keyAddr, + uint16_t algo, void* keyAddr, uint16_t keySz); /** @@ -989,7 +989,7 @@ int wh_Client_KeyExportPublicDmaResponse(whClientContext* c, uint8_t* label, * @return int Returns 0 on success, or a negative error code on failure. */ int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, - uint16_t algo, const void* keyAddr, + uint16_t algo, void* keyAddr, uint16_t keySz, uint8_t* label, uint16_t labelSz, uint16_t* outSz); #endif /* WOLFHSM_CFG_DMA */