From de1579180eba0fc15ab02efaee50fac2074ef255 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 22 May 2026 13:37:03 -0700 Subject: [PATCH 01/12] Add support for LMS and XMSS --- src/wh_client_crypto.c | 772 +++++++++++++++ src/wh_client_cryptocb.c | 311 ++++++ src/wh_crypto.c | 285 ++++++ src/wh_message_crypto.c | 179 ++++ src/wh_server_crypto.c | 1394 +++++++++++++++++++++++++-- test/config/user_settings.h | 8 + test/wh_test_check_struct_padding.c | 8 + test/wh_test_crypto.c | 319 ++++++ test/wh_test_wolfcrypt_test.c | 1 + wolfhsm/wh_client_crypto.h | 71 ++ wolfhsm/wh_crypto.h | 43 + wolfhsm/wh_message_crypto.h | 117 +++ wolfhsm/wh_server_crypto.h | 22 + 13 files changed, 3461 insertions(+), 69 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index e5cefb5d6..b0e3df638 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -56,6 +56,12 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#if defined(WOLFSSL_HAVE_LMS) +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif +#if defined(WOLFSSL_HAVE_XMSS) +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif #include "wolfssl/wolfcrypt/sha256.h" #include "wolfssl/wolfcrypt/sha512.h" #endif @@ -10403,4 +10409,770 @@ int wh_Client_MlKemDecapsulateDma(whClientContext* ctx, MlKemKey* key, #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +#ifdef WOLFHSM_CFG_DMA + +#ifdef WOLFSSL_HAVE_LMS + +int wh_Client_LmsSetKeyId(LmsKey* key, whKeyId keyId) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + key->devCtx = WH_KEYID_TO_DEVCTX(keyId); + return WH_ERROR_OK; +} + +int wh_Client_LmsGetKeyId(LmsKey* key, whKeyId* outId) +{ + if (key == NULL || outId == NULL) { + return WH_ERROR_BADARGS; + } + *outId = WH_DEVCTX_TO_KEYID(key->devCtx); + return WH_ERROR_OK; +} + +int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* req; + whMessageCrypto_PqcStatefulSigKeyGenDmaResponse* res; + word32 pubLen32 = 0; + uintptr_t pubAddr = 0; + + if ((ctx == NULL) || (key == NULL) || (key->params == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_LmsKey_GetPubLen(key, &pubLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigKeyGenDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN, + WC_PQC_STATEFUL_SIG_TYPE_LMS, ctx->cryptoAffinity); + + if (inout_key_id != NULL) { + key_id = *inout_key_id; + } + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + memset(req, 0, sizeof(*req)); + req->flags = flags; + req->keyId = key_id; + req->access = WH_NVM_ACCESS_ANY; + req->lmsLevels = key->params->levels; + req->lmsHeight = key->params->height; + req->lmsWinternitz = key->params->width; + req->pub.sz = pubLen32; + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)key->pub, (void**)&pubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->pub.addr = (uint64_t)(uintptr_t)pubAddr; + } + + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); + req->labelSize = label_len; + } + + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)key->pub, (void**)&pubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN, + (uint8_t**)&res); + if (ret >= 0) { + key_id = (whKeyId)res->keyId; + if (inout_key_id != NULL) { + *inout_key_id = key_id; + } + wh_Client_LmsSetKeyId(key, key_id); + } + } + } + + return ret; +} + +int wh_Client_LmsMakeExportKeyDma(whClientContext* ctx, LmsKey* key) +{ + return wh_Client_LmsMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, + NULL); +} + +int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, + byte* sig, word32* sigSz, LmsKey* key) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigSignDmaRequest* req; + whMessageCrypto_PqcStatefulSigSignDmaResponse* res; + uintptr_t msgAddr = 0; + uintptr_t sigAddr = 0; + whKeyId key_id; + word32 sigCap; + + if ((ctx == NULL) || (key == NULL) || (msg == NULL) || (sig == NULL) || + (sigSz == NULL)) { + return WH_ERROR_BADARGS; + } + + sigCap = *sigSz; + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + if (WH_KEYID_ISERASED(key_id)) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigSignDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN, + WC_PQC_STATEFUL_SIG_TYPE_LMS, ctx->cryptoAffinity); + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + memset(req, 0, sizeof(*req)); + req->keyId = key_id; + req->options = 0; + req->msg.sz = msgSz; + req->sig.sz = sigCap; + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->msg.addr = (uint64_t)(uintptr_t)msgAddr; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigCap, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + } + if (ret == WH_ERROR_OK) { + req->sig.addr = (uint64_t)(uintptr_t)sigAddr; + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigCap, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN, + (uint8_t**)&res); + if (ret >= 0) { + if (res->sigLen > sigCap) { + ret = WH_ERROR_BADARGS; + } + else { + *sigSz = res->sigLen; + ret = WH_ERROR_OK; + } + } + } + } + + return ret; +} + +int wh_Client_LmsVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, + const byte* msg, word32 msgSz, int* res, LmsKey* key) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigVerifyDmaRequest* req; + whMessageCrypto_PqcStatefulSigVerifyDmaResponse* resp; + uintptr_t sigAddr = 0; + uintptr_t msgAddr = 0; + whKeyId key_id; + + if ((ctx == NULL) || (key == NULL) || (sig == NULL) || (msg == NULL) || + (res == NULL)) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + if (WH_KEYID_ISERASED(key_id)) { + /* No HSM-resident key; let wolfCrypt fall through to software verify + * using the client-side public key. */ + return WH_ERROR_NOTIMPL; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigVerifyDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY, + WC_PQC_STATEFUL_SIG_TYPE_LMS, ctx->cryptoAffinity); + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + memset(req, 0, sizeof(*req)); + req->keyId = key_id; + req->sig.sz = sigSz; + req->msg.sz = msgSz; + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->sig.addr = (uint64_t)(uintptr_t)sigAddr; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + } + if (ret == WH_ERROR_OK) { + req->msg.addr = (uint64_t)(uintptr_t)msgAddr; + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY, + (uint8_t**)&resp); + if (ret >= 0) { + *res = (int)resp->res; + ret = WH_ERROR_OK; + } + } + } + + return ret; +} + +int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, + word32* sigsLeft) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* req; + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* res; + whKeyId key_id; + + if ((ctx == NULL) || (key == NULL) || (sigsLeft == NULL)) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + if (WH_KEYID_ISERASED(key_id)) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT, + WC_PQC_STATEFUL_SIG_TYPE_LMS, ctx->cryptoAffinity); + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + memset(req, 0, sizeof(*req)); + req->keyId = key_id; + + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT, + (uint8_t**)&res); + if (ret >= 0) { + *sigsLeft = res->sigsLeft; + ret = WH_ERROR_OK; + } + } + } + + return ret; +} + +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS + +int wh_Client_XmssSetKeyId(XmssKey* key, whKeyId keyId) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + key->devCtx = WH_KEYID_TO_DEVCTX(keyId); + return WH_ERROR_OK; +} + +int wh_Client_XmssGetKeyId(XmssKey* key, whKeyId* outId) +{ + if (key == NULL || outId == NULL) { + return WH_ERROR_BADARGS; + } + *outId = WH_DEVCTX_TO_KEYID(key->devCtx); + return WH_ERROR_OK; +} + +/* The XMSS implementations mirror the LMS ones; the only differences are the + * subType passed to _createCryptoRequestWithSubtype and the key field names + * (key->pk instead of key->pub, key->params is XmssParams). */ +int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* req; + whMessageCrypto_PqcStatefulSigKeyGenDmaResponse* res; + word32 pubLen32 = 0; + uintptr_t pubAddr = 0; + + if ((ctx == NULL) || (key == NULL) || (key->params == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_XmssKey_GetPubLen(key, &pubLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigKeyGenDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN, + WC_PQC_STATEFUL_SIG_TYPE_XMSS, ctx->cryptoAffinity); + + if (inout_key_id != NULL) { + key_id = *inout_key_id; + } + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + memset(req, 0, sizeof(*req)); + req->flags = flags; + req->keyId = key_id; + req->access = WH_NVM_ACCESS_ANY; + req->pub.sz = pubLen32; + + { + const char* paramStr = NULL; + ret = wc_XmssKey_GetParamStr(key, ¶mStr); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + if (XSTRLEN(paramStr) >= sizeof(req->xmssParamStr)) { + return WH_ERROR_BADARGS; + } + XSTRNCPY(req->xmssParamStr, paramStr, sizeof(req->xmssParamStr)); + req->xmssParamStr[sizeof(req->xmssParamStr) - 1] = '\0'; + } + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)key->pk, (void**)&pubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->pub.addr = (uint64_t)(uintptr_t)pubAddr; + } + + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); + req->labelSize = label_len; + } + + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)key->pk, (void**)&pubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN, + (uint8_t**)&res); + if (ret >= 0) { + key_id = (whKeyId)res->keyId; + if (inout_key_id != NULL) { + *inout_key_id = key_id; + } + wh_Client_XmssSetKeyId(key, key_id); + } + } + } + + return ret; +} + +int wh_Client_XmssMakeExportKeyDma(whClientContext* ctx, XmssKey* key) +{ + return wh_Client_XmssMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, + NULL); +} + +int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, + byte* sig, word32* sigSz, XmssKey* key) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigSignDmaRequest* req; + whMessageCrypto_PqcStatefulSigSignDmaResponse* res; + uintptr_t msgAddr = 0; + uintptr_t sigAddr = 0; + whKeyId key_id; + word32 sigCap; + + if ((ctx == NULL) || (key == NULL) || (msg == NULL) || (sig == NULL) || + (sigSz == NULL)) { + return WH_ERROR_BADARGS; + } + + sigCap = *sigSz; + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + if (WH_KEYID_ISERASED(key_id)) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigSignDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN, + WC_PQC_STATEFUL_SIG_TYPE_XMSS, ctx->cryptoAffinity); + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + memset(req, 0, sizeof(*req)); + req->keyId = key_id; + req->options = 0; + req->msg.sz = msgSz; + req->sig.sz = sigCap; + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->msg.addr = (uint64_t)(uintptr_t)msgAddr; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigCap, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + } + if (ret == WH_ERROR_OK) { + req->sig.addr = (uint64_t)(uintptr_t)sigAddr; + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigCap, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN, + (uint8_t**)&res); + if (ret >= 0) { + if (res->sigLen > sigCap) { + ret = WH_ERROR_BADARGS; + } + else { + *sigSz = res->sigLen; + ret = WH_ERROR_OK; + } + } + } + } + + return ret; +} + +int wh_Client_XmssVerifyDma(whClientContext* ctx, const byte* sig, + word32 sigSz, const byte* msg, word32 msgSz, + int* res, XmssKey* key) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigVerifyDmaRequest* req; + whMessageCrypto_PqcStatefulSigVerifyDmaResponse* resp; + uintptr_t sigAddr = 0; + uintptr_t msgAddr = 0; + whKeyId key_id; + + if ((ctx == NULL) || (key == NULL) || (sig == NULL) || (msg == NULL) || + (res == NULL)) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + if (WH_KEYID_ISERASED(key_id)) { + /* No HSM-resident key; let wolfCrypt fall through to software verify + * using the client-side public key. */ + return WH_ERROR_NOTIMPL; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigVerifyDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY, + WC_PQC_STATEFUL_SIG_TYPE_XMSS, ctx->cryptoAffinity); + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + memset(req, 0, sizeof(*req)); + req->keyId = key_id; + req->sig.sz = sigSz; + req->msg.sz = msgSz; + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->sig.addr = (uint64_t)(uintptr_t)sigAddr; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + } + if (ret == WH_ERROR_OK) { + req->msg.addr = (uint64_t)(uintptr_t)msgAddr; + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sigSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY, + (uint8_t**)&resp); + if (ret >= 0) { + *res = (int)resp->res; + ret = WH_ERROR_OK; + } + } + } + + return ret; +} + +int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, + word32* sigsLeft) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* req; + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* res; + whKeyId key_id; + + if ((ctx == NULL) || (key == NULL) || (sigsLeft == NULL)) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + if (WH_KEYID_ISERASED(key_id)) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT, + WC_PQC_STATEFUL_SIG_TYPE_XMSS, ctx->cryptoAffinity); + + { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint16_t req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + memset(req, 0, sizeof(*req)); + req->keyId = key_id; + + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &req_len, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, + WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT, + (uint8_t**)&res); + if (ret >= 0) { + *sigsLeft = res->sigsLeft; + ret = WH_ERROR_OK; + } + } + } + + return ret; +} + +#endif /* WOLFSSL_HAVE_XMSS */ + +#endif /* WOLFHSM_CFG_DMA */ +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #endif /* !WOLFHSM_CFG_NO_CRYPTO && WOLFHSM_CFG_ENABLE_CLIENT */ diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 11d1d0269..92155cf75 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -48,6 +48,12 @@ #include "wolfssl/wolfcrypt/sha256.h" #include "wolfssl/wolfcrypt/sha512.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#if defined(WOLFSSL_HAVE_LMS) +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif +#if defined(WOLFSSL_HAVE_XMSS) +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif #include "wolfhsm/wh_crypto.h" #include "wolfhsm/wh_client_crypto.h" @@ -63,6 +69,17 @@ static int _handlePqcEncaps(whClientContext* ctx, wc_CryptoInfo* info, static int _handlePqcDecaps(whClientContext* ctx, wc_CryptoInfo* info, int useDma); #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +static int _handlePqcStatefulSigKeyGen(whClientContext* ctx, + wc_CryptoInfo* info, int useDma); +static int _handlePqcStatefulSigSign(whClientContext* ctx, wc_CryptoInfo* info, + int useDma); +static int _handlePqcStatefulSigVerify(whClientContext* ctx, + wc_CryptoInfo* info, int useDma); +static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, + wc_CryptoInfo* info, int useDma); +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, @@ -487,6 +504,24 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) break; #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + case WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN: + ret = _handlePqcStatefulSigKeyGen(ctx, info, 0); + break; + + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN: + ret = _handlePqcStatefulSigSign(ctx, info, 0); + break; + + case WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY: + ret = _handlePqcStatefulSigVerify(ctx, info, 0); + break; + + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT: + ret = _handlePqcStatefulSigSigsLeft(ctx, info, 0); + break; + +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) case WC_PK_TYPE_PQC_SIG_KEYGEN: @@ -827,6 +862,268 @@ static int _handlePqcDecaps(whClientContext* ctx, wc_CryptoInfo* info, } #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +static int _handlePqcStatefulSigKeyGen(whClientContext* ctx, + wc_CryptoInfo* info, int useDma) +{ + int ret = CRYPTOCB_UNAVAILABLE; + int type = info->pk.pqc_stateful_sig_kg.type; + +#ifndef WOLFHSM_CFG_DMA + (void)ctx; + if (useDma) { + return WC_HW_E; + } +#endif + + switch (type) { +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + case WC_PQC_STATEFUL_SIG_TYPE_LMS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_LmsMakeExportKeyDma( + ctx, (LmsKey*)info->pk.pqc_stateful_sig_kg.key); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + /* Non-DMA transport not supported in v1; signatures exceed the + * default WOLFHSM_CFG_COMM_DATA_LEN. */ + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + case WC_PQC_STATEFUL_SIG_TYPE_XMSS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_XmssMakeExportKeyDma( + ctx, (XmssKey*)info->pk.pqc_stateful_sig_kg.key); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + if (ret == WH_ERROR_BADARGS) { + ret = BAD_FUNC_ARG; + } + else if (ret == WH_ERROR_NOTIMPL) { + ret = CRYPTOCB_UNAVAILABLE; + } + + return ret; +} + +static int _handlePqcStatefulSigSign(whClientContext* ctx, wc_CryptoInfo* info, + int useDma) +{ + int ret = CRYPTOCB_UNAVAILABLE; + int type = info->pk.pqc_stateful_sig_sign.type; + +#ifndef WOLFHSM_CFG_DMA + (void)ctx; + if (useDma) { + return WC_HW_E; + } +#endif + + switch (type) { +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + case WC_PQC_STATEFUL_SIG_TYPE_LMS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_LmsSignDma( + ctx, + info->pk.pqc_stateful_sig_sign.msg, + info->pk.pqc_stateful_sig_sign.msgSz, + info->pk.pqc_stateful_sig_sign.out, + info->pk.pqc_stateful_sig_sign.outSz, + (LmsKey*)info->pk.pqc_stateful_sig_sign.key); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + case WC_PQC_STATEFUL_SIG_TYPE_XMSS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_XmssSignDma( + ctx, + info->pk.pqc_stateful_sig_sign.msg, + info->pk.pqc_stateful_sig_sign.msgSz, + info->pk.pqc_stateful_sig_sign.out, + info->pk.pqc_stateful_sig_sign.outSz, + (XmssKey*)info->pk.pqc_stateful_sig_sign.key); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + if (ret == WH_ERROR_BADARGS) { + ret = BAD_FUNC_ARG; + } + else if (ret == WH_ERROR_NOTIMPL) { + ret = CRYPTOCB_UNAVAILABLE; + } + + return ret; +} + +static int _handlePqcStatefulSigVerify(whClientContext* ctx, + wc_CryptoInfo* info, int useDma) +{ + int ret = CRYPTOCB_UNAVAILABLE; + int type = info->pk.pqc_stateful_sig_verify.type; + +#ifndef WOLFHSM_CFG_DMA + (void)ctx; + if (useDma) { + return WC_HW_E; + } +#endif + + switch (type) { +#ifdef WOLFSSL_HAVE_LMS + case WC_PQC_STATEFUL_SIG_TYPE_LMS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_LmsVerifyDma( + ctx, + info->pk.pqc_stateful_sig_verify.sig, + info->pk.pqc_stateful_sig_verify.sigSz, + info->pk.pqc_stateful_sig_verify.msg, + info->pk.pqc_stateful_sig_verify.msgSz, + info->pk.pqc_stateful_sig_verify.res, + (LmsKey*)info->pk.pqc_stateful_sig_verify.key); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_LMS */ +#ifdef WOLFSSL_HAVE_XMSS + case WC_PQC_STATEFUL_SIG_TYPE_XMSS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_XmssVerifyDma( + ctx, + info->pk.pqc_stateful_sig_verify.sig, + info->pk.pqc_stateful_sig_verify.sigSz, + info->pk.pqc_stateful_sig_verify.msg, + info->pk.pqc_stateful_sig_verify.msgSz, + info->pk.pqc_stateful_sig_verify.res, + (XmssKey*)info->pk.pqc_stateful_sig_verify.key); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_XMSS */ + + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + if (ret == WH_ERROR_BADARGS) { + ret = BAD_FUNC_ARG; + } + else if (ret == WH_ERROR_NOTIMPL) { + ret = CRYPTOCB_UNAVAILABLE; + } + + return ret; +} + +static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, + wc_CryptoInfo* info, int useDma) +{ + int ret = CRYPTOCB_UNAVAILABLE; + int type = info->pk.pqc_stateful_sig_sigs_left.type; + +#ifndef WOLFHSM_CFG_DMA + (void)ctx; + if (useDma) { + return WC_HW_E; + } +#endif + + switch (type) { +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + case WC_PQC_STATEFUL_SIG_TYPE_LMS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_LmsSigsLeftDma( + ctx, + (LmsKey*)info->pk.pqc_stateful_sig_sigs_left.key, + info->pk.pqc_stateful_sig_sigs_left.sigsLeft); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + case WC_PQC_STATEFUL_SIG_TYPE_XMSS: +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_XmssSigsLeftDma( + ctx, + (XmssKey*)info->pk.pqc_stateful_sig_sigs_left.key, + info->pk.pqc_stateful_sig_sigs_left.sigsLeft); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = CRYPTOCB_UNAVAILABLE; + } + break; +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + if (ret == WH_ERROR_BADARGS) { + ret = BAD_FUNC_ARG; + } + else if (ret == WH_ERROR_NOTIMPL) { + ret = CRYPTOCB_UNAVAILABLE; + } + + return ret; +} +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma) @@ -1102,6 +1399,20 @@ int wh_Client_CryptoCbDma(int devId, wc_CryptoInfo* info, void* inCtx) ret = _handlePqcDecaps(ctx, info, 1); break; #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + case WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN: + ret = _handlePqcStatefulSigKeyGen(ctx, info, 1); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN: + ret = _handlePqcStatefulSigSign(ctx, info, 1); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY: + ret = _handlePqcStatefulSigVerify(ctx, info, 1); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT: + ret = _handlePqcStatefulSigSigsLeft(ctx, info, 1); + break; +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) case WC_PK_TYPE_PQC_SIG_KEYGEN: ret = _handlePqcSigKeyGen(ctx, info, 1); diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 1f73bd1a5..7ad7647eb 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -45,6 +45,12 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#if defined(WOLFSSL_HAVE_LMS) +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif +#if defined(WOLFSSL_HAVE_XMSS) +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif #include "wolfssl/wolfcrypt/memory.h" #include "wolfhsm/wh_error.h" @@ -488,6 +494,285 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, } #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +/* Stateful hash-based signature key serialization helpers (LMS / XMSS). + * + * Slot blob layout: + * uint32_t magic; + * uint16_t pubLen; + * uint16_t privLen; + * uint16_t paramLen; + * uint16_t reserved; (must be 0) + * uint8_t paramDescriptor[paramLen]; + * uint8_t pub[pubLen]; + * uint8_t priv[privLen]; + * + * paramDescriptor encodes the parameter set: + * LMS : 3 bytes (levels, height, winternitz) - paramLen == 3 + * XMSS : NUL-terminated parameter string, paramLen == strlen+1 + * + * The blob is server-internal (NVM-stored), never traverses the wire, and uses + * native byte order. */ + +#define WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS 0x4C4D5301u /* 'LMS\1' */ +#define WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS 0x584D5301u /* 'XMS\1' */ + +static int _StatefulSigEncodeHeader(uint8_t* buffer, uint32_t magic, + uint16_t pubLen, uint16_t privLen, + uint16_t paramLen) +{ + uint16_t reserved = 0; + memcpy(buffer + 0, &magic, sizeof(magic)); + memcpy(buffer + 4, &pubLen, sizeof(pubLen)); + memcpy(buffer + 6, &privLen, sizeof(privLen)); + memcpy(buffer + 8, ¶mLen, sizeof(paramLen)); + memcpy(buffer + 10, &reserved, sizeof(reserved)); + return WH_ERROR_OK; +} + +static int _StatefulSigDecodeHeader(const uint8_t* buffer, uint16_t size, + uint32_t expectMagic, uint16_t* pubLen, + uint16_t* privLen, uint16_t* paramLen) +{ + uint32_t magic; + + if (size < WH_CRYPTO_STATEFUL_SIG_HEADER_SZ) { + return WH_ERROR_BADARGS; + } + memcpy(&magic, buffer + 0, sizeof(magic)); + if (magic != expectMagic) { + return WH_ERROR_BADARGS; + } + memcpy(pubLen, buffer + 4, sizeof(*pubLen)); + memcpy(privLen, buffer + 6, sizeof(*privLen)); + memcpy(paramLen, buffer + 8, sizeof(*paramLen)); + if ((uint32_t)WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + *paramLen + *pubLen + + *privLen > size) { + return WH_ERROR_BADARGS; + } + return WH_ERROR_OK; +} +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + +#ifdef WOLFSSL_HAVE_LMS +int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, + uint16_t* out_size) +{ + word32 pubLen32 = 0; + uint16_t pubLen; + uint16_t privLen; + uint16_t paramLen = 3; /* levels, height, winternitz */ + uint32_t totalLen; + int ret; + + if ((key == NULL) || (buffer == NULL) || (out_size == NULL) || + (key->params == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_LmsKey_GetPubLen(key, &pubLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + pubLen = (uint16_t)pubLen32; + privLen = (uint16_t)HSS_PRIVATE_KEY_LEN(key->params->hash_len); + + totalLen = (uint32_t)WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen + + privLen; + if (totalLen > max_size) { + return WH_ERROR_BUFFER_SIZE; + } + + (void)_StatefulSigEncodeHeader(buffer, + WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS, + pubLen, privLen, paramLen); + + /* paramDescriptor: levels, height, winternitz */ + buffer[WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 0] = key->params->levels; + buffer[WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 1] = key->params->height; + buffer[WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 2] = key->params->width; + + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen, + key->pub, pubLen); + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen, + key->priv_raw, privLen); + + *out_size = (uint16_t)totalLen; + return WH_ERROR_OK; +} + +int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, + LmsKey* key) +{ + uint16_t pubLen; + uint16_t privLen; + uint16_t paramLen; + word32 expectPubLen = 0; + int ret; + int levels; + int height; + int winternitz; + const uint8_t* p; + + if ((buffer == NULL) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = _StatefulSigDecodeHeader(buffer, size, + WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS, + &pubLen, &privLen, ¶mLen); + if (ret != WH_ERROR_OK) { + return ret; + } + if (paramLen != 3) { + return WH_ERROR_BADARGS; + } + + p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ; + levels = (int)p[0]; + height = (int)p[1]; + winternitz = (int)p[2]; + + ret = wc_LmsKey_SetParameters(key, levels, height, winternitz); + if (ret != 0) { + return ret; + } + + /* Sanity-check pub size against the bound parameter set */ + ret = wc_LmsKey_GetPubLen(key, &expectPubLen); + if ((ret != 0) || (expectPubLen != pubLen)) { + return WH_ERROR_BADARGS; + } + if (privLen != (uint16_t)HSS_PRIVATE_KEY_LEN(key->params->hash_len)) { + return WH_ERROR_BADARGS; + } + + p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; + memcpy(key->pub, p, pubLen); + p += pubLen; + memcpy(key->priv_raw, p, privLen); + + return WH_ERROR_OK; +} +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS +int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, + uint16_t max_size, uint8_t* buffer, + uint16_t* out_size) +{ + word32 pubLen32 = 0; + word32 privLen32 = 0; + uint16_t pubLen; + uint16_t privLen; + uint16_t paramLen; + uint32_t totalLen; + size_t strLen; + int ret; + + if ((key == NULL) || (paramStr == NULL) || (buffer == NULL) || + (out_size == NULL) || (key->params == NULL) || (key->sk == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_XmssKey_GetPubLen(key, &pubLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + ret = wc_XmssKey_GetPrivLen(key, &privLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + pubLen = (uint16_t)pubLen32; + privLen = (uint16_t)privLen32; + + strLen = strlen(paramStr); + if (strLen >= 0xFFFFu) { + return WH_ERROR_BADARGS; + } + paramLen = (uint16_t)(strLen + 1); /* include NUL */ + + totalLen = (uint32_t)WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen + + privLen; + if (totalLen > max_size) { + return WH_ERROR_BUFFER_SIZE; + } + + (void)_StatefulSigEncodeHeader(buffer, + WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS, + pubLen, privLen, paramLen); + + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ, paramStr, paramLen); + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen, + key->pk, pubLen); + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen, + key->sk, privLen); + + *out_size = (uint16_t)totalLen; + return WH_ERROR_OK; +} + +int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, + XmssKey* key) +{ + uint16_t pubLen; + uint16_t privLen; + uint16_t paramLen; + word32 expectPubLen = 0; + word32 expectPrivLen = 0; + int ret; + const char* paramStr; + const uint8_t* p; + + if ((buffer == NULL) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = _StatefulSigDecodeHeader(buffer, size, + WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS, + &pubLen, &privLen, ¶mLen); + if (ret != WH_ERROR_OK) { + return ret; + } + if (paramLen == 0) { + return WH_ERROR_BADARGS; + } + + /* paramDescriptor must be NUL-terminated and within paramLen */ + paramStr = (const char*)(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ); + if (paramStr[paramLen - 1] != '\0') { + return WH_ERROR_BADARGS; + } + + /* SetParamStr binds key->params; sk is allocated later by Reload via + * the read callback path (or directly if the caller wants to pre-load + * it). */ + ret = wc_XmssKey_SetParamStr(key, paramStr); + if (ret != 0) { + return ret; + } + + ret = wc_XmssKey_GetPubLen(key, &expectPubLen); + if ((ret != 0) || (expectPubLen != pubLen)) { + return WH_ERROR_BADARGS; + } + ret = wc_XmssKey_GetPrivLen(key, &expectPrivLen); + if ((ret != 0) || (expectPrivLen != privLen)) { + return WH_ERROR_BADARGS; + } + + p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; + memcpy(key->pk, p, pubLen); + /* The private key is left in the slot blob; downstream paths read it + * via the bridge ReadCb against the cached slot (sk is allocated by + * Reload, not by deserialize). */ + (void)privLen; + + return WH_ERROR_OK; +} +#endif /* WOLFSSL_HAVE_XMSS */ + + #ifdef WOLFSSL_CMAC void wh_Crypto_CmacAesSaveStateToMsg(whMessageCrypto_CmacAesState* state, const Cmac* cmac) diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index 3e5bafc27..205065c9a 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -1378,6 +1378,185 @@ int wh_MessageCrypto_TranslateMlKemDecapsDmaResponse( return 0; } +/* Stateful sig DMA Key Generation Request translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* src, + whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->pub, &dest->pub); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, access); + WH_T32(magic, dest, src, labelSize); + WH_T32(magic, dest, src, lmsLevels); + WH_T32(magic, dest, src, lmsHeight); + WH_T32(magic, dest, src, lmsWinternitz); + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + memcpy(dest->xmssParamStr, src->xmssParamStr, + sizeof(src->xmssParamStr)); + } + return 0; +} + +/* Stateful sig DMA Key Generation Response translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigKeyGenDmaResponse* src, + whMessageCrypto_PqcStatefulSigKeyGenDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, pubSize); + return 0; +} + +/* Stateful sig DMA Sign Request translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigSignDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSignDmaRequest* src, + whMessageCrypto_PqcStatefulSigSignDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->msg, &dest->msg); + if (ret != 0) { + return ret; + } + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->sig, &dest->sig); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, keyId); + return 0; +} + +/* Stateful sig DMA Sign Response translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigSignDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSignDmaResponse* src, + whMessageCrypto_PqcStatefulSigSignDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, sigLen); + return 0; +} + +/* Stateful sig DMA Verify Request translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigVerifyDmaRequest* src, + whMessageCrypto_PqcStatefulSigVerifyDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->sig, &dest->sig); + if (ret != 0) { + return ret; + } + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->msg, &dest->msg); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, keyId); + return 0; +} + +/* Stateful sig DMA Verify Response translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigVerifyDmaResponse* src, + whMessageCrypto_PqcStatefulSigVerifyDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, res); + return 0; +} + +/* Stateful sig DMA Signatures-Left Request translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* src, + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + WH_T32(magic, dest, src, keyId); + return 0; +} + +/* Stateful sig DMA Signatures-Left Response translation */ +int wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* src, + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + WH_T32(magic, dest, src, sigsLeft); + return 0; +} + /* Ed25519 DMA Sign Request translation */ int wh_MessageCrypto_TranslateEd25519SignDmaRequest( uint16_t magic, const whMessageCrypto_Ed25519SignDmaRequest* src, diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 788f91f53..2fbfbca42 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -44,6 +44,12 @@ #include "wolfssl/wolfcrypt/sha512.h" #include "wolfssl/wolfcrypt/cmac.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" +#if defined(WOLFSSL_HAVE_LMS) +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif +#if defined(WOLFSSL_HAVE_XMSS) +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif #include "wolfssl/wolfcrypt/wc_mlkem.h" #include "wolfssl/wolfcrypt/hmac.h" #include "wolfssl/wolfcrypt/kdf.h" @@ -879,6 +885,255 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, } #endif /* WOLFSSL_HAVE_MLKEM */ +#if (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) && \ + defined(WOLFHSM_CFG_DMA) +/* Stateful-key persistence bridge. + * + * wolfCrypt's wc_LmsKey_Sign and wc_XmssKey_Sign require write/read callbacks + * for the software path. We wire write_private_key directly to atomic NVM + * commit (wh_Nvm_AddObjectWithReclaim): wolfCrypt's contract is to advance + * the index, call write_cb, and only emit the signature if write_cb returned + * success. That gives us pre-commit-then-emit ordering for free — see + * doc/LMS_XMSS_CryptoCb.md and the plan for the crash-safety analysis. + * + * The bridge keeps a pointer into the server's cache slot blob (laid out by + * wh_Crypto_{Lms,Xmss}SerializeKey). Each write_cb invocation overwrites the + * priv region of the slot in place and re-commits the entire slot. */ +typedef struct whServerStatefulSigBridge { + whServerContext* server; + whKeyId keyId; + whNvmMetadata* meta; /* points at the cache slot's metadata */ + uint8_t* slotBuf; /* points at the cache slot's data buffer */ + uint16_t hdrSz; /* offset to priv region inside slotBuf */ + uint16_t pubLen; /* offset of priv = hdrSz + paramLen + pubLen */ + uint16_t paramLen; + uint16_t slotCapacity; +} whServerStatefulSigBridge; + +/* Compute the priv-region offset inside the slot blob from a bridge. */ +static uint16_t _StatefulBridgePrivOffset(const whServerStatefulSigBridge* b) +{ + return (uint16_t)(b->hdrSz + b->paramLen + b->pubLen); +} + +/* Update the slot blob's privLen field (header + 6 -> priv length). */ +static void _StatefulBridgeWritePrivLen(uint8_t* slotBuf, uint16_t privLen) +{ + /* See wh_crypto.c for layout: privLen is at offset +6. */ + memcpy(slotBuf + 6, &privLen, sizeof(privLen)); +} + +#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFHSM_CFG_DMA) +static int _LmsBridgeWriteCb(const byte* priv, word32 privSz, void* context) +{ + whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + uint16_t privOff; + uint32_t newLen; + int rc; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL) || + (b->meta == NULL)) { + return WC_LMS_RC_BAD_ARG; + } + + privOff = _StatefulBridgePrivOffset(b); + newLen = (uint32_t)privOff + privSz; + if (newLen > b->slotCapacity) { + return WC_LMS_RC_WRITE_FAIL; + } + + memcpy(b->slotBuf + privOff, priv, privSz); + _StatefulBridgeWritePrivLen(b->slotBuf, (uint16_t)privSz); + b->meta->len = (whNvmSize)newLen; + + /* Atomic dual-partition commit. Wolfcrypt aborts the sign if this + * returns anything other than _SAVED_TO_NV_MEMORY, so the signature + * never escapes for an un-persisted index. */ + rc = wh_Nvm_AddObjectWithReclaim(b->server->nvm, b->meta, b->meta->len, + b->slotBuf); + return (rc == WH_ERROR_OK) ? WC_LMS_RC_SAVED_TO_NV_MEMORY + : WC_LMS_RC_WRITE_FAIL; +} + +static int _LmsBridgeReadCb(byte* priv, word32 privSz, void* context) +{ + whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + uint16_t privOff; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_LMS_RC_BAD_ARG; + } + + privOff = _StatefulBridgePrivOffset(b); + if ((uint32_t)privOff + privSz > b->meta->len) { + return WC_LMS_RC_READ_FAIL; + } + + memcpy(priv, b->slotBuf + privOff, privSz); + return WC_LMS_RC_READ_TO_MEMORY; +} +#endif /* WOLFSSL_HAVE_LMS && WOLFHSM_CFG_DMA */ + +#if defined(WOLFSSL_HAVE_XMSS) && defined(WOLFHSM_CFG_DMA) +static enum wc_XmssRc _XmssBridgeWriteCb(const byte* priv, word32 privSz, + void* context) +{ + whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + uint16_t privOff; + uint32_t newLen; + int rc; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL) || + (b->meta == NULL)) { + return WC_XMSS_RC_BAD_ARG; + } + + privOff = _StatefulBridgePrivOffset(b); + newLen = (uint32_t)privOff + privSz; + if (newLen > b->slotCapacity) { + return WC_XMSS_RC_WRITE_FAIL; + } + + memcpy(b->slotBuf + privOff, priv, privSz); + _StatefulBridgeWritePrivLen(b->slotBuf, (uint16_t)privSz); + b->meta->len = (whNvmSize)newLen; + + rc = wh_Nvm_AddObjectWithReclaim(b->server->nvm, b->meta, b->meta->len, + b->slotBuf); + return (rc == WH_ERROR_OK) ? WC_XMSS_RC_SAVED_TO_NV_MEMORY + : WC_XMSS_RC_WRITE_FAIL; +} + +static enum wc_XmssRc _XmssBridgeReadCb(byte* priv, word32 privSz, + void* context) +{ + whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + uint16_t privOff; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_XMSS_RC_BAD_ARG; + } + + privOff = _StatefulBridgePrivOffset(b); + if ((uint32_t)privOff + privSz > b->meta->len) { + return WC_XMSS_RC_READ_FAIL; + } + + memcpy(priv, b->slotBuf + privOff, privSz); + return WC_XMSS_RC_READ_TO_MEMORY; +} +#endif /* WOLFSSL_HAVE_XMSS && WOLFHSM_CFG_DMA */ +#endif /* (WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS) && WOLFHSM_CFG_DMA */ + +#ifdef WOLFSSL_HAVE_LMS +int wh_Server_LmsKeyCacheImport(whServerContext* ctx, LmsKey* key, + whKeyId keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + uint16_t slotCapacity = WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE; + uint16_t blobSize; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId)) || + ((label != NULL) && (label_len > sizeof(cacheMeta->label)))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, + &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_LmsSerializeKey(key, slotCapacity, cacheBuf, &blobSize); + } + if (ret == WH_ERROR_OK) { + cacheMeta->id = keyId; + cacheMeta->len = blobSize; + cacheMeta->flags = flags; + cacheMeta->access = WH_NVM_ACCESS_ANY; + if ((label != NULL) && (label_len > 0)) { + memcpy(cacheMeta->label, label, label_len); + } + } + return ret; +} + +int wh_Server_LmsKeyCacheExport(whServerContext* ctx, whKeyId keyId, + LmsKey* key) +{ + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + int ret; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_LmsDeserializeKey(cacheBuf, (uint16_t)cacheMeta->len, + key); + } + return ret; +} +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS +int wh_Server_XmssKeyCacheImport(whServerContext* ctx, XmssKey* key, + const char* paramStr, whKeyId keyId, + whNvmFlags flags, uint16_t label_len, + uint8_t* label) +{ + int ret = WH_ERROR_OK; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + uint16_t slotCapacity = WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE; + uint16_t blobSize; + + if ((ctx == NULL) || (key == NULL) || (paramStr == NULL) || + (WH_KEYID_ISERASED(keyId)) || + ((label != NULL) && (label_len > sizeof(cacheMeta->label)))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, + &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_XmssSerializeKey(key, paramStr, slotCapacity, cacheBuf, + &blobSize); + } + if (ret == WH_ERROR_OK) { + cacheMeta->id = keyId; + cacheMeta->len = blobSize; + cacheMeta->flags = flags; + cacheMeta->access = WH_NVM_ACCESS_ANY; + if ((label != NULL) && (label_len > 0)) { + memcpy(cacheMeta->label, label, label_len); + } + } + return ret; +} + +int wh_Server_XmssKeyCacheExport(whServerContext* ctx, whKeyId keyId, + XmssKey* key) +{ + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + int ret; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_XmssDeserializeKey(cacheBuf, (uint16_t)cacheMeta->len, + key); + } + return ret; +} +#endif /* WOLFSSL_HAVE_XMSS */ + /** Request/Response Handling functions */ @@ -6673,100 +6928,1090 @@ static int _HandlePqcKemAlgorithmDma(whServerContext* ctx, uint16_t magic, } #endif /* WOLFSSL_HAVE_MLKEM */ -#if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) -static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, - uint16_t seq, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +/* Decode the slot blob's header lengths into the bridge struct. The blob + * format (see wh_crypto.c) places pubLen at +4, privLen at +6, paramLen at + * +8. */ +static int _StatefulBridgeFromSlot(whServerStatefulSigBridge* b, + whServerContext* server, + whKeyId keyId, + uint8_t* slotBuf, whNvmMetadata* meta, + uint16_t slotCapacity) { - (void)seq; + uint16_t pubLen, paramLen; - int ret = 0; - whMessageCrypto_CmacAesDmaRequest req; - whMessageCrypto_CmacAesDmaResponse res; + if ((b == NULL) || (server == NULL) || (slotBuf == NULL) || (meta == NULL)) { + return WH_ERROR_BADARGS; + } + memcpy(&pubLen, slotBuf + 4, sizeof(pubLen)); + memcpy(¶mLen, slotBuf + 8, sizeof(paramLen)); - if (inSize < sizeof(whMessageCrypto_CmacAesDmaRequest)) { + b->server = server; + b->keyId = keyId; + b->meta = meta; + b->slotBuf = slotBuf; + b->hdrSz = WH_CRYPTO_STATEFUL_SIG_HEADER_SZ; + b->paramLen = paramLen; + b->pubLen = pubLen; + b->slotCapacity = slotCapacity; + return WH_ERROR_OK; +} +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + +#ifdef WOLFSSL_HAVE_LMS +/* Dummy cbs used during keygen. wc_LmsKey_MakeKey requires both cbs to be + * set; we don't actually persist via cb during keygen because the slot blob + * (including pub) is assembled after MakeKey populates key->pub and + * key->priv_raw. See _HandleLmsKeyGenDma for the full sequence. */ +static int _LmsDummyWriteCb(const byte* priv, word32 privSz, void* context) +{ + (void)priv; (void)privSz; (void)context; + return WC_LMS_RC_SAVED_TO_NV_MEMORY; +} +static int _LmsDummyReadCb(byte* priv, word32 privSz, void* context) +{ + (void)priv; (void)privSz; (void)context; + return WC_LMS_RC_READ_TO_MEMORY; +} + +static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_LMS_VERIFY_ONLY + (void)ctx; (void)magic; (void)devId; (void)cryptoDataIn; (void)inSize; + (void)cryptoDataOut; (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret; + LmsKey key[1]; + void* clientPubAddr = NULL; + word32 pubLen32 = 0; + whKeyId keyId; + whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; + whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(req)) { return WH_ERROR_BADARGS; } - /* Translate request */ - ret = wh_MessageCrypto_TranslateCmacAesDmaRequest( - magic, (whMessageCrypto_CmacAesDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigKeyGenDmaRequest*)cryptoDataIn, + &req); if (ret != WH_ERROR_OK) { return ret; } - /* Validate variable-length fields fit within inSize. Trailing layout: - * uint8_t in[inlineInSz] - * uint8_t key[keySz] - */ - uint32_t available = inSize - sizeof(whMessageCrypto_CmacAesDmaRequest); - if (req.inlineInSz > available) { - return WH_ERROR_BADARGS; + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret != 0) { + return ret; } - available -= req.inlineInSz; - if (req.keySz > available) { - return WH_ERROR_BADARGS; + + ret = wc_LmsKey_SetParameters(key, (int)req.lmsLevels, (int)req.lmsHeight, + (int)req.lmsWinternitz); + if (ret == 0) { + ret = wc_LmsKey_SetWriteCb(key, _LmsDummyWriteCb); } - if (req.keySz > AES_256_KEY_SIZE) { - return WH_ERROR_BADARGS; + if (ret == 0) { + ret = wc_LmsKey_SetReadCb(key, _LmsDummyReadCb); + } + if (ret == 0) { + ret = wc_LmsKey_SetContext(key, NULL); + } + if (ret == 0) { + ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); } - word32 len; + /* Resolve the public key length and validate the client-supplied DMA + * buffer (size and address) BEFORE importing/committing the key. If these + * checks run afterward, an undersized buffer or bad address fails only + * after the key is already persisted in NVM; the keyId is then never + * returned to the client, orphaning an unreachable key and letting + * repeated failed keygens exhaust NVM. */ + if (ret == 0) { + ret = wc_LmsKey_GetPubLen(key, &pubLen32); + } + if (ret == 0 && req.pub.sz < pubLen32) { + ret = WH_ERROR_BUFFER_SIZE; + } + if (ret == 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.pub.addr, &clientPubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret != 0) { + res.dmaAddrStatus.badAddr = req.pub; + } + } - /* Pointers to inline trailing data */ - uint8_t* inlineIn = - (uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_CmacAesDmaRequest); - uint8_t* key = inlineIn + req.inlineInSz; - uint8_t* out = - (uint8_t*)(cryptoDataOut) + sizeof(whMessageCrypto_CmacAesDmaResponse); + if (ret == 0) { + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId); + } + } - memset(&res, 0, sizeof(res)); + if (ret == 0) { + ret = wh_Server_LmsKeyCacheImport(ctx, key, keyId, req.flags, + (uint16_t)req.labelSize, req.label); + } - /* DMA translated address for input */ - void* inAddr = NULL; + /* For non-ephemeral keys, commit to NVM so the key survives a server + * restart. Ephemeral keys are cache-only. */ + if ((ret == 0) && ((req.flags & WH_NVM_FLAGS_EPHEMERAL) == 0)) { + ret = wh_Server_KeystoreCommitKey(ctx, keyId); + } - uint8_t tmpKey[AES_256_KEY_SIZE]; - uint32_t tmpKeyLen = sizeof(tmpKey); - Cmac cmac[1]; + /* Stream the public key out via the already-validated DMA buffer. The copy + * cannot fail, so once the key is committed the client is guaranteed to + * receive its keyId. */ + if (ret == 0) { + memcpy(clientPubAddr, key->pub, pubLen32); + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.pubSize = pubLen32; + } + if (clientPubAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.pub.addr, &clientPubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + } - /* Oneshot fast path: DMA input only (no inline), output requested. The - * streaming protocol never produces outSz>0 with DMA input (Final is - * inline-only), so this branch is only taken by CmacGenerateDma. */ - if (req.inlineInSz == 0 && req.input.sz != 0 && req.outSz != 0) { - len = req.outSz; + wc_LmsKey_Free(key); - /* Translate DMA address for input */ - ret = wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - if (ret == WH_ERROR_ACCESS) { - res.dmaAddrStatus.badAddr = req.input; - } + (void)wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigKeyGenDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +#endif /* WOLFSSL_LMS_VERIFY_ONLY */ +} - /* Resolve key */ - if (ret == WH_ERROR_OK) { - ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, - &tmpKeyLen); - } +static int _HandleLmsSignDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_LMS_VERIFY_ONLY + (void)ctx; (void)magic; (void)devId; (void)cryptoDataIn; (void)inSize; + (void)cryptoDataOut; (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret; + LmsKey key[1]; + int keyInited = 0; + void* msgAddr = NULL; + void* sigAddr = NULL; + word32 sigLen; + whKeyId keyId; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + whServerStatefulSigBridge bridge; + whMessageCrypto_PqcStatefulSigSignDmaRequest req; + whMessageCrypto_PqcStatefulSigSignDmaResponse res; - if (ret == WH_ERROR_OK && req.keySz != 0) { - /* Client-supplied key - direct one-shot */ - WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot\n"); + memset(&res, 0, sizeof(res)); - ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, req.input.sz, - tmpKey, (word32)tmpKeyLen, NULL, devId); - } - else if (ret == WH_ERROR_OK) { - /* HSM-local key via keyId - init then generate */ - WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot with keyId:%x\n", - req.keyId); + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigSignDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigSignDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + return ret; + } - ret = wc_InitCmac_ex(cmac, tmpKey, (word32)tmpKeyLen, WC_CMAC_AES, - NULL, NULL, devId); + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } - if (ret == WH_ERROR_OK) { - ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, + sigLen = (word32)req.sig.sz; + + /* Hold the NVM lock for the entire load -> sign -> commit sequence so + * concurrent sign requests on the same keyId can't race past each other. + * Pattern from wh_server_counter.c. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret != WH_ERROR_OK) { + return ret; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret == 0) { + keyInited = 1; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_LmsDeserializeKey(cacheBuf, (uint16_t)cacheMeta->len, + key); + } + if (ret == WH_ERROR_OK) { + ret = _StatefulBridgeFromSlot( + &bridge, ctx, keyId, cacheBuf, cacheMeta, + WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE); + } + if (ret == WH_ERROR_OK) { + (void)wc_LmsKey_SetWriteCb(key, _LmsBridgeWriteCb); + (void)wc_LmsKey_SetReadCb(key, _LmsBridgeReadCb); + (void)wc_LmsKey_SetContext(key, &bridge); + ret = wc_LmsKey_Reload(key); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.msg; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.sig; + } + } + if (ret == WH_ERROR_OK) { + /* wolfCrypt's flow (verified against wc_lms.c:1439-1474 post-patch): + * 1. wc_hss_sign computes the signature into sig and advances + * key->priv_raw in memory. + * 2. write_private_key (our bridge) is called with the new + * priv_raw and atomically commits it to NVM. + * 3. If the bridge returns anything other than + * WC_LMS_RC_SAVED_TO_NV_MEMORY, wolfCrypt does ForceZero(sig) + * and returns IO_FAILED_E. + * Net effect: a signature is exposed to the caller only if the NVM + * commit succeeded. A process crash anywhere in the sequence either + * (a) leaves the old state in NVM with no signature exposed, or + * (b) commits the new state with the signature lost in transit - + * one wasted index but never an index reused with a fresh sig. */ + ret = wc_LmsKey_Sign(key, sigAddr, &sigLen, msgAddr, (int)req.msg.sz); + if (ret == 0) { + res.sigLen = sigLen; + } + } + if (sigAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, sigLen, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + } + if (msgAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (keyInited) { + wc_LmsKey_Free(key); + } + + if ((req.options & WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT) != 0) { + (void)wh_Server_KeystoreEvictKey(ctx, keyId); + } + + (void)WH_SERVER_NVM_UNLOCK(ctx); + + (void)wh_MessageCrypto_TranslatePqcStatefulSigSignDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigSignDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +#endif /* WOLFSSL_LMS_VERIFY_ONLY */ +} + +static int _HandleLmsVerifyDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ + int ret; + LmsKey key[1]; + int keyInited = 0; + void* sigAddr = NULL; + void* msgAddr = NULL; + whKeyId keyId; + whMessageCrypto_PqcStatefulSigVerifyDmaRequest req; + whMessageCrypto_PqcStatefulSigVerifyDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigVerifyDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret == 0) { + keyInited = 1; + ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); + } + if (ret == WH_ERROR_OK) { + /* Deserialize leaves the key in PARMSET; wc_LmsKey_Verify needs + * OK or VERIFYONLY. Pub is populated and that's all verify uses. */ + key->state = WC_LMS_STATE_VERIFYONLY; + } + + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.sig; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.msg; + } + } + if (ret == WH_ERROR_OK) { + int verifyRet = wc_LmsKey_Verify(key, sigAddr, (word32)req.sig.sz, + msgAddr, (int)req.msg.sz); + if (verifyRet == 0) { + res.res = 1; + } + else if (verifyRet == WC_NO_ERR_TRACE(SIG_VERIFY_E)) { + res.res = 0; + } + else { + ret = verifyRet; + } + } + + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + + if (keyInited) { + wc_LmsKey_Free(key); + } + + if ((req.options & WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT) != 0) { + (void)wh_Server_KeystoreEvictKey(ctx, keyId); + } + + (void)wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigVerifyDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +} + +static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ +#ifdef WOLFSSL_LMS_VERIFY_ONLY + (void)ctx; (void)magic; (void)devId; (void)cryptoDataIn; (void)inSize; + (void)cryptoDataOut; (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret; + LmsKey key[1]; + int keyInited = 0; + whKeyId keyId; + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest req; + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaRequest( + magic, + (whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret == 0) { + keyInited = 1; + ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); + } + if (ret == WH_ERROR_OK) { + res.sigsLeft = (uint32_t)wc_LmsKey_SigsLeft(key); + } + + if (keyInited) { + wc_LmsKey_Free(key); + } + + (void)wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +#endif /* WOLFSSL_LMS_VERIFY_ONLY */ +} +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS +/* wolfCrypt's wc_XmssKey_MakeKey calls write_private_key with the freshly + * generated sk and then ForceZero's key->sk (see wc_xmss.c). To get a usable + * sk back into key->sk for the subsequent serialize step, we capture it here + * via a context-pointed buffer. */ +typedef struct { + byte* buf; + word32 cap; + word32 len; +} _XmssSkCapture; + +static enum wc_XmssRc _XmssKeygenWriteCb(const byte* priv, word32 privSz, + void* context) +{ + _XmssSkCapture* cap = (_XmssSkCapture*)context; + if ((cap == NULL) || (priv == NULL) || (privSz > cap->cap)) { + return WC_XMSS_RC_WRITE_FAIL; + } + memcpy(cap->buf, priv, privSz); + cap->len = privSz; + return WC_XMSS_RC_SAVED_TO_NV_MEMORY; +} +static enum wc_XmssRc _XmssDummyReadCb(byte* priv, word32 privSz, + void* context) +{ + (void)priv; (void)privSz; (void)context; + return WC_XMSS_RC_READ_TO_MEMORY; +} + +static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ +#ifdef WOLFSSL_XMSS_VERIFY_ONLY + (void)ctx; (void)magic; (void)devId; (void)cryptoDataIn; (void)inSize; + (void)cryptoDataOut; (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret; + XmssKey key[1]; + void* clientPubAddr = NULL; + word32 pubLen32 = 0; + word32 privLen32 = 0; + whKeyId keyId; + whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; + whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; + _XmssSkCapture sk_cap; + /* WC_XMSS_MAX_SK comes from the params table; sized for the largest + * supported XMSS variant. The variants enabled in user_settings.h all + * fit in 4 KiB, but use the wolfCrypt-reported priv length to be + * exact. */ + byte sk_buf[4096]; + + memset(&res, 0, sizeof(res)); + memset(&sk_cap, 0, sizeof(sk_cap)); + sk_cap.buf = sk_buf; + sk_cap.cap = (word32)sizeof(sk_buf); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigKeyGenDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + /* xmssParamStr arrives via the request struct (populated by the client in + * wh_Client_XmssMakeKeyDma). Defensively enforce NUL-termination before + * passing it to wolfCrypt, since it originates from the client. */ + req.xmssParamStr[sizeof(req.xmssParamStr) - 1] = '\0'; + + ret = wc_XmssKey_Init(key, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wc_XmssKey_SetParamStr(key, req.xmssParamStr); + if (ret == 0) { + /* Use a real capture cb: wolfCrypt ForceZero's key->sk after MakeKey + * (see wc_xmss.c), so we copy sk into sk_buf via the cb and restore + * it on key->sk before serializing into the cache slot. */ + ret = wc_XmssKey_SetWriteCb(key, _XmssKeygenWriteCb); + } + if (ret == 0) { + ret = wc_XmssKey_SetReadCb(key, _XmssDummyReadCb); + } + if (ret == 0) { + ret = wc_XmssKey_SetContext(key, &sk_cap); + } + if (ret == 0) { + ret = wc_XmssKey_MakeKey(key, ctx->crypto->rng); + } + + if (ret == 0) { + /* Sanity-check the captured sk size against what wolfCrypt expects. */ + ret = wc_XmssKey_GetPrivLen(key, &privLen32); + if ((ret == 0) && (sk_cap.len != privLen32)) { + ret = WH_ERROR_ABORTED; + } + } + if (ret == 0) { + /* Restore sk so SerializeKey captures real bytes, not the + * MakeKey-zeroed buffer. */ + memcpy(key->sk, sk_cap.buf, sk_cap.len); + } + + /* Resolve the public key length and validate the client-supplied DMA + * buffer (size and address) BEFORE importing/committing the key, so an + * undersized buffer or bad address cannot leave an orphaned, unreachable + * key persisted in NVM (see _HandleLmsKeyGenDma for the full rationale). */ + if (ret == 0) { + ret = wc_XmssKey_GetPubLen(key, &pubLen32); + } + if (ret == 0 && req.pub.sz < pubLen32) { + ret = WH_ERROR_BUFFER_SIZE; + } + if (ret == 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.pub.addr, &clientPubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret != 0) { + res.dmaAddrStatus.badAddr = req.pub; + } + } + + if (ret == 0) { + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId); + } + } + + if (ret == 0) { + ret = wh_Server_XmssKeyCacheImport(ctx, key, req.xmssParamStr, keyId, + req.flags, (uint16_t)req.labelSize, + req.label); + } + + if ((ret == 0) && ((req.flags & WH_NVM_FLAGS_EPHEMERAL) == 0)) { + ret = wh_Server_KeystoreCommitKey(ctx, keyId); + } + + /* Stream the public key out via the already-validated DMA buffer; the copy + * cannot fail, so a committed key always returns its keyId to the client. */ + if (ret == 0) { + memcpy(clientPubAddr, key->pk, pubLen32); + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.pubSize = pubLen32; + } + if (clientPubAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.pub.addr, &clientPubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + } + + wc_XmssKey_Free(key); + + (void)wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigKeyGenDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + + wh_Utils_ForceZero(sk_buf, sizeof(sk_buf)); + wh_Utils_ForceZero(&sk_cap, sizeof(sk_cap)); + return ret; +#endif /* WOLFSSL_XMSS_VERIFY_ONLY */ +} + +static int _HandleXmssSignDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_XMSS_VERIFY_ONLY + (void)ctx; (void)magic; (void)devId; (void)cryptoDataIn; (void)inSize; + (void)cryptoDataOut; (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret; + XmssKey key[1]; + int keyInited = 0; + void* msgAddr = NULL; + void* sigAddr = NULL; + word32 sigLen; + whKeyId keyId; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + whServerStatefulSigBridge bridge; + whMessageCrypto_PqcStatefulSigSignDmaRequest req; + whMessageCrypto_PqcStatefulSigSignDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigSignDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigSignDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + sigLen = (word32)req.sig.sz; + + /* See _HandleLmsSignDma for the NVM-lock rationale. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret != WH_ERROR_OK) { + return ret; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wc_XmssKey_Init(key, NULL, devId); + if (ret == 0) { + keyInited = 1; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_XmssDeserializeKey(cacheBuf, (uint16_t)cacheMeta->len, + key); + } + if (ret == WH_ERROR_OK) { + ret = _StatefulBridgeFromSlot( + &bridge, ctx, keyId, cacheBuf, cacheMeta, + WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE); + } + if (ret == WH_ERROR_OK) { + (void)wc_XmssKey_SetWriteCb(key, _XmssBridgeWriteCb); + (void)wc_XmssKey_SetReadCb(key, _XmssBridgeReadCb); + (void)wc_XmssKey_SetContext(key, &bridge); + ret = wc_XmssKey_Reload(key); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.msg; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.sig; + } + } + if (ret == WH_ERROR_OK) { + ret = wc_XmssKey_Sign(key, sigAddr, &sigLen, msgAddr, (int)req.msg.sz); + if (ret == 0) { + res.sigLen = sigLen; + } + } + + if (sigAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, sigLen, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + } + if (msgAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (keyInited) { + wc_XmssKey_Free(key); + } + + if ((req.options & WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT) != 0) { + (void)wh_Server_KeystoreEvictKey(ctx, keyId); + } + + (void)WH_SERVER_NVM_UNLOCK(ctx); + + (void)wh_MessageCrypto_TranslatePqcStatefulSigSignDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigSignDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +#endif /* WOLFSSL_XMSS_VERIFY_ONLY */ +} + +static int _HandleXmssVerifyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + int ret; + XmssKey key[1]; + int keyInited = 0; + void* sigAddr = NULL; + void* msgAddr = NULL; + whKeyId keyId; + whMessageCrypto_PqcStatefulSigVerifyDmaRequest req; + whMessageCrypto_PqcStatefulSigVerifyDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigVerifyDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + ret = wc_XmssKey_Init(key, NULL, devId); + if (ret == 0) { + keyInited = 1; + ret = wh_Server_XmssKeyCacheExport(ctx, keyId, key); + } + if (ret == WH_ERROR_OK) { + /* Deserialize leaves the key in PARMSET; wc_XmssKey_Verify needs + * OK or VERIFYONLY. Pub is populated and that's all verify uses. */ + key->state = WC_XMSS_STATE_VERIFYONLY; + } + + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.sig; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret != WH_ERROR_OK) { + res.dmaAddrStatus.badAddr = req.msg; + } + } + if (ret == WH_ERROR_OK) { + int verifyRet = wc_XmssKey_Verify(key, sigAddr, (word32)req.sig.sz, + msgAddr, (int)req.msg.sz); + if (verifyRet == 0) { + res.res = 1; + } + else if (verifyRet == WC_NO_ERR_TRACE(SIG_VERIFY_E)) { + res.res = 0; + } + else { + ret = verifyRet; + } + } + + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + + if (keyInited) { + wc_XmssKey_Free(key); + } + + if ((req.options & WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT) != 0) { + (void)wh_Server_KeystoreEvictKey(ctx, keyId); + } + + (void)wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigVerifyDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +} + +static int _HandleXmssSigsLeftDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ +#ifdef WOLFSSL_XMSS_VERIFY_ONLY + (void)ctx; (void)magic; (void)devId; (void)cryptoDataIn; (void)inSize; + (void)cryptoDataOut; (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret; + XmssKey key[1]; + int keyInited = 0; + whKeyId keyId; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + whServerStatefulSigBridge bridge; + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest req; + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaRequest( + magic, + (whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wc_XmssKey_Init(key, NULL, devId); + if (ret == 0) { + keyInited = 1; + } + } + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_XmssDeserializeKey(cacheBuf, (uint16_t)cacheMeta->len, + key); + } + if (ret == WH_ERROR_OK) { + ret = _StatefulBridgeFromSlot( + &bridge, ctx, keyId, cacheBuf, cacheMeta, + WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE); + } + if (ret == WH_ERROR_OK) { + /* Reload uses the bridge ReadCb to populate sk from the cached blob, + * then transitions state to OK so SigsLeft can run. */ + (void)wc_XmssKey_SetWriteCb(key, _XmssBridgeWriteCb); + (void)wc_XmssKey_SetReadCb(key, _XmssBridgeReadCb); + (void)wc_XmssKey_SetContext(key, &bridge); + ret = wc_XmssKey_Reload(key); + } + if (ret == WH_ERROR_OK) { + res.sigsLeft = (uint32_t)wc_XmssKey_SigsLeft(key); + } + + if (keyInited) { + wc_XmssKey_Free(key); + } + + (void)wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +#endif /* WOLFSSL_XMSS_VERIFY_ONLY */ +} +#endif /* WOLFSSL_HAVE_XMSS */ + +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +static int _HandlePqcStatefulSigAlgorithmDma( + whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, + uint16_t cryptoInSize, void* cryptoDataOut, uint16_t* cryptoOutSize, + uint32_t pkAlgoType, uint32_t pqAlgoType) +{ + int ret = WH_ERROR_NOHANDLER; + + switch (pqAlgoType) { +#ifdef WOLFSSL_HAVE_LMS + case WC_PQC_STATEFUL_SIG_TYPE_LMS: + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN: + ret = _HandleLmsKeyGenDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN: + ret = _HandleLmsSignDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY: + ret = _HandleLmsVerifyDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT: + ret = _HandleLmsSigsLeftDma(ctx, magic, devId, + cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + break; +#endif /* WOLFSSL_HAVE_LMS */ +#ifdef WOLFSSL_HAVE_XMSS + case WC_PQC_STATEFUL_SIG_TYPE_XMSS: + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN: + ret = _HandleXmssKeyGenDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN: + ret = _HandleXmssSignDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY: + ret = _HandleXmssVerifyDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT: + ret = _HandleXmssSigsLeftDma(ctx, magic, devId, + cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + break; +#endif /* WOLFSSL_HAVE_XMSS */ + default: + ret = WH_ERROR_NOHANDLER; + break; + } + + return ret; +} +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ +#if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) +static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, + uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)seq; + + int ret = 0; + whMessageCrypto_CmacAesDmaRequest req; + whMessageCrypto_CmacAesDmaResponse res; + + if (inSize < sizeof(whMessageCrypto_CmacAesDmaRequest)) { + return WH_ERROR_BADARGS; + } + + /* Translate request */ + ret = wh_MessageCrypto_TranslateCmacAesDmaRequest( + magic, (whMessageCrypto_CmacAesDmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + /* Validate variable-length fields fit within inSize. Trailing layout: + * uint8_t in[inlineInSz] + * uint8_t key[keySz] + */ + uint32_t available = inSize - sizeof(whMessageCrypto_CmacAesDmaRequest); + if (req.inlineInSz > available) { + return WH_ERROR_BADARGS; + } + available -= req.inlineInSz; + if (req.keySz > available) { + return WH_ERROR_BADARGS; + } + if (req.keySz > AES_256_KEY_SIZE) { + return WH_ERROR_BADARGS; + } + + word32 len; + + /* Pointers to inline trailing data */ + uint8_t* inlineIn = + (uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_CmacAesDmaRequest); + uint8_t* key = inlineIn + req.inlineInSz; + uint8_t* out = + (uint8_t*)(cryptoDataOut) + sizeof(whMessageCrypto_CmacAesDmaResponse); + + memset(&res, 0, sizeof(res)); + + /* DMA translated address for input */ + void* inAddr = NULL; + + uint8_t tmpKey[AES_256_KEY_SIZE]; + uint32_t tmpKeyLen = sizeof(tmpKey); + Cmac cmac[1]; + + /* Oneshot fast path: DMA input only (no inline), output requested. The + * streaming protocol never produces outSz>0 with DMA input (Final is + * inline-only), so this branch is only taken by CmacGenerateDma. */ + if (req.inlineInSz == 0 && req.input.sz != 0 && req.outSz != 0) { + len = req.outSz; + + /* Translate DMA address for input */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.input; + } + + /* Resolve key */ + if (ret == WH_ERROR_OK) { + ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen); + } + + if (ret == WH_ERROR_OK && req.keySz != 0) { + /* Client-supplied key - direct one-shot */ + WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot\n"); + + ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, req.input.sz, + tmpKey, (word32)tmpKeyLen, NULL, devId); + } + else if (ret == WH_ERROR_OK) { + /* HSM-local key via keyId - init then generate */ + WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot with keyId:%x\n", + req.keyId); + + ret = wc_InitCmac_ex(cmac, tmpKey, (word32)tmpKeyLen, WC_CMAC_AES, + NULL, NULL, devId); + + if (ret == WH_ERROR_OK) { + ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, req.input.sz, NULL, 0, NULL, devId); } } @@ -7078,6 +8323,17 @@ int wh_Server_HandleCryptoDmaRequest(whServerContext* ctx, uint16_t magic, rqstHeader.algoSubType); break; #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + case WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN: + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN: + case WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY: + case WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT: + ret = _HandlePqcStatefulSigAlgorithmDma( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, &cryptoOutSize, rqstHeader.algoType, + rqstHeader.algoSubType); + break; +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef HAVE_ED25519 case WC_PK_TYPE_ED25519_SIGN: ret = _HandleEd25519SignDma(ctx, magic, devId, cryptoDataIn, diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 478edfe2b..844a9332f 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -140,6 +140,14 @@ /* ML-KEM Options */ #define WOLFSSL_HAVE_MLKEM +/* LMS / HSS Options (RFC 8554, NIST SP 800-208) */ +#define WOLFSSL_HAVE_LMS +#define WOLFSSL_WC_LMS + +/* XMSS / XMSS^MT Options (RFC 8391, NIST SP 800-208) */ +#define WOLFSSL_HAVE_XMSS +#define WOLFSSL_WC_XMSS + /* Ed25519 Options */ #define HAVE_ED25519 diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index 0d1c388a9..ba3d86295 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -158,6 +158,14 @@ whMessageCrypto_MlKemEncapsDmaRequest pkMlkemEncapsDmaReq; whMessageCrypto_MlKemEncapsDmaResponse pkMlkemEncapsDmaRes; whMessageCrypto_MlKemDecapsDmaRequest pkMlkemDecapsDmaReq; whMessageCrypto_MlKemDecapsDmaResponse pkMlkemDecapsDmaRes; +whMessageCrypto_PqcStatefulSigKeyGenDmaRequest pqStatefulSigKeygenDmaReq; +whMessageCrypto_PqcStatefulSigKeyGenDmaResponse pqStatefulSigKeygenDmaRes; +whMessageCrypto_PqcStatefulSigSignDmaRequest pqStatefulSigSignDmaReq; +whMessageCrypto_PqcStatefulSigSignDmaResponse pqStatefulSigSignDmaRes; +whMessageCrypto_PqcStatefulSigVerifyDmaRequest pqStatefulSigVerifyDmaReq; +whMessageCrypto_PqcStatefulSigVerifyDmaResponse pqStatefulSigVerifyDmaRes; +whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest pqStatefulSigSigsLeftDmaReq; +whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse pqStatefulSigSigsLeftDmaRes; #endif /* WOLFHSM_CFG_DMA */ #endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 0ea823e67..c134e5fdf 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -33,6 +33,12 @@ #include "wolfssl/wolfcrypt/kdf.h" #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#if defined(WOLFSSL_HAVE_LMS) +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif +#if defined(WOLFSSL_HAVE_XMSS) +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif #include "wolfhsm/wh_error.h" @@ -12186,6 +12192,306 @@ int whTestCrypto_MlDsaVerifyOnlyDma(whClientContext* ctx, int devId, !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) static int whTestCrypto_MlKemGetLevels(int* levels, int maxLevels) +#if defined(WOLFHSM_CFG_DMA) && \ + defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) +/* L=1, H=5, W=8 keeps the signature ~1.3 KB and gives 2^5 = 32 signatures. */ +#define WH_TEST_LMS_LEVELS (1) +#define WH_TEST_LMS_HEIGHT (5) +#define WH_TEST_LMS_WINTERNITZ (8) +/* Generous buffer that fits L1_H5_W8 (~1328) and any W<8 variant of the same + * height (W=1 ~8688). Keeps off the stack so ASAN builds stay happy. */ +static byte whTest_LmsSigBuf[8800]; + +static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + LmsKey key[1]; + int keyInited = 0; + word32 sigLen = 0; + word32 sigCap = 0; + const byte msg[] = "wolfHSM LMS cryptocb test"; + word32 msgSz = (word32)sizeof(msg) - 1; + + (void)rng; + + memset(whTest_LmsSigBuf, 0, sizeof(whTest_LmsSigBuf)); + + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed wc_LmsKey_Init devId=0x%X ret=%d\n", devId, ret); + return ret; + } + keyInited = 1; + + if (ret == 0) { + ret = wc_LmsKey_SetParameters(key, WH_TEST_LMS_LEVELS, + WH_TEST_LMS_HEIGHT, + WH_TEST_LMS_WINTERNITZ); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS SetParameters ret=%d\n", ret); + } + } + + if (ret == 0) { + ret = wc_LmsKey_GetSigLen(key, &sigCap); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS GetSigLen ret=%d\n", ret); + } + else if (sigCap > sizeof(whTest_LmsSigBuf)) { + WH_ERROR_PRINT("LMS sig buffer too small: need=%u have=%u\n", + (unsigned)sigCap, + (unsigned)sizeof(whTest_LmsSigBuf)); + ret = BUFFER_E; + } + } + + /* MakeKey via cryptocb: server caches private key (ephemeral) and + * returns the public key over DMA. */ + if (ret == 0) { + ret = wc_LmsKey_MakeKey(key, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS MakeKey ret=%d\n", ret); + } + } + + /* wc_LmsKey_SigsLeft returns a boolean: nonzero = signatures available, + * 0 = exhausted. Fresh key should report nonzero. */ + if (ret == 0) { + if (wc_LmsKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("LMS reported exhausted on fresh key\n"); + ret = -1; + } + } + + /* Sign via cryptocb. */ + if (ret == 0) { + sigLen = sigCap; + ret = wc_LmsKey_Sign(key, whTest_LmsSigBuf, &sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS Sign ret=%d\n", ret); + } + else if (sigLen != sigCap) { + WH_ERROR_PRINT("LMS Sign produced unexpected length=%u expected=%u\n", + (unsigned)sigLen, (unsigned)sigCap); + ret = -1; + } + } + + /* Verify the signature via cryptocb. */ + if (ret == 0) { + ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS Verify ret=%d\n", ret); + } + } + + /* Tampered signature must fail to verify. */ + if (ret == 0) { + whTest_LmsSigBuf[0] ^= 0xFF; + ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, msg, (int)msgSz); + whTest_LmsSigBuf[0] ^= 0xFF; + if (ret == 0) { + WH_ERROR_PRINT("LMS Verify unexpectedly accepted tampered sig\n"); + ret = -1; + } + else { + ret = 0; + } + } + + /* Wrong message must also fail to verify. */ + if (ret == 0) { + const byte wrongMsg[] = "wolfHSM LMS cryptocb wrong"; + ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, wrongMsg, + (int)(sizeof(wrongMsg) - 1)); + if (ret == 0) { + WH_ERROR_PRINT("LMS Verify unexpectedly accepted wrong message\n"); + ret = -1; + } + else { + ret = 0; + } + } + + /* H=5 means 32 sigs total; after one sign, the key is still not + * exhausted. */ + if (ret == 0) { + if (wc_LmsKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("LMS reported exhausted after one sign\n"); + ret = -1; + } + } + + if (keyInited) { + whKeyId evictId = WH_KEYID_ERASED; + if ((wh_Client_LmsGetKeyId(key, &evictId) == 0) && + !WH_KEYID_ISERASED(evictId)) { + int evictRet = wh_Client_KeyEvict(ctx, evictId); + if ((evictRet != 0) && (ret == 0)) { + WH_ERROR_PRINT("Failed LMS evict keyId=0x%X ret=%d\n", + (unsigned)evictId, evictRet); + ret = evictRet; + } + } + wc_LmsKey_Free(key); + } + + if (ret == 0) { + WH_TEST_PRINT("LMS CryptoCb DEVID=0x%X SUCCESS\n", devId); + } + + return ret; +} +#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + +#if defined(WOLFHSM_CFG_DMA) && \ + defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) +/* "XMSS-SHA2_10_256" is the smallest standardized XMSS parameter set + * (height 10, 1024 signatures). pubLen=68, sigLen=2500. */ +#define WH_TEST_XMSS_PARAM_STR "XMSS-SHA2_10_256" +static byte whTest_XmssSigBuf[2500]; + +static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + XmssKey key[1]; + int keyInited = 0; + word32 sigLen = 0; + word32 sigCap = 0; + const byte msg[] = "wolfHSM XMSS cryptocb test"; + word32 msgSz = (word32)sizeof(msg) - 1; + + (void)rng; + + memset(whTest_XmssSigBuf, 0, sizeof(whTest_XmssSigBuf)); + + ret = wc_XmssKey_Init(key, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed wc_XmssKey_Init devId=0x%X ret=%d\n", devId, ret); + return ret; + } + keyInited = 1; + + if (ret == 0) { + ret = wc_XmssKey_SetParamStr(key, WH_TEST_XMSS_PARAM_STR); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS SetParamStr=\"%s\" ret=%d\n", + WH_TEST_XMSS_PARAM_STR, ret); + } + } + + if (ret == 0) { + ret = wc_XmssKey_GetSigLen(key, &sigCap); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS GetSigLen ret=%d\n", ret); + } + else if (sigCap > sizeof(whTest_XmssSigBuf)) { + WH_ERROR_PRINT("XMSS sig buffer too small: need=%u have=%u\n", + (unsigned)sigCap, + (unsigned)sizeof(whTest_XmssSigBuf)); + ret = BUFFER_E; + } + } + + /* MakeKey via cryptocb: server caches private key (ephemeral) and + * returns the public key over DMA. */ + if (ret == 0) { + ret = wc_XmssKey_MakeKey(key, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS MakeKey ret=%d\n", ret); + } + } + + /* wc_XmssKey_SigsLeft returns a boolean: nonzero = signatures available, + * 0 = exhausted. */ + if (ret == 0) { + if (wc_XmssKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("XMSS reported exhausted on fresh key\n"); + ret = -1; + } + } + + if (ret == 0) { + sigLen = sigCap; + ret = wc_XmssKey_Sign(key, whTest_XmssSigBuf, &sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS Sign ret=%d\n", ret); + } + else if (sigLen != sigCap) { + WH_ERROR_PRINT("XMSS Sign produced unexpected length=%u expected=%u\n", + (unsigned)sigLen, (unsigned)sigCap); + ret = -1; + } + } + + if (ret == 0) { + ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS Verify ret=%d\n", ret); + } + } + + if (ret == 0) { + whTest_XmssSigBuf[0] ^= 0xFF; + ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, msg, (int)msgSz); + whTest_XmssSigBuf[0] ^= 0xFF; + if (ret == 0) { + WH_ERROR_PRINT("XMSS Verify unexpectedly accepted tampered sig\n"); + ret = -1; + } + else { + ret = 0; + } + } + + if (ret == 0) { + const byte wrongMsg[] = "wolfHSM XMSS cryptocb wrong"; + ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, wrongMsg, + (int)(sizeof(wrongMsg) - 1)); + if (ret == 0) { + WH_ERROR_PRINT("XMSS Verify unexpectedly accepted wrong message\n"); + ret = -1; + } + else { + ret = 0; + } + } + + /* H=10 means 1024 sigs total; after one sign, the key is still not + * exhausted. */ + if (ret == 0) { + if (wc_XmssKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("XMSS reported exhausted after one sign\n"); + ret = -1; + } + } + + if (keyInited) { + whKeyId evictId = WH_KEYID_ERASED; + if ((wh_Client_XmssGetKeyId(key, &evictId) == 0) && + !WH_KEYID_ISERASED(evictId)) { + int evictRet = wh_Client_KeyEvict(ctx, evictId); + if ((evictRet != 0) && (ret == 0)) { + WH_ERROR_PRINT("Failed XMSS evict keyId=0x%X ret=%d\n", + (unsigned)evictId, evictRet); + ret = evictRet; + } + } + wc_XmssKey_Free(key); + } + + if (ret == 0) { + WH_TEST_PRINT("XMSS CryptoCb DEVID=0x%X SUCCESS\n", devId); + } + + return ret; +} +#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + +/* Test key usage policy enforcement for various crypto operations */ +int whTest_CryptoKeyUsagePolicies(whClientContext* client, WC_RNG* rng) { int count = 0; @@ -14968,6 +15274,19 @@ int whTest_CryptoClientConfig(whClientConfig* config) !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) */ #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFHSM_CFG_DMA) && \ + defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + if (ret == 0) { + ret = whTestCrypto_LmsCryptoCb(client, WH_DEV_ID_DMA, rng); + } +#endif + +#if defined(WOLFHSM_CFG_DMA) && \ + defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + if (ret == 0) { + ret = whTestCrypto_XmssCryptoCb(client, WH_DEV_ID_DMA, rng); + } +#endif #ifdef WOLFHSM_CFG_DEBUG_VERBOSE if (ret == 0) { diff --git a/test/wh_test_wolfcrypt_test.c b/test/wh_test_wolfcrypt_test.c index 739472480..5d4a937b4 100644 --- a/test/wh_test_wolfcrypt_test.c +++ b/test/wh_test_wolfcrypt_test.c @@ -246,6 +246,7 @@ static int wh_ClientServer_MemThreadTest(void) .comm_config = cs_conf, .nvm = nvm, .crypto = crypto, + .devId = INVALID_DEVID, }}; WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf)); diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 27ca89f49..c60fb61f9 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3208,5 +3208,76 @@ int wh_Client_MlKemDecapsulateDma(whClientContext* ctx, MlKemKey* key, #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +#ifdef WOLFHSM_CFG_DMA + +#ifdef WOLFSSL_HAVE_LMS + +/* Bind / read the wolfHSM key id stored in key->devCtx. */ +int wh_Client_LmsSetKeyId(LmsKey* key, whKeyId keyId); +int wh_Client_LmsGetKeyId(LmsKey* key, whKeyId* outId); + +/* Generate an LMS key on the server. The key's parameter set + * (levels/height/winternitz) must be bound on the in-memory key before this + * call (e.g. via wc_LmsKey_SetParameters). On success the key's devCtx + * carries the server-side keyId. + * + * If flags include WH_NVM_FLAGS_EPHEMERAL, the server returns the public key + * via DMA and the caller can sign with it as long as it remains cached on + * the server. Otherwise the key is committed to the keystore. */ +int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/* Convenience wrapper: WH_NVM_FLAGS_EPHEMERAL keygen, returns pub via DMA. */ +int wh_Client_LmsMakeExportKeyDma(whClientContext* ctx, LmsKey* key); + +/* Sign msg with an HSM-resident LMS key (key->devCtx carries the keyId). + * The new private state is committed atomically to NVM by the server before + * the signature is returned. */ +int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, + byte* sig, word32* sigSz, LmsKey* key); + +/* Verify sig against msg using an HSM-resident LMS key. *res is set to 1 on + * success, 0 on signature mismatch. */ +int wh_Client_LmsVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, + const byte* msg, word32 msgSz, int* res, + LmsKey* key); + +/* Query remaining signatures on an HSM-resident LMS key. */ +int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, + word32* sigsLeft); + +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS + +int wh_Client_XmssSetKeyId(XmssKey* key, whKeyId keyId); +int wh_Client_XmssGetKeyId(XmssKey* key, whKeyId* outId); + +/* Generate an XMSS / XMSS^MT key on the server. The parameter string must be + * bound on the in-memory key (via wc_XmssKey_SetParamStr) before this call. + */ +int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +int wh_Client_XmssMakeExportKeyDma(whClientContext* ctx, XmssKey* key); + +int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, + byte* sig, word32* sigSz, XmssKey* key); + +int wh_Client_XmssVerifyDma(whClientContext* ctx, const byte* sig, + word32 sigSz, const byte* msg, word32 msgSz, + int* res, XmssKey* key); + +int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, + word32* sigsLeft); + +#endif /* WOLFSSL_HAVE_XMSS */ + +#endif /* WOLFHSM_CFG_DMA */ +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #endif /* !WOLFHSM_CFG_NO_CRYPTO */ #endif /* !WOLFHSM_WH_CLIENT_CRYPTO_H_ */ diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 88094b9c8..41dd3baa8 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -129,6 +129,49 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, MlKemKey* key); #endif /* WOLFSSL_HAVE_MLKEM */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +/* Size of the fixed header at the start of a stateful-sig (LMS/XMSS) slot blob: + * magic(4) + pubLen(2) + privLen(2) + paramLen(2) + reserved(2). Shared so the + * server bridge can locate the variable-length sections that follow it. The + * full blob layout is documented in wh_crypto.c. */ +#define WH_CRYPTO_STATEFUL_SIG_HEADER_SZ 12 +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + +#ifdef WOLFSSL_HAVE_LMS +/* Store an LmsKey (parameter set + public key + priv_raw) into a byte + * sequence. + * + * @param [in] key LmsKey to serialize. + * @param [in] max_size Capacity of buffer in bytes. + * @param [out] buffer Destination buffer. + * @param [in,out] out_size On success, total blob size. + * @return WH_ERROR_OK on success, WH_ERROR_BUFFER_SIZE if max_size is too + * small, WH_ERROR_BADARGS otherwise. */ +int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, + uint16_t* out_size); + +/* Restore an LmsKey from a byte sequence. + * + * @param [in] buffer Source blob. + * @param [in] size Blob size in bytes. + * @param [in,out] key Initialized LmsKey to populate. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS on malformed blob. */ +int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, + LmsKey* key); +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS +/* Store an XmssKey (param string + public key + secret state) into a byte + * sequence. */ +int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, + uint16_t max_size, uint8_t* buffer, + uint16_t* out_size); + +/* Restore an XmssKey from a byte sequence */ +int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, + XmssKey* key); +#endif /* WOLFSSL_HAVE_XMSS */ + #endif /* !WOLFHSM_CFG_NO_CRYPTO */ #endif /* WOLFHSM_WH_CRYPTO_H_ */ diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index 2ff2e3f15..74fbfd24c 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -1550,6 +1550,123 @@ int wh_MessageCrypto_TranslateMlKemDecapsDmaResponse( uint16_t magic, const whMessageCrypto_MlKemDecapsDmaResponse* src, whMessageCrypto_MlKemDecapsDmaResponse* dest); +/* Stateful hash-based signature (LMS / XMSS) DMA messages. + * + * The discriminator (LMS vs XMSS) rides on the generic request header's + * algoSubType field, set to WC_PQC_STATEFUL_SIG_TYPE_LMS or _XMSS by the + * client. Parameter selection on keygen uses lmsLevels/lmsHeight/lmsWinternitz + * when algoSubType == LMS, or xmssParamStr when algoSubType == XMSS. + * xmssParamStr is sized to fit the longest XMSS^MT name (e.g. + * "XMSSMT-SHAKE256_60/12_256") plus NUL. + */ + +/* Stateful sig DMA Key Generation Request */ +typedef struct { + whMessageCrypto_DmaBuffer pub; /* Server writes pub key here */ + uint32_t flags; + uint32_t keyId; + uint32_t access; + uint32_t labelSize; + uint32_t lmsLevels; + uint32_t lmsHeight; + uint32_t lmsWinternitz; + uint8_t label[WH_NVM_LABEL_LEN]; + char xmssParamStr[32]; + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ +} whMessageCrypto_PqcStatefulSigKeyGenDmaRequest; + +/* Stateful sig DMA Key Generation Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t keyId; + uint32_t pubSize; +} whMessageCrypto_PqcStatefulSigKeyGenDmaResponse; + +/* Stateful sig DMA Sign Request */ +typedef struct { + whMessageCrypto_DmaBuffer msg; /* Message to sign */ + whMessageCrypto_DmaBuffer sig; /* Server writes signature here */ + uint32_t options; +#define WH_MESSAGE_CRYPTO_STATEFUL_SIG_OPTIONS_EVICT (1 << 0) + uint32_t keyId; +} whMessageCrypto_PqcStatefulSigSignDmaRequest; + +/* Stateful sig DMA Sign Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t sigLen; + uint8_t WH_PAD[4]; +} whMessageCrypto_PqcStatefulSigSignDmaResponse; + +/* Stateful sig DMA Verify Request */ +typedef struct { + whMessageCrypto_DmaBuffer sig; /* Signature to verify */ + whMessageCrypto_DmaBuffer msg; /* Message that was signed */ + uint32_t options; + uint32_t keyId; +} whMessageCrypto_PqcStatefulSigVerifyDmaRequest; + +/* Stateful sig DMA Verify Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t res; /* 1 if signature valid, 0 otherwise */ + uint8_t WH_PAD[4]; +} whMessageCrypto_PqcStatefulSigVerifyDmaResponse; + +/* Stateful sig DMA Signatures-Left Request. + * + * No DMA buffers are required for this query; the request is named with the + * Dma suffix purely for naming consistency with the rest of the family. */ +typedef struct { + uint32_t keyId; +} whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest; + +/* Stateful sig DMA Signatures-Left Response. */ +typedef struct { + uint32_t sigsLeft; +} whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse; + +/* Stateful sig DMA translation functions */ +int wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* src, + whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigKeyGenDmaResponse* src, + whMessageCrypto_PqcStatefulSigKeyGenDmaResponse* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigSignDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSignDmaRequest* src, + whMessageCrypto_PqcStatefulSigSignDmaRequest* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigSignDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSignDmaResponse* src, + whMessageCrypto_PqcStatefulSigSignDmaResponse* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigVerifyDmaRequest* src, + whMessageCrypto_PqcStatefulSigVerifyDmaRequest* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigVerifyDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigVerifyDmaResponse* src, + whMessageCrypto_PqcStatefulSigVerifyDmaResponse* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaRequest( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* src, + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* dest); + +int wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaResponse( + uint16_t magic, + const whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* src, + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* dest); + /* Ed25519 DMA Sign Request */ typedef struct { whMessageCrypto_DmaBuffer msg; /* Message buffer */ diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h index 01ce0ea84..98d12bbc7 100644 --- a/wolfhsm/wh_server_crypto.h +++ b/wolfhsm/wh_server_crypto.h @@ -121,6 +121,28 @@ int wh_Server_KeyCacheImportRaw(whServerContext* ctx, const uint8_t* keyData, uint32_t keySize, whKeyId keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label); +#ifdef WOLFSSL_HAVE_LMS +/* Persist an LmsKey (param descriptor + pub + priv_raw) into the server key + * cache. Subsequent sign operations reload state from this slot via + * wh_Server_LmsKeyCacheExport. */ +int wh_Server_LmsKeyCacheImport(whServerContext* ctx, LmsKey* key, + whKeyId keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); +/* Restore an LmsKey from a server key cache slot. The key is left in a state + * suitable for installing read/write callbacks before invoking + * wc_LmsKey_Reload. */ +int wh_Server_LmsKeyCacheExport(whServerContext* ctx, whKeyId keyId, + LmsKey* key); +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS +int wh_Server_XmssKeyCacheImport(whServerContext* ctx, XmssKey* key, + const char* paramStr, whKeyId keyId, + whNvmFlags flags, uint16_t label_len, + uint8_t* label); +int wh_Server_XmssKeyCacheExport(whServerContext* ctx, whKeyId keyId, + XmssKey* key); +#endif /* WOLFSSL_HAVE_XMSS */ #ifdef HAVE_HKDF /* Store HKDF output into a server key cache with optional metadata */ From bb6cc10159dc10dd9c692ce17afcc59136fa1e8a Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 1 Jun 2026 09:09:49 -0700 Subject: [PATCH 02/12] Fix busted rebase --- test/wh_test_crypto.c | 1167 ++++++++++++++++++++--------------------- 1 file changed, 583 insertions(+), 584 deletions(-) diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index c134e5fdf..3e827e987 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -32,13 +32,13 @@ #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/kdf.h" #include "wolfssl/wolfcrypt/ed25519.h" -#include "wolfssl/wolfcrypt/wc_mlkem.h" #if defined(WOLFSSL_HAVE_LMS) #include "wolfssl/wolfcrypt/wc_lms.h" #endif #if defined(WOLFSSL_HAVE_XMSS) #include "wolfssl/wolfcrypt/wc_xmss.h" #endif +#include "wolfssl/wolfcrypt/wc_mlkem.h" #include "wolfhsm/wh_error.h" @@ -12192,445 +12192,145 @@ int whTestCrypto_MlDsaVerifyOnlyDma(whClientContext* ctx, int devId, !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) static int whTestCrypto_MlKemGetLevels(int* levels, int maxLevels) -#if defined(WOLFHSM_CFG_DMA) && \ - defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) -/* L=1, H=5, W=8 keeps the signature ~1.3 KB and gives 2^5 = 32 signatures. */ -#define WH_TEST_LMS_LEVELS (1) -#define WH_TEST_LMS_HEIGHT (5) -#define WH_TEST_LMS_WINTERNITZ (8) -/* Generous buffer that fits L1_H5_W8 (~1328) and any W<8 variant of the same - * height (W=1 ~8688). Keeps off the stack so ASAN builds stay happy. */ -static byte whTest_LmsSigBuf[8800]; - -static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, - WC_RNG* rng) { - int ret = 0; - LmsKey key[1]; - int keyInited = 0; - word32 sigLen = 0; - word32 sigCap = 0; - const byte msg[] = "wolfHSM LMS cryptocb test"; - word32 msgSz = (word32)sizeof(msg) - 1; + int count = 0; - (void)rng; +#ifndef WOLFSSL_NO_ML_KEM_512 + if (count < maxLevels) { + levels[count++] = WC_ML_KEM_512; + } +#endif +#ifndef WOLFSSL_NO_ML_KEM_768 + if (count < maxLevels) { + levels[count++] = WC_ML_KEM_768; + } +#endif +#ifndef WOLFSSL_NO_ML_KEM_1024 + if (count < maxLevels) { + levels[count++] = WC_ML_KEM_1024; + } +#endif - memset(whTest_LmsSigBuf, 0, sizeof(whTest_LmsSigBuf)); + return count; +} - ret = wc_LmsKey_Init(key, NULL, devId); - if (ret != 0) { - WH_ERROR_PRINT("Failed wc_LmsKey_Init devId=0x%X ret=%d\n", devId, ret); - return ret; - } - keyInited = 1; +static int whTestCrypto_MlKemWolfCrypt(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + int levels[3]; + int levelCnt = 0; + int i; + byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ssEnc[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + word32 ctLen; + word32 ssEncLen; + word32 ssDecLen; - if (ret == 0) { - ret = wc_LmsKey_SetParameters(key, WH_TEST_LMS_LEVELS, - WH_TEST_LMS_HEIGHT, - WH_TEST_LMS_WINTERNITZ); - if (ret != 0) { - WH_ERROR_PRINT("Failed LMS SetParameters ret=%d\n", ret); - } - } + (void)ctx; - if (ret == 0) { - ret = wc_LmsKey_GetSigLen(key, &sigCap); - if (ret != 0) { - WH_ERROR_PRINT("Failed LMS GetSigLen ret=%d\n", ret); - } - else if (sigCap > sizeof(whTest_LmsSigBuf)) { - WH_ERROR_PRINT("LMS sig buffer too small: need=%u have=%u\n", - (unsigned)sigCap, - (unsigned)sizeof(whTest_LmsSigBuf)); - ret = BUFFER_E; - } - } + levelCnt = + whTestCrypto_MlKemGetLevels(levels, (int)(sizeof(levels) / sizeof(levels[0]))); - /* MakeKey via cryptocb: server caches private key (ephemeral) and - * returns the public key over DMA. */ - if (ret == 0) { - ret = wc_LmsKey_MakeKey(key, rng); - if (ret != 0) { - WH_ERROR_PRINT("Failed LMS MakeKey ret=%d\n", ret); - } - } + for (i = 0; (ret == 0) && (i < levelCnt); i++) { + MlKemKey key[1]; + int keyInited = 0; - /* wc_LmsKey_SigsLeft returns a boolean: nonzero = signatures available, - * 0 = exhausted. Fresh key should report nonzero. */ - if (ret == 0) { - if (wc_LmsKey_SigsLeft(key) == 0) { - WH_ERROR_PRINT("LMS reported exhausted on fresh key\n"); - ret = -1; - } - } + ctLen = sizeof(ct); + ssEncLen = sizeof(ssEnc); + ssDecLen = sizeof(ssDec); + memset(ct, 0, sizeof(ct)); + memset(ssEnc, 0, sizeof(ssEnc)); + memset(ssDec, 0, sizeof(ssDec)); - /* Sign via cryptocb. */ - if (ret == 0) { - sigLen = sigCap; - ret = wc_LmsKey_Sign(key, whTest_LmsSigBuf, &sigLen, msg, (int)msgSz); + ret = wc_MlKemKey_Init(key, levels[i], NULL, devId); if (ret != 0) { - WH_ERROR_PRINT("Failed LMS Sign ret=%d\n", ret); - } - else if (sigLen != sigCap) { - WH_ERROR_PRINT("LMS Sign produced unexpected length=%u expected=%u\n", - (unsigned)sigLen, (unsigned)sigCap); - ret = -1; + WH_ERROR_PRINT("Failed to init ML-KEM key level=%d ret=%d\n", + levels[i], ret); + break; } - } + keyInited = 1; - /* Verify the signature via cryptocb. */ - if (ret == 0) { - ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, msg, (int)msgSz); + ret = wc_MlKemKey_MakeKey(key, rng); if (ret != 0) { - WH_ERROR_PRINT("Failed LMS Verify ret=%d\n", ret); + WH_ERROR_PRINT("Failed to make ML-KEM key level=%d ret=%d\n", + levels[i], ret); } - } - - /* Tampered signature must fail to verify. */ - if (ret == 0) { - whTest_LmsSigBuf[0] ^= 0xFF; - ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, msg, (int)msgSz); - whTest_LmsSigBuf[0] ^= 0xFF; if (ret == 0) { - WH_ERROR_PRINT("LMS Verify unexpectedly accepted tampered sig\n"); - ret = -1; - } - else { - ret = 0; + ret = wc_MlKemKey_CipherTextSize(key, &ctLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed to get ML-KEM ct size level=%d ret=%d\n", + levels[i], ret); + } } - } - - /* Wrong message must also fail to verify. */ - if (ret == 0) { - const byte wrongMsg[] = "wolfHSM LMS cryptocb wrong"; - ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, wrongMsg, - (int)(sizeof(wrongMsg) - 1)); if (ret == 0) { - WH_ERROR_PRINT("LMS Verify unexpectedly accepted wrong message\n"); - ret = -1; + ret = wc_MlKemKey_SharedSecretSize(key, &ssEncLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed to get ML-KEM ss size level=%d ret=%d\n", + levels[i], ret); + } + else { + ssDecLen = ssEncLen; + } } - else { - ret = 0; + if (ret == 0) { + ret = wc_MlKemKey_Encapsulate(key, ct, ssEnc, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM encapsulate level=%d ret=%d\n", + levels[i], ret); + } } - } - - /* H=5 means 32 sigs total; after one sign, the key is still not - * exhausted. */ - if (ret == 0) { - if (wc_LmsKey_SigsLeft(key) == 0) { - WH_ERROR_PRINT("LMS reported exhausted after one sign\n"); - ret = -1; + if (ret == 0) { + ret = wc_MlKemKey_Decapsulate(key, ssDec, ct, ctLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM decapsulate level=%d ret=%d\n", + levels[i], ret); + } + else if ((ssEncLen != ssDecLen) || + (memcmp(ssEnc, ssDec, ssEncLen) != 0)) { + WH_ERROR_PRINT("ML-KEM shared secret mismatch level=%d\n", + levels[i]); + ret = -1; + } } - } - if (keyInited) { - whKeyId evictId = WH_KEYID_ERASED; - if ((wh_Client_LmsGetKeyId(key, &evictId) == 0) && - !WH_KEYID_ISERASED(evictId)) { - int evictRet = wh_Client_KeyEvict(ctx, evictId); - if ((evictRet != 0) && (ret == 0)) { - WH_ERROR_PRINT("Failed LMS evict keyId=0x%X ret=%d\n", - (unsigned)evictId, evictRet); - ret = evictRet; - } + if (keyInited) { + wc_MlKemKey_Free(key); } - wc_LmsKey_Free(key); } if (ret == 0) { - WH_TEST_PRINT("LMS CryptoCb DEVID=0x%X SUCCESS\n", devId); + WH_TEST_PRINT("ML-KEM DEVID=0x%X SUCCESS\n", devId); } return ret; } -#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ - -#if defined(WOLFHSM_CFG_DMA) && \ - defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) -/* "XMSS-SHA2_10_256" is the smallest standardized XMSS parameter set - * (height 10, 1024 signatures). pubLen=68, sigLen=2500. */ -#define WH_TEST_XMSS_PARAM_STR "XMSS-SHA2_10_256" -static byte whTest_XmssSigBuf[2500]; -static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, - WC_RNG* rng) +static int whTestCrypto_MlKemClient(whClientContext* ctx, int devId, WC_RNG* rng) { - int ret = 0; - XmssKey key[1]; - int keyInited = 0; - word32 sigLen = 0; - word32 sigCap = 0; - const byte msg[] = "wolfHSM XMSS cryptocb test"; - word32 msgSz = (word32)sizeof(msg) - 1; + int ret = 0; + int levels[3]; + int levelCnt = 0; + int i; + byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ssEnc[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + byte ssWrong[WC_ML_KEM_SS_SZ]; + byte usageCt[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte usageSs[WC_ML_KEM_SS_SZ]; + word32 ctLen; + word32 ssEncLen; + word32 ssDecLen; + word32 ssWrongLen; + word32 usageCtLen; + word32 usageSsLen; + const uint8_t usageLabel[] = "mlkem-no-derive"; (void)rng; - memset(whTest_XmssSigBuf, 0, sizeof(whTest_XmssSigBuf)); - - ret = wc_XmssKey_Init(key, NULL, devId); - if (ret != 0) { - WH_ERROR_PRINT("Failed wc_XmssKey_Init devId=0x%X ret=%d\n", devId, ret); - return ret; - } - keyInited = 1; - - if (ret == 0) { - ret = wc_XmssKey_SetParamStr(key, WH_TEST_XMSS_PARAM_STR); - if (ret != 0) { - WH_ERROR_PRINT("Failed XMSS SetParamStr=\"%s\" ret=%d\n", - WH_TEST_XMSS_PARAM_STR, ret); - } - } - - if (ret == 0) { - ret = wc_XmssKey_GetSigLen(key, &sigCap); - if (ret != 0) { - WH_ERROR_PRINT("Failed XMSS GetSigLen ret=%d\n", ret); - } - else if (sigCap > sizeof(whTest_XmssSigBuf)) { - WH_ERROR_PRINT("XMSS sig buffer too small: need=%u have=%u\n", - (unsigned)sigCap, - (unsigned)sizeof(whTest_XmssSigBuf)); - ret = BUFFER_E; - } - } - - /* MakeKey via cryptocb: server caches private key (ephemeral) and - * returns the public key over DMA. */ - if (ret == 0) { - ret = wc_XmssKey_MakeKey(key, rng); - if (ret != 0) { - WH_ERROR_PRINT("Failed XMSS MakeKey ret=%d\n", ret); - } - } - - /* wc_XmssKey_SigsLeft returns a boolean: nonzero = signatures available, - * 0 = exhausted. */ - if (ret == 0) { - if (wc_XmssKey_SigsLeft(key) == 0) { - WH_ERROR_PRINT("XMSS reported exhausted on fresh key\n"); - ret = -1; - } - } - - if (ret == 0) { - sigLen = sigCap; - ret = wc_XmssKey_Sign(key, whTest_XmssSigBuf, &sigLen, msg, (int)msgSz); - if (ret != 0) { - WH_ERROR_PRINT("Failed XMSS Sign ret=%d\n", ret); - } - else if (sigLen != sigCap) { - WH_ERROR_PRINT("XMSS Sign produced unexpected length=%u expected=%u\n", - (unsigned)sigLen, (unsigned)sigCap); - ret = -1; - } - } - - if (ret == 0) { - ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, msg, (int)msgSz); - if (ret != 0) { - WH_ERROR_PRINT("Failed XMSS Verify ret=%d\n", ret); - } - } - - if (ret == 0) { - whTest_XmssSigBuf[0] ^= 0xFF; - ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, msg, (int)msgSz); - whTest_XmssSigBuf[0] ^= 0xFF; - if (ret == 0) { - WH_ERROR_PRINT("XMSS Verify unexpectedly accepted tampered sig\n"); - ret = -1; - } - else { - ret = 0; - } - } - - if (ret == 0) { - const byte wrongMsg[] = "wolfHSM XMSS cryptocb wrong"; - ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, wrongMsg, - (int)(sizeof(wrongMsg) - 1)); - if (ret == 0) { - WH_ERROR_PRINT("XMSS Verify unexpectedly accepted wrong message\n"); - ret = -1; - } - else { - ret = 0; - } - } - - /* H=10 means 1024 sigs total; after one sign, the key is still not - * exhausted. */ - if (ret == 0) { - if (wc_XmssKey_SigsLeft(key) == 0) { - WH_ERROR_PRINT("XMSS reported exhausted after one sign\n"); - ret = -1; - } - } - - if (keyInited) { - whKeyId evictId = WH_KEYID_ERASED; - if ((wh_Client_XmssGetKeyId(key, &evictId) == 0) && - !WH_KEYID_ISERASED(evictId)) { - int evictRet = wh_Client_KeyEvict(ctx, evictId); - if ((evictRet != 0) && (ret == 0)) { - WH_ERROR_PRINT("Failed XMSS evict keyId=0x%X ret=%d\n", - (unsigned)evictId, evictRet); - ret = evictRet; - } - } - wc_XmssKey_Free(key); - } - - if (ret == 0) { - WH_TEST_PRINT("XMSS CryptoCb DEVID=0x%X SUCCESS\n", devId); - } - - return ret; -} -#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ - -/* Test key usage policy enforcement for various crypto operations */ -int whTest_CryptoKeyUsagePolicies(whClientContext* client, WC_RNG* rng) -{ - int count = 0; - -#ifndef WOLFSSL_NO_ML_KEM_512 - if (count < maxLevels) { - levels[count++] = WC_ML_KEM_512; - } -#endif -#ifndef WOLFSSL_NO_ML_KEM_768 - if (count < maxLevels) { - levels[count++] = WC_ML_KEM_768; - } -#endif -#ifndef WOLFSSL_NO_ML_KEM_1024 - if (count < maxLevels) { - levels[count++] = WC_ML_KEM_1024; - } -#endif - - return count; -} - -static int whTestCrypto_MlKemWolfCrypt(whClientContext* ctx, int devId, - WC_RNG* rng) -{ - int ret = 0; - int levels[3]; - int levelCnt = 0; - int i; - byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; - byte ssEnc[WC_ML_KEM_SS_SZ]; - byte ssDec[WC_ML_KEM_SS_SZ]; - word32 ctLen; - word32 ssEncLen; - word32 ssDecLen; - - (void)ctx; - - levelCnt = - whTestCrypto_MlKemGetLevels(levels, (int)(sizeof(levels) / sizeof(levels[0]))); - - for (i = 0; (ret == 0) && (i < levelCnt); i++) { - MlKemKey key[1]; - int keyInited = 0; - - ctLen = sizeof(ct); - ssEncLen = sizeof(ssEnc); - ssDecLen = sizeof(ssDec); - memset(ct, 0, sizeof(ct)); - memset(ssEnc, 0, sizeof(ssEnc)); - memset(ssDec, 0, sizeof(ssDec)); - - ret = wc_MlKemKey_Init(key, levels[i], NULL, devId); - if (ret != 0) { - WH_ERROR_PRINT("Failed to init ML-KEM key level=%d ret=%d\n", - levels[i], ret); - break; - } - keyInited = 1; - - ret = wc_MlKemKey_MakeKey(key, rng); - if (ret != 0) { - WH_ERROR_PRINT("Failed to make ML-KEM key level=%d ret=%d\n", - levels[i], ret); - } - if (ret == 0) { - ret = wc_MlKemKey_CipherTextSize(key, &ctLen); - if (ret != 0) { - WH_ERROR_PRINT("Failed to get ML-KEM ct size level=%d ret=%d\n", - levels[i], ret); - } - } - if (ret == 0) { - ret = wc_MlKemKey_SharedSecretSize(key, &ssEncLen); - if (ret != 0) { - WH_ERROR_PRINT("Failed to get ML-KEM ss size level=%d ret=%d\n", - levels[i], ret); - } - else { - ssDecLen = ssEncLen; - } - } - if (ret == 0) { - ret = wc_MlKemKey_Encapsulate(key, ct, ssEnc, rng); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM encapsulate level=%d ret=%d\n", - levels[i], ret); - } - } - if (ret == 0) { - ret = wc_MlKemKey_Decapsulate(key, ssDec, ct, ctLen); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM decapsulate level=%d ret=%d\n", - levels[i], ret); - } - else if ((ssEncLen != ssDecLen) || - (memcmp(ssEnc, ssDec, ssEncLen) != 0)) { - WH_ERROR_PRINT("ML-KEM shared secret mismatch level=%d\n", - levels[i]); - ret = -1; - } - } - - if (keyInited) { - wc_MlKemKey_Free(key); - } - } - - if (ret == 0) { - WH_TEST_PRINT("ML-KEM DEVID=0x%X SUCCESS\n", devId); - } - - return ret; -} - -static int whTestCrypto_MlKemClient(whClientContext* ctx, int devId, WC_RNG* rng) -{ - int ret = 0; - int levels[3]; - int levelCnt = 0; - int i; - byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; - byte ssEnc[WC_ML_KEM_SS_SZ]; - byte ssDec[WC_ML_KEM_SS_SZ]; - byte ssWrong[WC_ML_KEM_SS_SZ]; - byte usageCt[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; - byte usageSs[WC_ML_KEM_SS_SZ]; - word32 ctLen; - word32 ssEncLen; - word32 ssDecLen; - word32 ssWrongLen; - word32 usageCtLen; - word32 usageSsLen; - const uint8_t usageLabel[] = "mlkem-no-derive"; - - (void)rng; - - levelCnt = - whTestCrypto_MlKemGetLevels(levels, (int)(sizeof(levels) / sizeof(levels[0]))); + levelCnt = + whTestCrypto_MlKemGetLevels(levels, (int)(sizeof(levels) / sizeof(levels[0]))); for (i = 0; (ret == 0) && (i < levelCnt); i++) { MlKemKey key[1]; @@ -13312,211 +13012,509 @@ static int whTestCrypto_MlKemDmaClient(whClientContext* ctx, int devId, levels[i], ret); } } - if (ret == 0) { - ret = wh_Client_MlKemMakeExportKeyDma(ctx, levels[i], wrongKey); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA wrong keygen level=%d ret=%d\n", - levels[i], ret); - } + if (ret == 0) { + ret = wh_Client_MlKemMakeExportKeyDma(ctx, levels[i], wrongKey); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA wrong keygen level=%d ret=%d\n", + levels[i], ret); + } + } + + if (ret == 0) { + ret = wh_Crypto_MlKemSerializeKey(key, keyBuf1Len, keyBuf1, + &keyBuf1Len); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA serialize key level=%d " + "ret=%d\n", + levels[i], ret); + } + } + if (ret == 0) { + ret = wh_Client_MlKemImportKeyDma( + ctx, key, &keyId, WH_NVM_FLAGS_NONE, + (uint16_t)strlen((const char*)cacheLabel), (uint8_t*)cacheLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA import key level=%d ret=%d\n", + levels[i], ret); + } + else { + keyCached = 1; + } + } + if (ret == 0) { + ret = wh_Client_MlKemExportKeyDma( + ctx, keyId, importedKey, + (uint16_t)strlen((const char*)cacheLabel), (uint8_t*)cacheLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA export key level=%d ret=%d\n", + levels[i], ret); + } + } + if (ret == 0) { + ret = wh_Crypto_MlKemSerializeKey(importedKey, keyBuf2Len, keyBuf2, + &keyBuf2Len); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA serialize imported key " + "level=%d ret=%d\n", + levels[i], ret); + } + else if ((keyBuf1Len != keyBuf2Len) || + (memcmp(keyBuf1, keyBuf2, keyBuf1Len) != 0)) { + WH_ERROR_PRINT("ML-KEM DMA imported key mismatch level=%d\n", + levels[i]); + ret = -1; + } + } + + if (ret == 0) { + ret = wh_Client_MlKemEncapsulateDma(ctx, key, ct, &ctLen, ssEnc, + &ssEncLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA encapsulate level=%d ret=%d\n", + levels[i], ret); + } + } + if (ret == 0) { + ret = wh_Client_MlKemDecapsulateDma(ctx, key, ct, ctLen, ssDec, + &ssDecLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA decapsulate level=%d ret=%d\n", + levels[i], ret); + } + else if ((ssEncLen != ssDecLen) || + (memcmp(ssEnc, ssDec, ssEncLen) != 0)) { + WH_ERROR_PRINT("ML-KEM DMA shared secret mismatch level=%d\n", + levels[i]); + ret = -1; + } + } + if (ret == 0) { + ret = wh_Client_MlKemDecapsulateDma(ctx, wrongKey, ct, ctLen, + ssWrong, &ssWrongLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA wrong-key decaps level=%d " + "ret=%d\n", + levels[i], ret); + } + else if ((ssWrongLen == ssEncLen) && + (memcmp(ssWrong, ssEnc, ssEncLen) == 0)) { + WH_ERROR_PRINT("ML-KEM DMA wrong-key decaps unexpectedly " + "matched level=%d\n", + levels[i]); + ret = -1; + } + } + + /* Usage policy enforcement: key without derive should be denied */ + if (ret == 0) { + MlKemKey usageKey[1]; + whKeyId usageKeyId = WH_KEYID_ERASED; + int usageInited = 0; + int usageKeyCached = 0; + const uint8_t usageLabel[] = "mlkem-dma-nouse"; + + ret = wh_Client_MlKemMakeCacheKey( + ctx, levels[i], &usageKeyId, WH_NVM_FLAGS_NONE, + (uint16_t)strlen((const char*)usageLabel), + (uint8_t*)usageLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed ML-KEM DMA cache key without derive " + "level=%d ret=%d\n", + levels[i], ret); + } + else { + usageKeyCached = 1; + } + if (ret == 0) { + ret = wc_MlKemKey_Init(usageKey, levels[i], NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed init ML-KEM DMA usage key " + "level=%d ret=%d\n", + levels[i], ret); + } + else { + usageInited = 1; + } + } + if (ret == 0) { + ret = wh_Client_MlKemSetKeyId(usageKey, usageKeyId); + } + if (ret == 0) { + word32 tmpCtLen = sizeof(ct); + word32 tmpSsLen = sizeof(ssEnc); + ret = wh_Client_MlKemEncapsulateDma(ctx, usageKey, ct, + &tmpCtLen, ssEnc, + &tmpSsLen); + if (ret == WH_ERROR_USAGE) { + ret = 0; /* Expected */ + } + else { + WH_ERROR_PRINT("Expected WH_ERROR_USAGE for ML-KEM DMA " + "derive policy encaps level=%d got=%d\n", + levels[i], ret); + ret = WH_ERROR_ABORTED; + } + } + /* Negative test: DMA decapsulate with key lacking derive usage */ + if (ret == 0) { + byte dummyCt[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE] = {0}; + word32 dummySsLen = sizeof(ssEnc); + ret = wh_Client_MlKemDecapsulateDma( + ctx, usageKey, dummyCt, + sizeof(dummyCt), ssEnc, &dummySsLen); + if (ret == WH_ERROR_USAGE) { + ret = 0; /* Expected */ + } + else { + WH_ERROR_PRINT("Expected WH_ERROR_USAGE for ML-KEM DMA " + "derive policy decaps level=%d got=%d\n", + levels[i], ret); + ret = WH_ERROR_ABORTED; + } + } + if (usageKeyCached) { + int evictRet = wh_Client_KeyEvict(ctx, usageKeyId); + if ((evictRet != 0) && (ret == 0)) { + WH_ERROR_PRINT("Failed ML-KEM DMA usage key evict " + "level=%d ret=%d\n", + levels[i], evictRet); + ret = evictRet; + } + } + if (usageInited) { + wc_MlKemKey_Free(usageKey); + } + } + + if (keyCached) { + int evictRet = wh_Client_KeyEvict(ctx, keyId); + if ((evictRet != 0) && (ret == 0)) { + WH_ERROR_PRINT("Failed ML-KEM DMA evict cached key level=%d " + "ret=%d\n", + levels[i], evictRet); + ret = evictRet; + } + } + if (wrongInited) { + wc_MlKemKey_Free(wrongKey); + } + if (importedInited) { + wc_MlKemKey_Free(importedKey); + } + if (keyInited) { + wc_MlKemKey_Free(key); + } + } + + if (ret == 0) { + WH_TEST_PRINT("ML-KEM Client DMA API SUCCESS\n"); + } + + return ret; +} +#endif /* WOLFHSM_CFG_DMA */ +#endif /* !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) */ +#endif /* WOLFSSL_HAVE_MLKEM */ + +#if defined(WOLFHSM_CFG_DMA) && \ + defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) +/* L=1, H=5, W=8 keeps the signature ~1.3 KB and gives 2^5 = 32 signatures. */ +#define WH_TEST_LMS_LEVELS (1) +#define WH_TEST_LMS_HEIGHT (5) +#define WH_TEST_LMS_WINTERNITZ (8) +/* Generous buffer that fits L1_H5_W8 (~1328) and any W<8 variant of the same + * height (W=1 ~8688). Keeps off the stack so ASAN builds stay happy. */ +static byte whTest_LmsSigBuf[8800]; + +static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + LmsKey key[1]; + int keyInited = 0; + word32 sigLen = 0; + word32 sigCap = 0; + const byte msg[] = "wolfHSM LMS cryptocb test"; + word32 msgSz = (word32)sizeof(msg) - 1; + + (void)rng; + + memset(whTest_LmsSigBuf, 0, sizeof(whTest_LmsSigBuf)); + + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed wc_LmsKey_Init devId=0x%X ret=%d\n", devId, ret); + return ret; + } + keyInited = 1; + + if (ret == 0) { + ret = wc_LmsKey_SetParameters(key, WH_TEST_LMS_LEVELS, + WH_TEST_LMS_HEIGHT, + WH_TEST_LMS_WINTERNITZ); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS SetParameters ret=%d\n", ret); + } + } + + if (ret == 0) { + ret = wc_LmsKey_GetSigLen(key, &sigCap); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS GetSigLen ret=%d\n", ret); + } + else if (sigCap > sizeof(whTest_LmsSigBuf)) { + WH_ERROR_PRINT("LMS sig buffer too small: need=%u have=%u\n", + (unsigned)sigCap, + (unsigned)sizeof(whTest_LmsSigBuf)); + ret = BUFFER_E; + } + } + + /* MakeKey via cryptocb: server caches private key (ephemeral) and + * returns the public key over DMA. */ + if (ret == 0) { + ret = wc_LmsKey_MakeKey(key, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS MakeKey ret=%d\n", ret); + } + } + + /* wc_LmsKey_SigsLeft returns a boolean: nonzero = signatures available, + * 0 = exhausted. Fresh key should report nonzero. */ + if (ret == 0) { + if (wc_LmsKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("LMS reported exhausted on fresh key\n"); + ret = -1; + } + } + + /* Sign via cryptocb. */ + if (ret == 0) { + sigLen = sigCap; + ret = wc_LmsKey_Sign(key, whTest_LmsSigBuf, &sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS Sign ret=%d\n", ret); + } + else if (sigLen != sigCap) { + WH_ERROR_PRINT("LMS Sign produced unexpected length=%u expected=%u\n", + (unsigned)sigLen, (unsigned)sigCap); + ret = -1; + } + } + + /* Verify the signature via cryptocb. */ + if (ret == 0) { + ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed LMS Verify ret=%d\n", ret); + } + } + + /* Tampered signature must fail to verify. */ + if (ret == 0) { + whTest_LmsSigBuf[0] ^= 0xFF; + ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, msg, (int)msgSz); + whTest_LmsSigBuf[0] ^= 0xFF; + if (ret == 0) { + WH_ERROR_PRINT("LMS Verify unexpectedly accepted tampered sig\n"); + ret = -1; + } + else { + ret = 0; + } + } + + /* Wrong message must also fail to verify. */ + if (ret == 0) { + const byte wrongMsg[] = "wolfHSM LMS cryptocb wrong"; + ret = wc_LmsKey_Verify(key, whTest_LmsSigBuf, sigLen, wrongMsg, + (int)(sizeof(wrongMsg) - 1)); + if (ret == 0) { + WH_ERROR_PRINT("LMS Verify unexpectedly accepted wrong message\n"); + ret = -1; + } + else { + ret = 0; + } + } + + /* H=5 means 32 sigs total; after one sign, the key is still not + * exhausted. */ + if (ret == 0) { + if (wc_LmsKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("LMS reported exhausted after one sign\n"); + ret = -1; + } + } + + if (keyInited) { + whKeyId evictId = WH_KEYID_ERASED; + if ((wh_Client_LmsGetKeyId(key, &evictId) == 0) && + !WH_KEYID_ISERASED(evictId)) { + int evictRet = wh_Client_KeyEvict(ctx, evictId); + if ((evictRet != 0) && (ret == 0)) { + WH_ERROR_PRINT("Failed LMS evict keyId=0x%X ret=%d\n", + (unsigned)evictId, evictRet); + ret = evictRet; + } + } + wc_LmsKey_Free(key); + } + + if (ret == 0) { + WH_TEST_PRINT("LMS CryptoCb DEVID=0x%X SUCCESS\n", devId); + } + + return ret; +} +#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + +#if defined(WOLFHSM_CFG_DMA) && \ + defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) +/* "XMSS-SHA2_10_256" is the smallest standardized XMSS parameter set + * (height 10, 1024 signatures). pubLen=68, sigLen=2500. */ +#define WH_TEST_XMSS_PARAM_STR "XMSS-SHA2_10_256" +static byte whTest_XmssSigBuf[2500]; + +static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = 0; + XmssKey key[1]; + int keyInited = 0; + word32 sigLen = 0; + word32 sigCap = 0; + const byte msg[] = "wolfHSM XMSS cryptocb test"; + word32 msgSz = (word32)sizeof(msg) - 1; + + (void)rng; + + memset(whTest_XmssSigBuf, 0, sizeof(whTest_XmssSigBuf)); + + ret = wc_XmssKey_Init(key, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed wc_XmssKey_Init devId=0x%X ret=%d\n", devId, ret); + return ret; + } + keyInited = 1; + + if (ret == 0) { + ret = wc_XmssKey_SetParamStr(key, WH_TEST_XMSS_PARAM_STR); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS SetParamStr=\"%s\" ret=%d\n", + WH_TEST_XMSS_PARAM_STR, ret); + } + } + + if (ret == 0) { + ret = wc_XmssKey_GetSigLen(key, &sigCap); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS GetSigLen ret=%d\n", ret); + } + else if (sigCap > sizeof(whTest_XmssSigBuf)) { + WH_ERROR_PRINT("XMSS sig buffer too small: need=%u have=%u\n", + (unsigned)sigCap, + (unsigned)sizeof(whTest_XmssSigBuf)); + ret = BUFFER_E; } + } - if (ret == 0) { - ret = wh_Crypto_MlKemSerializeKey(key, keyBuf1Len, keyBuf1, - &keyBuf1Len); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA serialize key level=%d " - "ret=%d\n", - levels[i], ret); - } + /* MakeKey via cryptocb: server caches private key (ephemeral) and + * returns the public key over DMA. */ + if (ret == 0) { + ret = wc_XmssKey_MakeKey(key, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS MakeKey ret=%d\n", ret); } - if (ret == 0) { - ret = wh_Client_MlKemImportKeyDma( - ctx, key, &keyId, WH_NVM_FLAGS_NONE, - (uint16_t)strlen((const char*)cacheLabel), (uint8_t*)cacheLabel); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA import key level=%d ret=%d\n", - levels[i], ret); - } - else { - keyCached = 1; - } + } + + /* wc_XmssKey_SigsLeft returns a boolean: nonzero = signatures available, + * 0 = exhausted. */ + if (ret == 0) { + if (wc_XmssKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("XMSS reported exhausted on fresh key\n"); + ret = -1; } - if (ret == 0) { - ret = wh_Client_MlKemExportKeyDma( - ctx, keyId, importedKey, - (uint16_t)strlen((const char*)cacheLabel), (uint8_t*)cacheLabel); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA export key level=%d ret=%d\n", - levels[i], ret); - } + } + + if (ret == 0) { + sigLen = sigCap; + ret = wc_XmssKey_Sign(key, whTest_XmssSigBuf, &sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS Sign ret=%d\n", ret); } - if (ret == 0) { - ret = wh_Crypto_MlKemSerializeKey(importedKey, keyBuf2Len, keyBuf2, - &keyBuf2Len); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA serialize imported key " - "level=%d ret=%d\n", - levels[i], ret); - } - else if ((keyBuf1Len != keyBuf2Len) || - (memcmp(keyBuf1, keyBuf2, keyBuf1Len) != 0)) { - WH_ERROR_PRINT("ML-KEM DMA imported key mismatch level=%d\n", - levels[i]); - ret = -1; - } + else if (sigLen != sigCap) { + WH_ERROR_PRINT("XMSS Sign produced unexpected length=%u expected=%u\n", + (unsigned)sigLen, (unsigned)sigCap); + ret = -1; } + } - if (ret == 0) { - ret = wh_Client_MlKemEncapsulateDma(ctx, key, ct, &ctLen, ssEnc, - &ssEncLen); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA encapsulate level=%d ret=%d\n", - levels[i], ret); - } + if (ret == 0) { + ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, msg, (int)msgSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed XMSS Verify ret=%d\n", ret); } + } + + if (ret == 0) { + whTest_XmssSigBuf[0] ^= 0xFF; + ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, msg, (int)msgSz); + whTest_XmssSigBuf[0] ^= 0xFF; if (ret == 0) { - ret = wh_Client_MlKemDecapsulateDma(ctx, key, ct, ctLen, ssDec, - &ssDecLen); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA decapsulate level=%d ret=%d\n", - levels[i], ret); - } - else if ((ssEncLen != ssDecLen) || - (memcmp(ssEnc, ssDec, ssEncLen) != 0)) { - WH_ERROR_PRINT("ML-KEM DMA shared secret mismatch level=%d\n", - levels[i]); - ret = -1; - } + WH_ERROR_PRINT("XMSS Verify unexpectedly accepted tampered sig\n"); + ret = -1; } - if (ret == 0) { - ret = wh_Client_MlKemDecapsulateDma(ctx, wrongKey, ct, ctLen, - ssWrong, &ssWrongLen); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA wrong-key decaps level=%d " - "ret=%d\n", - levels[i], ret); - } - else if ((ssWrongLen == ssEncLen) && - (memcmp(ssWrong, ssEnc, ssEncLen) == 0)) { - WH_ERROR_PRINT("ML-KEM DMA wrong-key decaps unexpectedly " - "matched level=%d\n", - levels[i]); - ret = -1; - } + else { + ret = 0; } + } - /* Usage policy enforcement: key without derive should be denied */ + if (ret == 0) { + const byte wrongMsg[] = "wolfHSM XMSS cryptocb wrong"; + ret = wc_XmssKey_Verify(key, whTest_XmssSigBuf, sigLen, wrongMsg, + (int)(sizeof(wrongMsg) - 1)); if (ret == 0) { - MlKemKey usageKey[1]; - whKeyId usageKeyId = WH_KEYID_ERASED; - int usageInited = 0; - int usageKeyCached = 0; - const uint8_t usageLabel[] = "mlkem-dma-nouse"; + WH_ERROR_PRINT("XMSS Verify unexpectedly accepted wrong message\n"); + ret = -1; + } + else { + ret = 0; + } + } - ret = wh_Client_MlKemMakeCacheKey( - ctx, levels[i], &usageKeyId, WH_NVM_FLAGS_NONE, - (uint16_t)strlen((const char*)usageLabel), - (uint8_t*)usageLabel); - if (ret != 0) { - WH_ERROR_PRINT("Failed ML-KEM DMA cache key without derive " - "level=%d ret=%d\n", - levels[i], ret); - } - else { - usageKeyCached = 1; - } - if (ret == 0) { - ret = wc_MlKemKey_Init(usageKey, levels[i], NULL, devId); - if (ret != 0) { - WH_ERROR_PRINT("Failed init ML-KEM DMA usage key " - "level=%d ret=%d\n", - levels[i], ret); - } - else { - usageInited = 1; - } - } - if (ret == 0) { - ret = wh_Client_MlKemSetKeyId(usageKey, usageKeyId); - } - if (ret == 0) { - word32 tmpCtLen = sizeof(ct); - word32 tmpSsLen = sizeof(ssEnc); - ret = wh_Client_MlKemEncapsulateDma(ctx, usageKey, ct, - &tmpCtLen, ssEnc, - &tmpSsLen); - if (ret == WH_ERROR_USAGE) { - ret = 0; /* Expected */ - } - else { - WH_ERROR_PRINT("Expected WH_ERROR_USAGE for ML-KEM DMA " - "derive policy encaps level=%d got=%d\n", - levels[i], ret); - ret = WH_ERROR_ABORTED; - } - } - /* Negative test: DMA decapsulate with key lacking derive usage */ - if (ret == 0) { - byte dummyCt[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE] = {0}; - word32 dummySsLen = sizeof(ssEnc); - ret = wh_Client_MlKemDecapsulateDma( - ctx, usageKey, dummyCt, - sizeof(dummyCt), ssEnc, &dummySsLen); - if (ret == WH_ERROR_USAGE) { - ret = 0; /* Expected */ - } - else { - WH_ERROR_PRINT("Expected WH_ERROR_USAGE for ML-KEM DMA " - "derive policy decaps level=%d got=%d\n", - levels[i], ret); - ret = WH_ERROR_ABORTED; - } - } - if (usageKeyCached) { - int evictRet = wh_Client_KeyEvict(ctx, usageKeyId); - if ((evictRet != 0) && (ret == 0)) { - WH_ERROR_PRINT("Failed ML-KEM DMA usage key evict " - "level=%d ret=%d\n", - levels[i], evictRet); - ret = evictRet; - } - } - if (usageInited) { - wc_MlKemKey_Free(usageKey); - } + /* H=10 means 1024 sigs total; after one sign, the key is still not + * exhausted. */ + if (ret == 0) { + if (wc_XmssKey_SigsLeft(key) == 0) { + WH_ERROR_PRINT("XMSS reported exhausted after one sign\n"); + ret = -1; } + } - if (keyCached) { - int evictRet = wh_Client_KeyEvict(ctx, keyId); + if (keyInited) { + whKeyId evictId = WH_KEYID_ERASED; + if ((wh_Client_XmssGetKeyId(key, &evictId) == 0) && + !WH_KEYID_ISERASED(evictId)) { + int evictRet = wh_Client_KeyEvict(ctx, evictId); if ((evictRet != 0) && (ret == 0)) { - WH_ERROR_PRINT("Failed ML-KEM DMA evict cached key level=%d " - "ret=%d\n", - levels[i], evictRet); + WH_ERROR_PRINT("Failed XMSS evict keyId=0x%X ret=%d\n", + (unsigned)evictId, evictRet); ret = evictRet; } } - if (wrongInited) { - wc_MlKemKey_Free(wrongKey); - } - if (importedInited) { - wc_MlKemKey_Free(importedKey); - } - if (keyInited) { - wc_MlKemKey_Free(key); - } + wc_XmssKey_Free(key); } if (ret == 0) { - WH_TEST_PRINT("ML-KEM Client DMA API SUCCESS\n"); + WH_TEST_PRINT("XMSS CryptoCb DEVID=0x%X SUCCESS\n", devId); } return ret; } -#endif /* WOLFHSM_CFG_DMA */ -#endif /* !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ - !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ - !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) */ -#endif /* WOLFSSL_HAVE_MLKEM */ +#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ /* Test key usage policy enforcement for various crypto operations */ int whTest_CryptoKeyUsagePolicies(whClientContext* client, WC_RNG* rng) @@ -15274,6 +15272,7 @@ int whTest_CryptoClientConfig(whClientConfig* config) !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) */ #endif /* WOLFSSL_HAVE_MLKEM */ + #if defined(WOLFHSM_CFG_DMA) && \ defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) if (ret == 0) { From a6e74cb4ab5872f9fb999cf514665e8575ccc1d2 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 16 Jun 2026 13:22:20 -0700 Subject: [PATCH 03/12] Apply WH_NVM_FLAGS_NONEXPORTABLE to prevent export of lms/xmss priv key --- src/wh_server_crypto.c | 16 ++++++++++------ test/wh_test_crypto.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 2fbfbca42..0e96a16d8 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -1047,9 +1047,11 @@ int wh_Server_LmsKeyCacheImport(whServerContext* ctx, LmsKey* key, ret = wh_Crypto_LmsSerializeKey(key, slotCapacity, cacheBuf, &blobSize); } if (ret == WH_ERROR_OK) { - cacheMeta->id = keyId; - cacheMeta->len = blobSize; - cacheMeta->flags = flags; + cacheMeta->id = keyId; + cacheMeta->len = blobSize; + /* Stateful private key state must never leave the HSM; reuse of a + * one-time signature index breaks the scheme. Force non-exportable. */ + cacheMeta->flags = flags | WH_NVM_FLAGS_NONEXPORTABLE; cacheMeta->access = WH_NVM_ACCESS_ANY; if ((label != NULL) && (label_len > 0)) { memcpy(cacheMeta->label, label, label_len); @@ -1103,9 +1105,11 @@ int wh_Server_XmssKeyCacheImport(whServerContext* ctx, XmssKey* key, &blobSize); } if (ret == WH_ERROR_OK) { - cacheMeta->id = keyId; - cacheMeta->len = blobSize; - cacheMeta->flags = flags; + cacheMeta->id = keyId; + cacheMeta->len = blobSize; + /* Stateful private key state must never leave the HSM; reuse of a + * one-time signature index breaks the scheme. Force non-exportable. */ + cacheMeta->flags = flags | WH_NVM_FLAGS_NONEXPORTABLE; cacheMeta->access = WH_NVM_ACCESS_ANY; if ((label != NULL) && (label_len > 0)) { memcpy(cacheMeta->label, label, label_len); diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 3e827e987..e5ff8844d 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13349,6 +13349,25 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } + /* The generic export API must refuse to return the private key state. + * Keygen forces WH_NVM_FLAGS_NONEXPORTABLE, so export of the resident + * key is denied with WH_ERROR_ACCESS. */ + if (ret == 0) { + whKeyId exportId = WH_KEYID_ERASED; + uint8_t expBuf[256]; + uint16_t expLen = (uint16_t)sizeof(expBuf); + if ((wh_Client_LmsGetKeyId(key, &exportId) == 0) && + !WH_KEYID_ISERASED(exportId)) { + int expRet = + wh_Client_KeyExport(ctx, exportId, NULL, 0, expBuf, &expLen); + if (expRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("LMS export not blocked: ret=%d " + "(expected WH_ERROR_ACCESS)\n", expRet); + ret = (expRet == 0) ? WH_ERROR_ABORTED : expRet; + } + } + } + if (keyInited) { whKeyId evictId = WH_KEYID_ERASED; if ((wh_Client_LmsGetKeyId(key, &evictId) == 0) && @@ -13494,6 +13513,25 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } + /* The generic export API must refuse to return the private key state. + * Keygen forces WH_NVM_FLAGS_NONEXPORTABLE, so export of the resident + * key is denied with WH_ERROR_ACCESS. */ + if (ret == 0) { + whKeyId exportId = WH_KEYID_ERASED; + uint8_t expBuf[256]; + uint16_t expLen = (uint16_t)sizeof(expBuf); + if ((wh_Client_XmssGetKeyId(key, &exportId) == 0) && + !WH_KEYID_ISERASED(exportId)) { + int expRet = + wh_Client_KeyExport(ctx, exportId, NULL, 0, expBuf, &expLen); + if (expRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("XMSS export not blocked: ret=%d " + "(expected WH_ERROR_ACCESS)\n", expRet); + ret = (expRet == 0) ? WH_ERROR_ABORTED : expRet; + } + } + } + if (keyInited) { whKeyId evictId = WH_KEYID_ERASED; if ((wh_Client_XmssGetKeyId(key, &evictId) == 0) && From a3546cdfaf5a65d07e43773abd992ad642815aab Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 16 Jun 2026 15:15:27 -0700 Subject: [PATCH 04/12] Block import of a private LMS/XMSS key --- src/wh_crypto.c | 16 ++++++++ src/wh_server_keystore.c | 22 +++++++++++ src/wh_server_nvm.c | 56 ++++++++++++++++++++------- test/wh_test_crypto.c | 82 ++++++++++++++++++++++++++++++++++++++++ wolfhsm/wh_crypto.h | 6 +++ 5 files changed, 168 insertions(+), 14 deletions(-) diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 7ad7647eb..dd77fbdee 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -517,6 +517,22 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, #define WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS 0x4C4D5301u /* 'LMS\1' */ #define WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS 0x584D5301u /* 'XMS\1' */ +int wh_Crypto_IsStatefulSigBlob(const uint8_t* buffer, uint16_t size) +{ + uint32_t magic; + + if ((buffer == NULL) || (size < sizeof(magic))) { + return 0; + } + /* Magic is stored native-order at offset 0; match what deserialize + * requires before it would accept the blob. */ + memcpy(&magic, buffer, sizeof(magic)); + return ((magic == WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS) || + (magic == WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS)) + ? 1 + : 0; +} + static int _StatefulSigEncodeHeader(uint8_t* buffer, uint32_t magic, uint16_t pubLen, uint16_t privLen, uint16_t paramLen) diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index e7529ab3d..feaf023be 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -712,6 +712,13 @@ static int _KeystoreCacheKey(whServerContext* server, whNvmMetadata* meta, return WH_ERROR_BADARGS; } +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + /* Checked calls must refuse access to the LMX/XMSS private key */ + if (checked && wh_Crypto_IsStatefulSigBlob(in, (uint16_t)meta->len)) { + return WH_ERROR_ACCESS; + } +#endif + if (checked) { ret = wh_Server_KeystoreGetCacheSlotChecked(server, meta->id, meta->len, &slotBuf, &slotMeta); @@ -1751,6 +1758,14 @@ static int _HandleKeyUnwrapAndCacheRequest( /* Store the assigned key ID in the response, preserving client flags */ resp->keyId = wh_KeyId_TranslateToClient(metadata.id); +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + /* Stateful (LMS/XMSS) private key state must never enter the keystore via + * unwrap; that would permit a signature-index roll-back. */ + if (wh_Crypto_IsStatefulSigBlob(key, (uint16_t)metadata.len)) { + return WH_ERROR_ACCESS; + } +#endif + /* Cache the key */ return wh_Server_KeystoreCacheKey(server, &metadata, key); } @@ -2815,6 +2830,13 @@ int _KeystoreCacheKeyDma(whServerContext* server, whNvmMetadata* meta, /* Copy key data using DMA */ ret = whServerDma_CopyFromClient(server, buffer, keyAddr, meta->len, (whServerDmaFlags){0}); +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + /* Checked calls must refuse access to the LMX/XMSS private key */ + if ((ret == 0) && checked && + wh_Crypto_IsStatefulSigBlob(buffer, (uint16_t)meta->len)) { + ret = WH_ERROR_ACCESS; + } +#endif if (ret != 0) { /* Clear the slot on error */ memset(buffer, 0, meta->len); diff --git a/src/wh_server_nvm.c b/src/wh_server_nvm.c index f5597b6b8..236ed4f37 100644 --- a/src/wh_server_nvm.c +++ b/src/wh_server_nvm.c @@ -43,6 +43,11 @@ #include "wolfhsm/wh_server.h" #include "wolfhsm/wh_server_nvm.h" +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && \ + (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) +#include "wolfhsm/wh_crypto.h" +#endif + /* Handle NVM read, do access checking and clamping */ static int _HandleNvmRead(whServerContext* server, uint8_t* out_data, whNvmSize offset, whNvmSize len, whNvmSize* out_len, @@ -255,13 +260,24 @@ int wh_Server_HandleNvmRequest(whServerContext* server, meta.len = req.len; memcpy(meta.label, req.label, sizeof(meta.label)); - rc = WH_SERVER_NVM_LOCK(server); + rc = WH_ERROR_OK; +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && \ + (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) + /* Block direct NVM import of stateful (LMS/XMSS) private key + * state; only on-HSM keygen may create such objects. */ + if (wh_Crypto_IsStatefulSigBlob(data, (uint16_t)req.len)) { + rc = WH_ERROR_ACCESS; + } +#endif if (rc == WH_ERROR_OK) { - rc = wh_Nvm_AddObjectChecked(server->nvm, &meta, req.len, - data); + rc = WH_SERVER_NVM_LOCK(server); + if (rc == WH_ERROR_OK) { + rc = wh_Nvm_AddObjectChecked(server->nvm, &meta, + req.len, data); - (void)WH_SERVER_NVM_UNLOCK(server); - } /* WH_SERVER_NVM_LOCK() */ + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + } resp.rc = rc; } } @@ -378,16 +394,28 @@ int wh_Server_HandleNvmRequest(whServerContext* server, } } if (resp.rc == 0) { - rc = WH_SERVER_NVM_LOCK(server); - if (rc == WH_ERROR_OK) { - /* Process the AddObject action */ - rc = wh_Nvm_AddObjectChecked( - server->nvm, (whNvmMetadata*)metadata, req.data_len, - (const uint8_t*)data); +#if !defined(WOLFHSM_CFG_NO_CRYPTO) && \ + (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) + /* Block direct NVM import of stateful (LMS/XMSS) private key state; + * only on-HSM keygen may create such objects. */ + if (wh_Crypto_IsStatefulSigBlob((const uint8_t*)data, + (uint16_t)req.data_len)) { + resp.rc = WH_ERROR_ACCESS; + } + else +#endif + { + rc = WH_SERVER_NVM_LOCK(server); + if (rc == WH_ERROR_OK) { + /* Process the AddObject action */ + rc = wh_Nvm_AddObjectChecked( + server->nvm, (whNvmMetadata*)metadata, req.data_len, + (const uint8_t*)data); - (void)WH_SERVER_NVM_UNLOCK(server); - } /* WH_SERVER_NVM_LOCK() */ - resp.rc = rc; + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + resp.rc = rc; + } } /* Always call POST for successful PREs, regardless of operation * result */ diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index e5ff8844d..472f05e9c 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13368,6 +13368,47 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } + /* Attempt to import an LMS key which must be rejected */ + if (ret == 0) { + uint8_t fakeBlob[64]; + uint32_t lmsMagic = 0x4C4D5301u; /* 'LMS\1', see wh_crypto.c */ + whKeyId impId = WH_KEYID_ERASED; + int impRet; + memset(fakeBlob, 0, sizeof(fakeBlob)); + memcpy(fakeBlob, &lmsMagic, sizeof(lmsMagic)); + impRet = wh_Client_KeyCache(ctx, 0, NULL, 0, fakeBlob, + (uint16_t)sizeof(fakeBlob), &impId); + if (impRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("LMS blob import not blocked: ret=%d " + "(expected WH_ERROR_ACCESS)\n", impRet); + if ((impRet == 0) && !WH_KEYID_ISERASED(impId)) { + (void)wh_Client_KeyEvict(ctx, impId); + } + ret = (impRet == 0) ? WH_ERROR_ABORTED : impRet; + } + } + + /* Also ensure direct NVM import is blocked */ + if (ret == 0) { + uint8_t fakeBlob[64]; + uint32_t lmsMagic = 0x4C4D5301u; /* 'LMS\1', see wh_crypto.c */ + int32_t addRc = 0; + int addRet; + whNvmId addId = 0x1042; /* An arbitrary ID in the NVM range */ + memset(fakeBlob, 0, sizeof(fakeBlob)); + memcpy(fakeBlob, &lmsMagic, sizeof(lmsMagic)); + addRet = wh_Client_NvmAddObject(ctx, addId, WH_NVM_ACCESS_ANY, + WH_NVM_FLAGS_NONE, 0, NULL, + (whNvmSize)sizeof(fakeBlob), fakeBlob, + &addRc); + if ((addRet != WH_ERROR_OK) || (addRc != WH_ERROR_ACCESS)) { + WH_ERROR_PRINT("LMS blob NVM import not blocked: ret=%d rc=%d " + "(expected rc WH_ERROR_ACCESS)\n", addRet, + (int)addRc); + ret = (addRc != 0) ? addRc : WH_ERROR_ABORTED + } + } + if (keyInited) { whKeyId evictId = WH_KEYID_ERASED; if ((wh_Client_LmsGetKeyId(key, &evictId) == 0) && @@ -13532,6 +13573,47 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } + /* Attempt to import an XMSS key which must be rejected */ + if (ret == 0) { + uint8_t fakeBlob[64]; + uint32_t xmssMagic = 0x584D5301u; /* 'XMS\1', see wh_crypto.c */ + whKeyId impId = WH_KEYID_ERASED; + int impRet; + memset(fakeBlob, 0, sizeof(fakeBlob)); + memcpy(fakeBlob, &xmssMagic, sizeof(xmssMagic)); + impRet = wh_Client_KeyCache(ctx, 0, NULL, 0, fakeBlob, + (uint16_t)sizeof(fakeBlob), &impId); + if (impRet != WH_ERROR_ACCESS) { + WH_ERROR_PRINT("XMSS blob import not blocked: ret=%d " + "(expected WH_ERROR_ACCESS)\n", impRet); + if ((impRet == 0) && !WH_KEYID_ISERASED(impId)) { + (void)wh_Client_KeyEvict(ctx, impId); + } + ret = (impRet == 0) ? WH_ERROR_ABORTED : impRet; + } + } + + /* Also ensure direct NVM import is blocked */ + if (ret == 0) { + uint8_t fakeBlob[64]; + uint32_t xmssMagic = 0x584D5301u; /* 'XMS\1', see wh_crypto.c */ + int32_t addRc = 0; + int addRet; + whNvmId addId = 0x1042; /* An arbitrary ID in the NVM range */ + memset(fakeBlob, 0, sizeof(fakeBlob)); + memcpy(fakeBlob, &xmssMagic, sizeof(xmssMagic)); + addRet = wh_Client_NvmAddObject(ctx, addId, WH_NVM_ACCESS_ANY, + WH_NVM_FLAGS_NONE, 0, NULL, + (whNvmSize)sizeof(fakeBlob), fakeBlob, + &addRc); + if ((addRet != WH_ERROR_OK) || (addRc != WH_ERROR_ACCESS)) { + WH_ERROR_PRINT("XMSS blob NVM import not blocked: ret=%d rc=%d " + "(expected rc WH_ERROR_ACCESS)\n", addRet, + (int)addRc); + ret = (addRc != 0) ? addRc : WH_ERROR_ABORTED + } + } + if (keyInited) { whKeyId evictId = WH_KEYID_ERASED; if ((wh_Client_XmssGetKeyId(key, &evictId) == 0) && diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 41dd3baa8..03da44aa3 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -135,6 +135,12 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, * server bridge can locate the variable-length sections that follow it. The * full blob layout is documented in wh_crypto.c. */ #define WH_CRYPTO_STATEFUL_SIG_HEADER_SZ 12 + +/* Returns 1 if buffer begins with an LMS/XMSS stateful-sig slot-blob magic, + * else 0. Used to reject client attempts to import (and thereby roll back) + * stateful private key state through the generic keystore/NVM paths. Only the + * on-HSM keygen may produce these blobs. */ +int wh_Crypto_IsStatefulSigBlob(const uint8_t* buffer, uint16_t size); #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS From 5285fcd67fe6aedd41a52d09a75f8ecde818e47c Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 16 Jun 2026 15:16:44 -0700 Subject: [PATCH 05/12] Add public key export via wh_Client_KeyExportPublic with WH_KEY_ALGO_LMS and WH_KEY_ALGO_XMSS --- src/wh_server_keystore.c | 90 ++++++++++++++++++++++++++++++++++++++ test/wh_test_crypto.c | 50 +++++++++++++++++++++ wolfhsm/wh_client_crypto.h | 6 +++ wolfhsm/wh_common.h | 2 + 4 files changed, 148 insertions(+) diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index feaf023be..587f1326e 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -70,6 +70,12 @@ #ifdef WOLFSSL_HAVE_MLKEM #include "wolfssl/wolfcrypt/wc_mlkem.h" #endif +#ifdef WOLFSSL_HAVE_LMS +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif +#ifdef WOLFSSL_HAVE_XMSS +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif static int _FindInCache(whServerContext* server, whKeyId keyId, int* out_index, int* out_big, uint8_t** out_buffer, @@ -588,6 +594,66 @@ static int _ExportMlkemPublicKey(whServerContext* server, whKeyId keyId, } #endif /* WOLFSSL_HAVE_MLKEM */ +#ifdef WOLFSSL_HAVE_LMS +/* Emit the raw LMS public key for a cached/committed key. Stateful private + * state stays in the HSM; only the public bytes leave. */ +static int _ExportLmsPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret; + LmsKey key[1]; + word32 pubLen = 0; + + ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_LmsKeyCacheExport(server, keyId, key); + if (ret == WH_ERROR_OK) { + ret = wc_LmsKey_GetPubLen(key, &pubLen); + } + if (ret == WH_ERROR_OK) { + if (pubLen > (word32)*outSz) { + ret = WH_ERROR_NOSPACE; + } + else { + memcpy(out, key->pub, pubLen); + *outSz = (uint16_t)pubLen; + } + } + wc_LmsKey_Free(key); + } + return ret; +} +#endif /* WOLFSSL_HAVE_LMS */ + +#ifdef WOLFSSL_HAVE_XMSS +static int _ExportXmssPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret; + XmssKey key[1]; + word32 pubLen = 0; + + ret = wc_XmssKey_Init(key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Server_XmssKeyCacheExport(server, keyId, key); + if (ret == WH_ERROR_OK) { + ret = wc_XmssKey_GetPubLen(key, &pubLen); + } + if (ret == WH_ERROR_OK) { + if (pubLen > (word32)*outSz) { + ret = WH_ERROR_NOSPACE; + } + else { + memcpy(out, key->pk, pubLen); + *outSz = (uint16_t)pubLen; + } + } + wc_XmssKey_Free(key); + } + return ret; +} +#endif /* WOLFSSL_HAVE_XMSS */ + int wh_Server_KeystoreGetUniqueId(whServerContext* server, whNvmId* inout_id) { int ret = WH_ERROR_OK; @@ -2195,6 +2261,18 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, stage, &stageMax); break; #endif /* WOLFSSL_HAVE_MLKEM */ + #ifdef WOLFSSL_HAVE_LMS + case WH_KEY_ALGO_LMS: + ret = _ExportLmsPublicKey(server, serverKeyId, + stage, &stageMax); + break; + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case WH_KEY_ALGO_XMSS: + ret = _ExportXmssPublicKey(server, serverKeyId, + stage, &stageMax); + break; + #endif /* WOLFSSL_HAVE_XMSS */ default: ret = WH_ERROR_BADARGS; break; @@ -2398,6 +2476,18 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, out, &max_der); break; #endif /* WOLFSSL_HAVE_MLKEM */ + #ifdef WOLFSSL_HAVE_LMS + case WH_KEY_ALGO_LMS: + ret = _ExportLmsPublicKey(server, serverKeyId, + out, &max_der); + break; + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case WH_KEY_ALGO_XMSS: + ret = _ExportXmssPublicKey(server, serverKeyId, + out, &max_der); + break; + #endif /* WOLFSSL_HAVE_XMSS */ default: ret = WH_ERROR_BADARGS; break; diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 472f05e9c..2db04c888 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13349,6 +13349,31 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } + /* Verify the public key matches when read back */ + if (ret == 0) { + whKeyId pubId = WH_KEYID_ERASED; + word32 pubLen = 0; + uint8_t pubBuf[128]; + uint16_t pubBufLen = (uint16_t)sizeof(pubBuf); + if ((wh_Client_LmsGetKeyId(key, &pubId) == 0) && + !WH_KEYID_ISERASED(pubId) && + (wc_LmsKey_GetPubLen(key, &pubLen) == 0) && + (pubLen <= sizeof(pubBuf))) { + int pubRet = wh_Client_KeyExportPublic(ctx, pubId, WH_KEY_ALGO_LMS, + NULL, 0, pubBuf, &pubBufLen); + if (pubRet != WH_ERROR_OK) { + WH_ERROR_PRINT("LMS export pub failed: ret=%d\n", pubRet); + ret = pubRet; + } + else if (((word32)pubBufLen != pubLen) || + (memcmp(pubBuf, key->pub, pubLen) != 0)) { + WH_ERROR_PRINT("LMS export pub mismatch len=%u expected=%u\n", + (unsigned)pubBufLen, (unsigned)pubLen); + ret = WH_ERROR_ABORTED; + } + } + } + /* The generic export API must refuse to return the private key state. * Keygen forces WH_NVM_FLAGS_NONEXPORTABLE, so export of the resident * key is denied with WH_ERROR_ACCESS. */ @@ -13554,6 +13579,31 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } + /* Verify the public key matches when read back */ + if (ret == 0) { + whKeyId pubId = WH_KEYID_ERASED; + word32 pubLen = 0; + uint8_t pubBuf[128]; + uint16_t pubBufLen = (uint16_t)sizeof(pubBuf); + if ((wh_Client_XmssGetKeyId(key, &pubId) == 0) && + !WH_KEYID_ISERASED(pubId) && + (wc_XmssKey_GetPubLen(key, &pubLen) == 0) && + (pubLen <= sizeof(pubBuf))) { + int pubRet = wh_Client_KeyExportPublic(ctx, pubId, WH_KEY_ALGO_XMSS, + NULL, 0, pubBuf, &pubBufLen); + if (pubRet != WH_ERROR_OK) { + WH_ERROR_PRINT("XMSS export pub failed: ret=%d\n", pubRet); + ret = pubRet; + } + else if (((word32)pubBufLen != pubLen) || + (memcmp(pubBuf, key->pk, pubLen) != 0)) { + WH_ERROR_PRINT("XMSS export pub mismatch len=%u expected=%u\n", + (unsigned)pubBufLen, (unsigned)pubLen); + ret = WH_ERROR_ABORTED; + } + } + } + /* The generic export API must refuse to return the private key state. * Keygen forces WH_NVM_FLAGS_NONEXPORTABLE, so export of the resident * key is denied with WH_ERROR_ACCESS. */ diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index c60fb61f9..e10264232 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3211,6 +3211,12 @@ int wh_Client_MlKemDecapsulateDma(whClientContext* ctx, MlKemKey* key, #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) #ifdef WOLFHSM_CFG_DMA +/* The raw public key is returned via DMA at keygen time (see the MakeKey + * functions below). To retrieve it again later from just a keyId, use the + * generic wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_LMS or + * WH_KEY_ALGO_XMSS, ...). The private state is non-exportable and cannot be + * read back by any path. */ + #ifdef WOLFSSL_HAVE_LMS /* Bind / read the wolfHSM key id stored in key->devCtx. */ diff --git a/wolfhsm/wh_common.h b/wolfhsm/wh_common.h index 73aa48c03..c58dcc22b 100644 --- a/wolfhsm/wh_common.h +++ b/wolfhsm/wh_common.h @@ -156,6 +156,8 @@ enum WH_KEY_ALGO_ENUM { WH_KEY_ALGO_ED25519 = 4, WH_KEY_ALGO_MLDSA = 5, WH_KEY_ALGO_MLKEM = 6, + WH_KEY_ALGO_LMS = 7, + WH_KEY_ALGO_XMSS = 8, }; #endif /* !WOLFHSM_WH_COMMON_H_ */ From e72d2515236d7c4c3c7dac6c0ebd0fed69b515ce Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 16 Jun 2026 16:11:49 -0700 Subject: [PATCH 06/12] Add public key import via wh_Client_LmsImportPubKey and wh_Client_XmssImportPubKey --- src/wh_client_crypto.c | 80 +++++++++++++++++++++ src/wh_crypto.c | 131 +++++++++++++++++++++++++++++++---- src/wh_server_keystore.c | 6 +- src/wh_server_nvm.c | 4 +- test/wh_test_crypto.c | 138 ++++++++++++++++++++++++++++++++++++- wolfhsm/wh_client_crypto.h | 23 +++++++ wolfhsm/wh_crypto.h | 24 +++++-- 7 files changed, 379 insertions(+), 27 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index b0e3df638..e1d2fc046 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -10783,6 +10783,43 @@ int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, return ret; } +int wh_Client_LmsImportPubKey(whClientContext* ctx, LmsKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret; + uint8_t blob[256]; + uint16_t blobSz = (uint16_t)sizeof(blob); + uint16_t keyId16; + + if ((ctx == NULL) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Build a public-only slot blob from the loaded public key, then provision + * it via the generic keystore. The server stores no private state, so the + * key is verify-only. */ + ret = wh_Crypto_LmsSerializePubKey(key, blobSz, blob, &blobSz); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId16 = (uint16_t)((inout_keyId != NULL) ? *inout_keyId + : WH_KEYID_ERASED); + ret = wh_Client_KeyCache(ctx, (uint32_t)flags, label, label_len, blob, + blobSz, &keyId16); + if ((ret == WH_ERROR_OK) && ((flags & WH_NVM_FLAGS_EPHEMERAL) == 0)) { + ret = wh_Client_KeyCommit(ctx, (whNvmId)keyId16); + } + if (ret == WH_ERROR_OK) { + wh_Client_LmsSetKeyId(key, (whKeyId)keyId16); + if (inout_keyId != NULL) { + *inout_keyId = (whKeyId)keyId16; + } + } + return ret; +} + #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS @@ -11170,6 +11207,49 @@ int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, return ret; } +int wh_Client_XmssImportPubKey(whClientContext* ctx, XmssKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret; + uint8_t blob[256]; + uint16_t blobSz = (uint16_t)sizeof(blob); + uint16_t keyId16; + const char* paramStr = NULL; + + if ((ctx == NULL) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_XmssKey_GetParamStr(key, ¶mStr); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + + /* Build a public-only slot blob, then provision it via the generic + * keystore. The server stores no secret state, so the key is verify-only. + */ + ret = wh_Crypto_XmssSerializePubKey(key, paramStr, blobSz, blob, &blobSz); + if (ret != WH_ERROR_OK) { + return ret; + } + + keyId16 = (uint16_t)((inout_keyId != NULL) ? *inout_keyId + : WH_KEYID_ERASED); + ret = wh_Client_KeyCache(ctx, (uint32_t)flags, label, label_len, blob, + blobSz, &keyId16); + if ((ret == WH_ERROR_OK) && ((flags & WH_NVM_FLAGS_EPHEMERAL) == 0)) { + ret = wh_Client_KeyCommit(ctx, (whNvmId)keyId16); + } + if (ret == WH_ERROR_OK) { + wh_Client_XmssSetKeyId(key, (whKeyId)keyId16); + if (inout_keyId != NULL) { + *inout_keyId = (whKeyId)keyId16; + } + } + return ret; +} + #endif /* WOLFSSL_HAVE_XMSS */ #endif /* WOLFHSM_CFG_DMA */ diff --git a/src/wh_crypto.c b/src/wh_crypto.c index dd77fbdee..aaf4c5454 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -517,20 +517,27 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, #define WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS 0x4C4D5301u /* 'LMS\1' */ #define WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS 0x584D5301u /* 'XMS\1' */ -int wh_Crypto_IsStatefulSigBlob(const uint8_t* buffer, uint16_t size) +int wh_Crypto_IsStatefulSigPrivBlob(const uint8_t* buffer, uint16_t size) { uint32_t magic; + uint16_t privLen; - if ((buffer == NULL) || (size < sizeof(magic))) { + /* Need the full fixed header to read privLen at offset 6. */ + if ((buffer == NULL) || (size < WH_CRYPTO_STATEFUL_SIG_HEADER_SZ)) { return 0; } /* Magic is stored native-order at offset 0; match what deserialize * requires before it would accept the blob. */ memcpy(&magic, buffer, sizeof(magic)); - return ((magic == WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS) || - (magic == WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS)) - ? 1 - : 0; + if ((magic != WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS) && + (magic != WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS)) { + return 0; + } + /* Only blobs carrying private key state are import-forbidden; a + * public-only blob (privLen == 0) is a verify key and is allowed. The + * deserialize path reads this same field to decide priv vs pub. */ + memcpy(&privLen, buffer + 6, sizeof(privLen)); + return (privLen > 0) ? 1 : 0; } static int _StatefulSigEncodeHeader(uint8_t* buffer, uint32_t magic, @@ -659,15 +666,60 @@ int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, if ((ret != 0) || (expectPubLen != pubLen)) { return WH_ERROR_BADARGS; } - if (privLen != (uint16_t)HSS_PRIVATE_KEY_LEN(key->params->hash_len)) { + /* privLen == 0 denotes a public-only (verify) key: load pub, no priv. */ + if ((privLen != 0) && + (privLen != (uint16_t)HSS_PRIVATE_KEY_LEN(key->params->hash_len))) { return WH_ERROR_BADARGS; } p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; memcpy(key->pub, p, pubLen); - p += pubLen; - memcpy(key->priv_raw, p, privLen); + if (privLen > 0) { + p += pubLen; + memcpy(key->priv_raw, p, privLen); + } + + return WH_ERROR_OK; +} + +int wh_Crypto_LmsSerializePubKey(LmsKey* key, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size) +{ + word32 pubLen32 = 0; + uint16_t pubLen; + uint16_t paramLen = 3; /* levels, height, winternitz */ + uint32_t totalLen; + int ret; + + if ((key == NULL) || (buffer == NULL) || (out_size == NULL) || + (key->params == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_LmsKey_GetPubLen(key, &pubLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + pubLen = (uint16_t)pubLen32; + + totalLen = (uint32_t)WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen; + if (totalLen > max_size) { + return WH_ERROR_BUFFER_SIZE; + } + + /* Public-only blob: privLen == 0, no private section follows. */ + (void)_StatefulSigEncodeHeader(buffer, + WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS, + pubLen, 0, paramLen); + + buffer[WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 0] = key->params->levels; + buffer[WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 1] = key->params->height; + buffer[WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 2] = key->params->width; + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen, + key->pub, pubLen); + + *out_size = (uint16_t)totalLen; return WH_ERROR_OK; } #endif /* WOLFSSL_HAVE_LMS */ @@ -772,20 +824,69 @@ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, if ((ret != 0) || (expectPubLen != pubLen)) { return WH_ERROR_BADARGS; } - ret = wc_XmssKey_GetPrivLen(key, &expectPrivLen); - if ((ret != 0) || (expectPrivLen != privLen)) { - return WH_ERROR_BADARGS; + /* privLen == 0 denotes a public-only (verify) key. */ + if (privLen != 0) { + ret = wc_XmssKey_GetPrivLen(key, &expectPrivLen); + if ((ret != 0) || (expectPrivLen != privLen)) { + return WH_ERROR_BADARGS; + } } p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; memcpy(key->pk, p, pubLen); - /* The private key is left in the slot blob; downstream paths read it - * via the bridge ReadCb against the cached slot (sk is allocated by - * Reload, not by deserialize). */ + /* The private key (if any) is left in the slot blob; downstream paths + * read it via the bridge ReadCb against the cached slot (sk is allocated + * by Reload, not by deserialize). */ (void)privLen; return WH_ERROR_OK; } + +int wh_Crypto_XmssSerializePubKey(XmssKey* key, const char* paramStr, + uint16_t max_size, uint8_t* buffer, + uint16_t* out_size) +{ + word32 pubLen32 = 0; + uint16_t pubLen; + uint16_t paramLen; + uint32_t totalLen; + size_t strLen; + int ret; + + if ((key == NULL) || (paramStr == NULL) || (buffer == NULL) || + (out_size == NULL) || (key->params == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wc_XmssKey_GetPubLen(key, &pubLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + pubLen = (uint16_t)pubLen32; + + strLen = strlen(paramStr); + if (strLen >= 0xFFFFu) { + return WH_ERROR_BADARGS; + } + paramLen = (uint16_t)(strLen + 1); /* include NUL */ + + totalLen = (uint32_t)WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen; + if (totalLen > max_size) { + return WH_ERROR_BUFFER_SIZE; + } + + /* Public-only blob: privLen == 0, no private section follows. */ + (void)_StatefulSigEncodeHeader(buffer, + WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS, + pubLen, 0, paramLen); + + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ, paramStr, paramLen); + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen, + key->pk, pubLen); + + *out_size = (uint16_t)totalLen; + return WH_ERROR_OK; +} #endif /* WOLFSSL_HAVE_XMSS */ diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index 587f1326e..f9d7f79af 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -780,7 +780,7 @@ static int _KeystoreCacheKey(whServerContext* server, whNvmMetadata* meta, #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) /* Checked calls must refuse access to the LMX/XMSS private key */ - if (checked && wh_Crypto_IsStatefulSigBlob(in, (uint16_t)meta->len)) { + if (checked && wh_Crypto_IsStatefulSigPrivBlob(in, (uint16_t)meta->len)) { return WH_ERROR_ACCESS; } #endif @@ -1827,7 +1827,7 @@ static int _HandleKeyUnwrapAndCacheRequest( #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) /* Stateful (LMS/XMSS) private key state must never enter the keystore via * unwrap; that would permit a signature-index roll-back. */ - if (wh_Crypto_IsStatefulSigBlob(key, (uint16_t)metadata.len)) { + if (wh_Crypto_IsStatefulSigPrivBlob(key, (uint16_t)metadata.len)) { return WH_ERROR_ACCESS; } #endif @@ -2923,7 +2923,7 @@ int _KeystoreCacheKeyDma(whServerContext* server, whNvmMetadata* meta, #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) /* Checked calls must refuse access to the LMX/XMSS private key */ if ((ret == 0) && checked && - wh_Crypto_IsStatefulSigBlob(buffer, (uint16_t)meta->len)) { + wh_Crypto_IsStatefulSigPrivBlob(buffer, (uint16_t)meta->len)) { ret = WH_ERROR_ACCESS; } #endif diff --git a/src/wh_server_nvm.c b/src/wh_server_nvm.c index 236ed4f37..9481db048 100644 --- a/src/wh_server_nvm.c +++ b/src/wh_server_nvm.c @@ -265,7 +265,7 @@ int wh_Server_HandleNvmRequest(whServerContext* server, (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) /* Block direct NVM import of stateful (LMS/XMSS) private key * state; only on-HSM keygen may create such objects. */ - if (wh_Crypto_IsStatefulSigBlob(data, (uint16_t)req.len)) { + if (wh_Crypto_IsStatefulSigPrivBlob(data, (uint16_t)req.len)) { rc = WH_ERROR_ACCESS; } #endif @@ -398,7 +398,7 @@ int wh_Server_HandleNvmRequest(whServerContext* server, (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) /* Block direct NVM import of stateful (LMS/XMSS) private key state; * only on-HSM keygen may create such objects. */ - if (wh_Crypto_IsStatefulSigBlob((const uint8_t*)data, + if (wh_Crypto_IsStatefulSigPrivBlob((const uint8_t*)data, (uint16_t)req.data_len)) { resp.rc = WH_ERROR_ACCESS; } diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 2db04c888..56e8d3c75 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13374,6 +13374,72 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } + /* Public-key import: provision a verify-only copy of this key's public + * half under a new keyId, verify the signature made above against it, and + * confirm signing with it is refused (no private state). */ + if (ret == 0) { + LmsKey pubKey[1]; + int pubInited = 0; + word32 pubLen = 0; + uint8_t pubRaw[128]; + whKeyId pubKeyId = WH_KEYID_ERASED; + int vres = 0; + + ret = wc_LmsKey_GetPubLen(key, &pubLen); + if ((ret == 0) && (pubLen > sizeof(pubRaw))) { + ret = BUFFER_E; + } + if (ret == 0) { + ret = wc_LmsKey_ExportPubRaw(key, pubRaw, &pubLen); + } + if (ret == 0) { + ret = wc_LmsKey_Init(pubKey, NULL, devId); + } + if (ret == 0) { + pubInited = 1; + ret = wc_LmsKey_SetParameters(pubKey, WH_TEST_LMS_LEVELS, + WH_TEST_LMS_HEIGHT, + WH_TEST_LMS_WINTERNITZ); + } + if (ret == 0) { + ret = wc_LmsKey_ImportPubRaw(pubKey, pubRaw, pubLen); + } + /* EPHEMERAL keeps it cache-only for an easy cleanup; production would + * pin with WH_NVM_FLAGS_NONMODIFIABLE and commit. */ + if (ret == 0) { + ret = wh_Client_LmsImportPubKey(ctx, pubKey, &pubKeyId, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("LMS import pub failed: ret=%d\n", ret); + } + } + if (ret == 0) { + ret = wh_Client_LmsVerifyDma(ctx, whTest_LmsSigBuf, sigLen, msg, + msgSz, &vres, pubKey); + if ((ret == 0) && (vres != 1)) { + WH_ERROR_PRINT("LMS verify with imported pub failed\n"); + ret = WH_ERROR_ABORTED; + } + } + if (ret == 0) { + word32 tmpSigLen = (word32)sizeof(whTest_LmsSigBuf); + int signRet = + wh_Client_LmsSignDma(ctx, msg, msgSz, whTest_LmsSigBuf, + &tmpSigLen, pubKey); + if (signRet == 0) { + WH_ERROR_PRINT("LMS sign with verify-only key unexpectedly " + "succeeded\n"); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(pubKeyId)) { + (void)wh_Client_KeyEvict(ctx, pubKeyId); + } + if (pubInited) { + wc_LmsKey_Free(pubKey); + } + } + /* The generic export API must refuse to return the private key state. * Keygen forces WH_NVM_FLAGS_NONEXPORTABLE, so export of the resident * key is denied with WH_ERROR_ACCESS. */ @@ -13401,6 +13467,7 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, int impRet; memset(fakeBlob, 0, sizeof(fakeBlob)); memcpy(fakeBlob, &lmsMagic, sizeof(lmsMagic)); + fakeBlob[6] = 1; /* privLen field nonzero: a private-bearing blob */ impRet = wh_Client_KeyCache(ctx, 0, NULL, 0, fakeBlob, (uint16_t)sizeof(fakeBlob), &impId); if (impRet != WH_ERROR_ACCESS) { @@ -13422,6 +13489,7 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, whNvmId addId = 0x1042; /* An arbitrary ID in the NVM range */ memset(fakeBlob, 0, sizeof(fakeBlob)); memcpy(fakeBlob, &lmsMagic, sizeof(lmsMagic)); + fakeBlob[6] = 1; /* privLen field nonzero: a private-bearing blob */ addRet = wh_Client_NvmAddObject(ctx, addId, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, 0, NULL, (whNvmSize)sizeof(fakeBlob), fakeBlob, @@ -13430,7 +13498,7 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, WH_ERROR_PRINT("LMS blob NVM import not blocked: ret=%d rc=%d " "(expected rc WH_ERROR_ACCESS)\n", addRet, (int)addRc); - ret = (addRc != 0) ? addRc : WH_ERROR_ABORTED + ret = (addRc != 0) ? addRc : WH_ERROR_ABORTED; } } @@ -13604,6 +13672,70 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } + /* Public-key import: provision a verify-only copy of this key's public + * half under a new keyId, verify the signature made above against it, and + * confirm signing with it is refused (no private state). */ + if (ret == 0) { + XmssKey pubKey[1]; + int pubInited = 0; + word32 pubLen = 0; + uint8_t pubRaw[128]; + whKeyId pubKeyId = WH_KEYID_ERASED; + int vres = 0; + + ret = wc_XmssKey_GetPubLen(key, &pubLen); + if ((ret == 0) && (pubLen > sizeof(pubRaw))) { + ret = BUFFER_E; + } + if (ret == 0) { + ret = wc_XmssKey_ExportPubRaw(key, pubRaw, &pubLen); + } + if (ret == 0) { + ret = wc_XmssKey_Init(pubKey, NULL, devId); + } + if (ret == 0) { + pubInited = 1; + ret = wc_XmssKey_SetParamStr(pubKey, WH_TEST_XMSS_PARAM_STR); + } + if (ret == 0) { + ret = wc_XmssKey_ImportPubRaw(pubKey, pubRaw, pubLen); + } + /* EPHEMERAL keeps it cache-only for an easy cleanup; production would + * pin with WH_NVM_FLAGS_NONMODIFIABLE and commit. */ + if (ret == 0) { + ret = wh_Client_XmssImportPubKey(ctx, pubKey, &pubKeyId, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT("XMSS import pub failed: ret=%d\n", ret); + } + } + if (ret == 0) { + ret = wh_Client_XmssVerifyDma(ctx, whTest_XmssSigBuf, sigLen, msg, + msgSz, &vres, pubKey); + if ((ret == 0) && (vres != 1)) { + WH_ERROR_PRINT("XMSS verify with imported pub failed\n"); + ret = WH_ERROR_ABORTED; + } + } + if (ret == 0) { + word32 tmpSigLen = (word32)sizeof(whTest_XmssSigBuf); + int signRet = + wh_Client_XmssSignDma(ctx, msg, msgSz, whTest_XmssSigBuf, + &tmpSigLen, pubKey); + if (signRet == 0) { + WH_ERROR_PRINT("XMSS sign with verify-only key unexpectedly " + "succeeded\n"); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(pubKeyId)) { + (void)wh_Client_KeyEvict(ctx, pubKeyId); + } + if (pubInited) { + wc_XmssKey_Free(pubKey); + } + } + /* The generic export API must refuse to return the private key state. * Keygen forces WH_NVM_FLAGS_NONEXPORTABLE, so export of the resident * key is denied with WH_ERROR_ACCESS. */ @@ -13631,6 +13763,7 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, int impRet; memset(fakeBlob, 0, sizeof(fakeBlob)); memcpy(fakeBlob, &xmssMagic, sizeof(xmssMagic)); + fakeBlob[6] = 1; /* privLen field nonzero: a private-bearing blob */ impRet = wh_Client_KeyCache(ctx, 0, NULL, 0, fakeBlob, (uint16_t)sizeof(fakeBlob), &impId); if (impRet != WH_ERROR_ACCESS) { @@ -13652,6 +13785,7 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, whNvmId addId = 0x1042; /* An arbitrary ID in the NVM range */ memset(fakeBlob, 0, sizeof(fakeBlob)); memcpy(fakeBlob, &xmssMagic, sizeof(xmssMagic)); + fakeBlob[6] = 1; /* privLen field nonzero: a private-bearing blob */ addRet = wh_Client_NvmAddObject(ctx, addId, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, 0, NULL, (whNvmSize)sizeof(fakeBlob), fakeBlob, @@ -13660,7 +13794,7 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, WH_ERROR_PRINT("XMSS blob NVM import not blocked: ret=%d rc=%d " "(expected rc WH_ERROR_ACCESS)\n", addRet, (int)addRc); - ret = (addRc != 0) ? addRc : WH_ERROR_ABORTED + ret = (addRc != 0) ? addRc : WH_ERROR_ABORTED; } } diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index e10264232..9543c72df 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3254,6 +3254,19 @@ int wh_Client_LmsVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, word32* sigsLeft); +/* Import a verify-only LMS public key into the keystore. The in-memory key + * must have its parameter set bound and the public key loaded (e.g. via + * wc_LmsKey_SetParameters + wc_LmsKey_ImportPubRaw). On success the key's + * devCtx carries the server-side keyId, usable with wh_Client_LmsVerifyDma. + * + * No private state is stored, so the key cannot sign. Pass a specific keyId in + * *inout_keyId to provision to a known slot (or WH_KEYID_ERASED to be + * assigned one), and WH_NVM_FLAGS_NONMODIFIABLE to pin it against later + * replacement. Committed to NVM unless flags include WH_NVM_FLAGS_EPHEMERAL. */ +int wh_Client_LmsImportPubKey(whClientContext* ctx, LmsKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS @@ -3280,6 +3293,16 @@ int wh_Client_XmssVerifyDma(whClientContext* ctx, const byte* sig, int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, word32* sigsLeft); +/* Import a verify-only XMSS / XMSS^MT public key into the keystore. The + * in-memory key must have its parameter string bound and the public key loaded + * (e.g. via wc_XmssKey_SetParamStr + wc_XmssKey_ImportPubRaw). Semantics match + * wh_Client_LmsImportPubKey: no private state is stored (verify only), the key + * may be pinned with WH_NVM_FLAGS_NONMODIFIABLE, and it is committed to NVM + * unless flags include WH_NVM_FLAGS_EPHEMERAL. */ +int wh_Client_XmssImportPubKey(whClientContext* ctx, XmssKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + #endif /* WOLFSSL_HAVE_XMSS */ #endif /* WOLFHSM_CFG_DMA */ diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 03da44aa3..82dbad7bb 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -136,11 +136,12 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, * full blob layout is documented in wh_crypto.c. */ #define WH_CRYPTO_STATEFUL_SIG_HEADER_SZ 12 -/* Returns 1 if buffer begins with an LMS/XMSS stateful-sig slot-blob magic, - * else 0. Used to reject client attempts to import (and thereby roll back) - * stateful private key state through the generic keystore/NVM paths. Only the - * on-HSM keygen may produce these blobs. */ -int wh_Crypto_IsStatefulSigBlob(const uint8_t* buffer, uint16_t size); +/* Returns 1 if buffer is an LMS/XMSS stateful-sig slot-blob that carries + * private key state (privLen > 0), else 0. Used to reject client attempts to + * import (and thereby roll back) private state through the generic + * keystore/NVM paths. A public-only blob (privLen == 0) is a verify key and + * returns 0 so it may be imported. */ +int wh_Crypto_IsStatefulSigPrivBlob(const uint8_t* buffer, uint16_t size); #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS @@ -164,6 +165,12 @@ int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, * @return WH_ERROR_OK on success, WH_ERROR_BADARGS on malformed blob. */ int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, LmsKey* key); + +/* Store the public half of an LmsKey (parameter set + public key, no private + * state) into a byte sequence. Produces a public-only slot blob (privLen == 0) + * suitable for importing a verify-only key. */ +int wh_Crypto_LmsSerializePubKey(LmsKey* key, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size); #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS @@ -176,6 +183,13 @@ int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, /* Restore an XmssKey from a byte sequence */ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, XmssKey* key); + +/* Store the public half of an XmssKey (param string + public key, no secret + * state) into a byte sequence. Produces a public-only slot blob + * (privLen == 0) suitable for importing a verify-only key. */ +int wh_Crypto_XmssSerializePubKey(XmssKey* key, const char* paramStr, + uint16_t max_size, uint8_t* buffer, + uint16_t* out_size); #endif /* WOLFSSL_HAVE_XMSS */ #endif /* !WOLFHSM_CFG_NO_CRYPTO */ From 172db4bef2817f20d21e2a8470affb820b7171f7 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 16 Jun 2026 16:50:11 -0700 Subject: [PATCH 07/12] Convert LMS/XMSS docs to doxygen in wh_client_crypto.h --- wolfhsm/wh_client_crypto.h | 231 ++++++++++++++++++++++++++++++++----- 1 file changed, 200 insertions(+), 31 deletions(-) diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 9543c72df..501abcd78 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3219,50 +3219,127 @@ int wh_Client_MlKemDecapsulateDma(whClientContext* ctx, MlKemKey* key, #ifdef WOLFSSL_HAVE_LMS -/* Bind / read the wolfHSM key id stored in key->devCtx. */ +/** + * @brief Bind a wolfHSM keyId into an LmsKey's devCtx. + * + * @param[in] key LmsKey to update. + * @param[in] keyId Server-side keyId to store in key->devCtx. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsSetKeyId(LmsKey* key, whKeyId keyId); + +/** + * @brief Read the wolfHSM keyId stored in an LmsKey's devCtx. + * + * @param[in] key LmsKey to query. + * @param[out] outId Receives the keyId held in key->devCtx. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsGetKeyId(LmsKey* key, whKeyId* outId); -/* Generate an LMS key on the server. The key's parameter set - * (levels/height/winternitz) must be bound on the in-memory key before this - * call (e.g. via wc_LmsKey_SetParameters). On success the key's devCtx - * carries the server-side keyId. +/** + * @brief Generate an LMS key on the server. + * + * The key's parameter set (levels/height/winternitz) must be bound on the + * in-memory key before this call (e.g. via wc_LmsKey_SetParameters). On + * success the key's devCtx carries the server-side keyId. If flags include + * WH_NVM_FLAGS_EPHEMERAL, the server returns the public key via DMA and the + * caller can sign with it while it remains cached on the server; otherwise the + * key is committed to the keystore. * - * If flags include WH_NVM_FLAGS_EPHEMERAL, the server returns the public key - * via DMA and the caller can sign with it as long as it remains cached on - * the server. Otherwise the key is committed to the keystore. */ + * @param[in] ctx Pointer to the client context. + * @param[in,out] key LmsKey with its parameter set bound; on success + * its devCtx carries the keyId. + * @param[in,out] inout_key_id On entry an optional requested keyId; on success + * the assigned keyId. May be NULL. + * @param[in] flags NVM flags for the new key. + * @param[in] label_len Length of label in bytes (0 if none). + * @param[in] label Optional label, or NULL. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); -/* Convenience wrapper: WH_NVM_FLAGS_EPHEMERAL keygen, returns pub via DMA. */ +/** + * @brief Convenience wrapper for an ephemeral keygen returning the public key. + * + * Equivalent to wh_Client_LmsMakeKeyDma with WH_NVM_FLAGS_EPHEMERAL; the public + * key is returned via DMA into the in-memory key. + * + * @param[in] ctx Pointer to the client context. + * @param[in,out] key LmsKey with its parameter set bound. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsMakeExportKeyDma(whClientContext* ctx, LmsKey* key); -/* Sign msg with an HSM-resident LMS key (key->devCtx carries the keyId). - * The new private state is committed atomically to NVM by the server before - * the signature is returned. */ +/** + * @brief Sign a message with an HSM-resident LMS key. + * + * The keyId is taken from key->devCtx. The new private state is committed + * atomically to NVM by the server before the signature is returned. + * + * @param[in] ctx Pointer to the client context. + * @param[in] msg Message to sign. + * @param[in] msgSz Length of msg in bytes. + * @param[out] sig Buffer to receive the signature. + * @param[in,out] sigSz On entry the capacity of sig; on success the signature + * length. + * @param[in] key LmsKey whose devCtx carries the keyId. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, byte* sig, word32* sigSz, LmsKey* key); -/* Verify sig against msg using an HSM-resident LMS key. *res is set to 1 on - * success, 0 on signature mismatch. */ +/** + * @brief Verify a signature using an HSM-resident LMS key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] sig Signature to verify. + * @param[in] sigSz Length of sig in bytes. + * @param[in] msg Message that was signed. + * @param[in] msgSz Length of msg in bytes. + * @param[out] res Set to 1 on a valid signature, 0 on mismatch. + * @param[in] key LmsKey whose devCtx carries the keyId. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, const byte* msg, word32 msgSz, int* res, LmsKey* key); -/* Query remaining signatures on an HSM-resident LMS key. */ +/** + * @brief Query the remaining signatures on an HSM-resident LMS key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key LmsKey whose devCtx carries the keyId. + * @param[out] sigsLeft Receives the count of remaining signatures. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, word32* sigsLeft); -/* Import a verify-only LMS public key into the keystore. The in-memory key - * must have its parameter set bound and the public key loaded (e.g. via - * wc_LmsKey_SetParameters + wc_LmsKey_ImportPubRaw). On success the key's - * devCtx carries the server-side keyId, usable with wh_Client_LmsVerifyDma. +/** + * @brief Import a verify-only LMS public key into the keystore. * - * No private state is stored, so the key cannot sign. Pass a specific keyId in - * *inout_keyId to provision to a known slot (or WH_KEYID_ERASED to be - * assigned one), and WH_NVM_FLAGS_NONMODIFIABLE to pin it against later - * replacement. Committed to NVM unless flags include WH_NVM_FLAGS_EPHEMERAL. */ + * The in-memory key must have its parameter set bound and the public key + * loaded (e.g. via wc_LmsKey_SetParameters + wc_LmsKey_ImportPubRaw). On + * success the key's devCtx carries the server-side keyId, usable with + * wh_Client_LmsVerifyDma. No private state is stored, so the key cannot sign. + * + * @param[in] ctx Pointer to the client context. + * @param[in,out] key LmsKey with its parameter set bound and public + * key loaded; on success its devCtx carries the + * keyId. + * @param[in,out] inout_keyId On entry a specific keyId to provision, or + * WH_KEYID_ERASED to be assigned one; on success the + * keyId. May be NULL. + * @param[in] flags NVM flags; WH_NVM_FLAGS_NONMODIFIABLE pins the + * key, and it is committed to NVM unless + * WH_NVM_FLAGS_EPHEMERAL is set. + * @param[in] label_len Length of label in bytes (0 if none). + * @param[in] label Optional label, or NULL. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_LmsImportPubKey(whClientContext* ctx, LmsKey* key, whKeyId* inout_keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label); @@ -3271,34 +3348,126 @@ int wh_Client_LmsImportPubKey(whClientContext* ctx, LmsKey* key, #ifdef WOLFSSL_HAVE_XMSS +/** + * @brief Bind a wolfHSM keyId into an XmssKey's devCtx. + * + * @param[in] key XmssKey to update. + * @param[in] keyId Server-side keyId to store in key->devCtx. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssSetKeyId(XmssKey* key, whKeyId keyId); + +/** + * @brief Read the wolfHSM keyId stored in an XmssKey's devCtx. + * + * @param[in] key XmssKey to query. + * @param[out] outId Receives the keyId held in key->devCtx. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssGetKeyId(XmssKey* key, whKeyId* outId); -/* Generate an XMSS / XMSS^MT key on the server. The parameter string must be - * bound on the in-memory key (via wc_XmssKey_SetParamStr) before this call. +/** + * @brief Generate an XMSS / XMSS^MT key on the server. + * + * The parameter string must be bound on the in-memory key (via + * wc_XmssKey_SetParamStr) before this call. On success the key's devCtx + * carries the server-side keyId. If flags include WH_NVM_FLAGS_EPHEMERAL, the + * server returns the public key via DMA and the caller can sign with it while + * it remains cached on the server; otherwise the key is committed to the + * keystore. + * + * @param[in] ctx Pointer to the client context. + * @param[in,out] key XmssKey with its parameter string bound; on + * success its devCtx carries the keyId. + * @param[in,out] inout_key_id On entry an optional requested keyId; on success + * the assigned keyId. May be NULL. + * @param[in] flags NVM flags for the new key. + * @param[in] label_len Length of label in bytes (0 if none). + * @param[in] label Optional label, or NULL. + * @return int Returns 0 on success or a negative error code on failure. */ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); +/** + * @brief Convenience wrapper for an ephemeral keygen returning the public key. + * + * Equivalent to wh_Client_XmssMakeKeyDma with WH_NVM_FLAGS_EPHEMERAL; the + * public key is returned via DMA into the in-memory key. + * + * @param[in] ctx Pointer to the client context. + * @param[in,out] key XmssKey with its parameter string bound. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssMakeExportKeyDma(whClientContext* ctx, XmssKey* key); +/** + * @brief Sign a message with an HSM-resident XMSS key. + * + * The keyId is taken from key->devCtx. The new private state is committed + * atomically to NVM by the server before the signature is returned. + * + * @param[in] ctx Pointer to the client context. + * @param[in] msg Message to sign. + * @param[in] msgSz Length of msg in bytes. + * @param[out] sig Buffer to receive the signature. + * @param[in,out] sigSz On entry the capacity of sig; on success the signature + * length. + * @param[in] key XmssKey whose devCtx carries the keyId. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, byte* sig, word32* sigSz, XmssKey* key); +/** + * @brief Verify a signature using an HSM-resident XMSS key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] sig Signature to verify. + * @param[in] sigSz Length of sig in bytes. + * @param[in] msg Message that was signed. + * @param[in] msgSz Length of msg in bytes. + * @param[out] res Set to 1 on a valid signature, 0 on mismatch. + * @param[in] key XmssKey whose devCtx carries the keyId. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, const byte* msg, word32 msgSz, int* res, XmssKey* key); +/** + * @brief Query the remaining signatures on an HSM-resident XMSS key. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key XmssKey whose devCtx carries the keyId. + * @param[out] sigsLeft Receives the count of remaining signatures. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, word32* sigsLeft); -/* Import a verify-only XMSS / XMSS^MT public key into the keystore. The - * in-memory key must have its parameter string bound and the public key loaded - * (e.g. via wc_XmssKey_SetParamStr + wc_XmssKey_ImportPubRaw). Semantics match - * wh_Client_LmsImportPubKey: no private state is stored (verify only), the key - * may be pinned with WH_NVM_FLAGS_NONMODIFIABLE, and it is committed to NVM - * unless flags include WH_NVM_FLAGS_EPHEMERAL. */ +/** + * @brief Import a verify-only XMSS / XMSS^MT public key into the keystore. + * + * The in-memory key must have its parameter string bound and the public key + * loaded (e.g. via wc_XmssKey_SetParamStr + wc_XmssKey_ImportPubRaw). Semantics + * match wh_Client_LmsImportPubKey: no private state is stored (verify only) so + * the key cannot sign, it may be pinned with WH_NVM_FLAGS_NONMODIFIABLE, and it + * is committed to NVM unless flags include WH_NVM_FLAGS_EPHEMERAL. + * + * @param[in] ctx Pointer to the client context. + * @param[in,out] key XmssKey with its parameter string bound and + * public key loaded; on success its devCtx carries + * the keyId. + * @param[in,out] inout_keyId On entry a specific keyId to provision, or + * WH_KEYID_ERASED to be assigned one; on success the + * keyId. May be NULL. + * @param[in] flags NVM flags (see wh_Client_LmsImportPubKey). + * @param[in] label_len Length of label in bytes (0 if none). + * @param[in] label Optional label, or NULL. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_XmssImportPubKey(whClientContext* ctx, XmssKey* key, whKeyId* inout_keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label); From a55c64b11961e4bf27605d4036855f1d9162e77b Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 16 Jun 2026 17:48:57 -0700 Subject: [PATCH 08/12] Enforce write-through on LMS/XMSS keygen, update docs --- docs/src/5-Features.md | 2 + src/wh_client_crypto.c | 16 ++++++-- src/wh_server_crypto.c | 18 +++++++-- test/wh_test_crypto.c | 76 ++++++++++++++++++++++++++++++++++++-- wolfhsm/wh_client_crypto.h | 36 ++++++++++-------- 5 files changed, 120 insertions(+), 28 deletions(-) diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 3a24cac1e..447c2e56c 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -248,6 +248,8 @@ wolfHSM ships with two reference flash drivers usable on host platforms and in t Vendor-supplied flash drivers ship with the platform ports under `port//`. New platforms are integrated into wolfHSM by implementing the `whFlashCb` callback set against the device's flash controller; nothing in the NVM library above this layer needs to change. +**Write-through requirement (port maintainers).** wolfHSM's power-loss guarantees assume the port's `Program` and `Verify` callbacks are write-through to the physical medium: `Program` must make the data durable before it returns, and `Verify` must read back from the medium rather than from any volatile write cache. A backend that buffers writes in a cache that can be lost on power failure breaks this assumption — on the next boot a committed object can roll back to a prior value. For stateless key material this is only a durability concern, but for **stateful or monotonic objects it is a security issue**: a rolled-back LMS or XMSS private key reuses a one-time signature index, enabling forgery, and a rolled-back monotonic counter defeats anti-rollback and replay protection. wolfHSM cannot detect or enforce this property, so a port whose flash controller caches writes must either disable that caching or issue an explicit flush before `Program`/`Verify` return. + ### Optional NVM Backing The NVM subsystem described above is **optional**. A server can be initialized with `whServerConfig.nvm == NULL`, in which case it runs with no persistent object store at all. This suits clients and cores that only need cached-key cryptography and have no flash available for an NVM partition — at the cost of a reduced feature set, since everything that depends on persistent storage becomes unavailable. diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index e1d2fc046..9851fc108 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -10448,6 +10448,11 @@ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, return WH_ERROR_BADARGS; } + /* Enforce write-through */ + if ((flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + return WH_ERROR_BADARGS; + } + ret = wc_LmsKey_GetPubLen(key, &pubLen32); if (ret != 0) { return WH_ERROR_BADARGS; @@ -10535,8 +10540,7 @@ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, int wh_Client_LmsMakeExportKeyDma(whClientContext* ctx, LmsKey* key) { - return wh_Client_LmsMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, - NULL); + return wh_Client_LmsMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_NONE, 0, NULL); } int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, @@ -10861,6 +10865,11 @@ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, return WH_ERROR_BADARGS; } + /* Enforce write-through */ + if ((flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + return WH_ERROR_BADARGS; + } + ret = wc_XmssKey_GetPubLen(key, &pubLen32); if (ret != 0) { return WH_ERROR_BADARGS; @@ -10958,8 +10967,7 @@ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, int wh_Client_XmssMakeExportKeyDma(whClientContext* ctx, XmssKey* key) { - return wh_Client_XmssMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, - NULL); + return wh_Client_XmssMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_NONE, 0, NULL); } int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 0e96a16d8..3aafb0045 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -7008,6 +7008,11 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, return ret; } + /* Reject EPHEMERAL keys since keygen itself is stateful */ + if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + return WH_ERROR_BADARGS; + } + ret = wc_LmsKey_Init(key, NULL, devId); if (ret != 0) { return ret; @@ -7062,9 +7067,8 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, (uint16_t)req.labelSize, req.label); } - /* For non-ephemeral keys, commit to NVM so the key survives a server - * restart. Ephemeral keys are cache-only. */ - if ((ret == 0) && ((req.flags & WH_NVM_FLAGS_EPHEMERAL) == 0)) { + /* Write-through before responding */ + if (ret == 0) { ret = wh_Server_KeystoreCommitKey(ctx, keyId); } @@ -7448,6 +7452,11 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, return ret; } + /* Reject EPHEMERAL keys since keygen itself is stateful */ + if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + return WH_ERROR_BADARGS; + } + /* xmssParamStr arrives via the request struct (populated by the client in * wh_Client_XmssMakeKeyDma). Defensively enforce NUL-termination before * passing it to wolfCrypt, since it originates from the client. */ @@ -7521,7 +7530,8 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, req.label); } - if ((ret == 0) && ((req.flags & WH_NVM_FLAGS_EPHEMERAL) == 0)) { + /* Write-through before responding */ + if (ret == 0) { ret = wh_Server_KeystoreCommitKey(ctx, keyId); } diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 56e8d3c75..18a4fde72 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13272,8 +13272,8 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } - /* MakeKey via cryptocb: server caches private key (ephemeral) and - * returns the public key over DMA. */ + /* MakeKey via cryptocb: the server commits the key to NVM before + * returning the public key over DMA. */ if (ret == 0) { ret = wc_LmsKey_MakeKey(key, rng); if (ret != 0) { @@ -13290,6 +13290,40 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } + /* Durability: keygen must commit the key to NVM before returning the pub, + * not defer it. Evict the volatile cache copy (as a power loss before the + * first sign would) and confirm the key is still resident in NVM. */ + if (ret == 0) { + whKeyId durId = WH_KEYID_ERASED; + word32 durLeft = 0; + if ((wh_Client_LmsGetKeyId(key, &durId) == 0) && + !WH_KEYID_ISERASED(durId)) { + ret = wh_Client_KeyEvict(ctx, durId); + if (ret != 0) { + WH_ERROR_PRINT("LMS durability evict failed: ret=%d\n", ret); + } + else { + ret = wh_Client_LmsSigsLeftDma(ctx, key, &durLeft); + if (ret != 0) { + WH_ERROR_PRINT("LMS key not durable after keygen: ret=%d\n", + ret); + } + } + } + } + + /* EPHEMERAL is invalid for a stateful private keygen and must be rejected + * locally with WH_ERROR_BADARGS (no server round-trip). */ + if (ret == 0) { + int badRet = wh_Client_LmsMakeKeyDma(ctx, key, NULL, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL); + if (badRet != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("LMS ephemeral keygen not rejected: ret=%d " + "(expected WH_ERROR_BADARGS)\n", badRet); + ret = WH_ERROR_ABORTED; + } + } + /* Sign via cryptocb. */ if (ret == 0) { sigLen = sigCap; @@ -13574,8 +13608,8 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } - /* MakeKey via cryptocb: server caches private key (ephemeral) and - * returns the public key over DMA. */ + /* MakeKey via cryptocb: the server commits the key to NVM before + * returning the public key over DMA. */ if (ret == 0) { ret = wc_XmssKey_MakeKey(key, rng); if (ret != 0) { @@ -13592,6 +13626,40 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } + /* Durability: keygen must commit the key to NVM before returning the pub. + * Evict the volatile cache copy (as a power loss before the first sign + * would) and confirm the key is still resident in NVM. */ + if (ret == 0) { + whKeyId durId = WH_KEYID_ERASED; + word32 durLeft = 0; + if ((wh_Client_XmssGetKeyId(key, &durId) == 0) && + !WH_KEYID_ISERASED(durId)) { + ret = wh_Client_KeyEvict(ctx, durId); + if (ret != 0) { + WH_ERROR_PRINT("XMSS durability evict failed: ret=%d\n", ret); + } + else { + ret = wh_Client_XmssSigsLeftDma(ctx, key, &durLeft); + if (ret != 0) { + WH_ERROR_PRINT("XMSS key not durable after keygen: " + "ret=%d\n", ret); + } + } + } + } + + /* EPHEMERAL is invalid for a stateful private keygen and must be rejected + * locally with WH_ERROR_BADARGS. */ + if (ret == 0) { + int badRet = wh_Client_XmssMakeKeyDma(ctx, key, NULL, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL); + if (badRet != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("XMSS ephemeral keygen not rejected: ret=%d " + "(expected WH_ERROR_BADARGS)\n", badRet); + ret = WH_ERROR_ABORTED; + } + } + if (ret == 0) { sigLen = sigCap; ret = wc_XmssKey_Sign(key, whTest_XmssSigBuf, &sigLen, msg, (int)msgSz); diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 501abcd78..1b49a4119 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3242,17 +3242,18 @@ int wh_Client_LmsGetKeyId(LmsKey* key, whKeyId* outId); * * The key's parameter set (levels/height/winternitz) must be bound on the * in-memory key before this call (e.g. via wc_LmsKey_SetParameters). On - * success the key's devCtx carries the server-side keyId. If flags include - * WH_NVM_FLAGS_EPHEMERAL, the server returns the public key via DMA and the - * caller can sign with it while it remains cached on the server; otherwise the - * key is committed to the keystore. + * success the key's devCtx carries the server-side keyId and the public key is + * returned via DMA. The key is always committed to the keystore before the + * public key is returned: WH_NVM_FLAGS_EPHEMERAL is rejected with + * WH_ERROR_BADARGS for stateful keys, since releasing the public key of a + * non-durable private key would orphan it on power loss. * * @param[in] ctx Pointer to the client context. * @param[in,out] key LmsKey with its parameter set bound; on success * its devCtx carries the keyId. * @param[in,out] inout_key_id On entry an optional requested keyId; on success * the assigned keyId. May be NULL. - * @param[in] flags NVM flags for the new key. + * @param[in] flags NVM flags; WH_NVM_FLAGS_EPHEMERAL is rejected. * @param[in] label_len Length of label in bytes (0 if none). * @param[in] label Optional label, or NULL. * @return int Returns 0 on success or a negative error code on failure. @@ -3262,10 +3263,11 @@ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, uint16_t label_len, uint8_t* label); /** - * @brief Convenience wrapper for an ephemeral keygen returning the public key. + * @brief Convenience wrapper for keygen that returns the public key via DMA. * - * Equivalent to wh_Client_LmsMakeKeyDma with WH_NVM_FLAGS_EPHEMERAL; the public - * key is returned via DMA into the in-memory key. + * Equivalent to wh_Client_LmsMakeKeyDma with a server-assigned keyId. As with + * that call the key is committed to the keystore before its public key is + * returned. * * @param[in] ctx Pointer to the client context. * @param[in,out] key LmsKey with its parameter set bound. @@ -3371,17 +3373,18 @@ int wh_Client_XmssGetKeyId(XmssKey* key, whKeyId* outId); * * The parameter string must be bound on the in-memory key (via * wc_XmssKey_SetParamStr) before this call. On success the key's devCtx - * carries the server-side keyId. If flags include WH_NVM_FLAGS_EPHEMERAL, the - * server returns the public key via DMA and the caller can sign with it while - * it remains cached on the server; otherwise the key is committed to the - * keystore. + * carries the server-side keyId and the public key is returned via DMA. The + * key is always committed to the keystore before the public key is returned: + * WH_NVM_FLAGS_EPHEMERAL is rejected with WH_ERROR_BADARGS for stateful keys, + * since releasing the public key of a non-durable private key would orphan it + * on power loss. * * @param[in] ctx Pointer to the client context. * @param[in,out] key XmssKey with its parameter string bound; on * success its devCtx carries the keyId. * @param[in,out] inout_key_id On entry an optional requested keyId; on success * the assigned keyId. May be NULL. - * @param[in] flags NVM flags for the new key. + * @param[in] flags NVM flags; WH_NVM_FLAGS_EPHEMERAL is rejected. * @param[in] label_len Length of label in bytes (0 if none). * @param[in] label Optional label, or NULL. * @return int Returns 0 on success or a negative error code on failure. @@ -3391,10 +3394,11 @@ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, uint16_t label_len, uint8_t* label); /** - * @brief Convenience wrapper for an ephemeral keygen returning the public key. + * @brief Convenience wrapper for keygen that returns the public key via DMA. * - * Equivalent to wh_Client_XmssMakeKeyDma with WH_NVM_FLAGS_EPHEMERAL; the - * public key is returned via DMA into the in-memory key. + * Equivalent to wh_Client_XmssMakeKeyDma with a server-assigned keyId. As with + * that call the key is committed to the keystore before its public key is + * returned. * * @param[in] ctx Pointer to the client context. * @param[in,out] key XmssKey with its parameter string bound. From b4fefb89efd74e2f881b721b264a7e46ec505214 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Wed, 17 Jun 2026 14:37:18 -0700 Subject: [PATCH 09/12] Remove stale documentation reference --- src/wh_server_crypto.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 3aafb0045..2bd85a43d 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -893,8 +893,7 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, * for the software path. We wire write_private_key directly to atomic NVM * commit (wh_Nvm_AddObjectWithReclaim): wolfCrypt's contract is to advance * the index, call write_cb, and only emit the signature if write_cb returned - * success. That gives us pre-commit-then-emit ordering for free — see - * doc/LMS_XMSS_CryptoCb.md and the plan for the crash-safety analysis. + * success. That gives us pre-commit-then-emit ordering for free. * * The bridge keeps a pointer into the server's cache slot blob (laid out by * wh_Crypto_{Lms,Xmss}SerializeKey). Each write_cb invocation overwrites the From c426453f62daabb80a8b15f1f4e8d60f5b3ff1ea Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Wed, 17 Jun 2026 15:08:43 -0700 Subject: [PATCH 10/12] Update SigsLeft routines to return boolean to follow wolfssl, update comments --- src/wh_client_crypto.c | 20 ++++++++++---------- src/wh_client_cryptocb.c | 20 ++++++++++++++------ test/wh_test_crypto.c | 24 ++++++++++++++++-------- wolfhsm/wh_client_crypto.h | 30 ++++++++++++++++-------------- 4 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 9851fc108..31923e045 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -10728,8 +10728,7 @@ int wh_Client_LmsVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, return ret; } -int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, - word32* sigsLeft) +int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key) { int ret = WH_ERROR_OK; uint8_t* dataPtr; @@ -10737,7 +10736,7 @@ int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* res; whKeyId key_id; - if ((ctx == NULL) || (key == NULL) || (sigsLeft == NULL)) { + if ((ctx == NULL) || (key == NULL)) { return WH_ERROR_BADARGS; } @@ -10778,8 +10777,9 @@ int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT, (uint8_t**)&res); if (ret >= 0) { - *sigsLeft = res->sigsLeft; - ret = WH_ERROR_OK; + /* The server mirrors wc_LmsKey_SigsLeft(), which is a + * boolean. Normalize so the only nonzero return is 1. */ + ret = (res->sigsLeft != 0) ? 1 : 0; } } } @@ -11156,8 +11156,7 @@ int wh_Client_XmssVerifyDma(whClientContext* ctx, const byte* sig, return ret; } -int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, - word32* sigsLeft) +int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key) { int ret = WH_ERROR_OK; uint8_t* dataPtr; @@ -11165,7 +11164,7 @@ int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* res; whKeyId key_id; - if ((ctx == NULL) || (key == NULL) || (sigsLeft == NULL)) { + if ((ctx == NULL) || (key == NULL)) { return WH_ERROR_BADARGS; } @@ -11206,8 +11205,9 @@ int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT, (uint8_t**)&res); if (ret >= 0) { - *sigsLeft = res->sigsLeft; - ret = WH_ERROR_OK; + /* The server mirrors wc_XmssKey_SigsLeft(), which is a + * boolean. Normalize so the only nonzero return is 1. */ + ret = (res->sigsLeft != 0) ? 1 : 0; } } } diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 92155cf75..94e0aa2fd 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -1079,10 +1079,14 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, case WC_PQC_STATEFUL_SIG_TYPE_LMS: #ifdef WOLFHSM_CFG_DMA if (useDma) { + /* ret is the error code if negative, otherwise a boolean */ ret = wh_Client_LmsSigsLeftDma( - ctx, - (LmsKey*)info->pk.pqc_stateful_sig_sigs_left.key, - info->pk.pqc_stateful_sig_sigs_left.sigsLeft); + ctx, (LmsKey*)info->pk.pqc_stateful_sig_sigs_left.key); + if (ret >= 0) { + *(info->pk.pqc_stateful_sig_sigs_left.sigsLeft) = + (word32)ret; + ret = WH_ERROR_OK; + } } else #endif /* WOLFHSM_CFG_DMA */ @@ -1095,10 +1099,14 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, case WC_PQC_STATEFUL_SIG_TYPE_XMSS: #ifdef WOLFHSM_CFG_DMA if (useDma) { + /* ret is the error code if negative, otherwise a boolean */ ret = wh_Client_XmssSigsLeftDma( - ctx, - (XmssKey*)info->pk.pqc_stateful_sig_sigs_left.key, - info->pk.pqc_stateful_sig_sigs_left.sigsLeft); + ctx, (XmssKey*)info->pk.pqc_stateful_sig_sigs_left.key); + if (ret >= 0) { + *(info->pk.pqc_stateful_sig_sigs_left.sigsLeft) = + (word32)ret; + ret = WH_ERROR_OK; + } } else #endif /* WOLFHSM_CFG_DMA */ diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 18a4fde72..7a8668869 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13294,8 +13294,7 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, * not defer it. Evict the volatile cache copy (as a power loss before the * first sign would) and confirm the key is still resident in NVM. */ if (ret == 0) { - whKeyId durId = WH_KEYID_ERASED; - word32 durLeft = 0; + whKeyId durId = WH_KEYID_ERASED; if ((wh_Client_LmsGetKeyId(key, &durId) == 0) && !WH_KEYID_ISERASED(durId)) { ret = wh_Client_KeyEvict(ctx, durId); @@ -13303,11 +13302,16 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, WH_ERROR_PRINT("LMS durability evict failed: ret=%d\n", ret); } else { - ret = wh_Client_LmsSigsLeftDma(ctx, key, &durLeft); - if (ret != 0) { + /* SigsLeft reloads the key from NVM; a negative return means + * keygen failed to commit it. A fresh key reports 1. */ + ret = wh_Client_LmsSigsLeftDma(ctx, key); + if (ret < 0) { WH_ERROR_PRINT("LMS key not durable after keygen: ret=%d\n", ret); } + else { + ret = 0; + } } } } @@ -13630,8 +13634,7 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, * Evict the volatile cache copy (as a power loss before the first sign * would) and confirm the key is still resident in NVM. */ if (ret == 0) { - whKeyId durId = WH_KEYID_ERASED; - word32 durLeft = 0; + whKeyId durId = WH_KEYID_ERASED; if ((wh_Client_XmssGetKeyId(key, &durId) == 0) && !WH_KEYID_ISERASED(durId)) { ret = wh_Client_KeyEvict(ctx, durId); @@ -13639,11 +13642,16 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, WH_ERROR_PRINT("XMSS durability evict failed: ret=%d\n", ret); } else { - ret = wh_Client_XmssSigsLeftDma(ctx, key, &durLeft); - if (ret != 0) { + /* SigsLeft reloads the key from NVM; a negative return means + * keygen failed to commit it. A fresh key reports 1. */ + ret = wh_Client_XmssSigsLeftDma(ctx, key); + if (ret < 0) { WH_ERROR_PRINT("XMSS key not durable after keygen: " "ret=%d\n", ret); } + else { + ret = 0; + } } } } diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 1b49a4119..ed1843033 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3310,15 +3310,16 @@ int wh_Client_LmsVerifyDma(whClientContext* ctx, const byte* sig, word32 sigSz, LmsKey* key); /** - * @brief Query the remaining signatures on an HSM-resident LMS key. + * @brief Report whether an HSM-resident LMS key can still produce signatures. * - * @param[in] ctx Pointer to the client context. - * @param[in] key LmsKey whose devCtx carries the keyId. - * @param[out] sigsLeft Receives the count of remaining signatures. - * @return int Returns 0 on success or a negative error code on failure. + * Mirrors wc_LmsKey_SigsLeft(): the result is a boolean, not a count. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key LmsKey whose devCtx carries the keyId. + * @return int Returns 1 if signatures remain, 0 if the key is exhausted, or a + * negative error code on failure. */ -int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key, - word32* sigsLeft); +int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key); /** * @brief Import a verify-only LMS public key into the keystore. @@ -3441,15 +3442,16 @@ int wh_Client_XmssVerifyDma(whClientContext* ctx, const byte* sig, int* res, XmssKey* key); /** - * @brief Query the remaining signatures on an HSM-resident XMSS key. + * @brief Report whether an HSM-resident XMSS key can still produce signatures. * - * @param[in] ctx Pointer to the client context. - * @param[in] key XmssKey whose devCtx carries the keyId. - * @param[out] sigsLeft Receives the count of remaining signatures. - * @return int Returns 0 on success or a negative error code on failure. + * Mirrors wc_XmssKey_SigsLeft(): the result is a boolean, not a count. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key XmssKey whose devCtx carries the keyId. + * @return int Returns 1 if signatures remain, 0 if the key is exhausted, or a + * negative error code on failure. */ -int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key, - word32* sigsLeft); +int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key); /** * @brief Import a verify-only XMSS / XMSS^MT public key into the keystore. From 85aebf3f56c1017d08096768d053d9b9a3991556 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Wed, 17 Jun 2026 15:21:13 -0700 Subject: [PATCH 11/12] Remove WOLFSSL_WC_LMS and WOLFSSL_WC_XMSS --- test/config/user_settings.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 844a9332f..83d03e364 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -142,11 +142,9 @@ #define WOLFSSL_HAVE_MLKEM /* LMS / HSS Options (RFC 8554, NIST SP 800-208) */ #define WOLFSSL_HAVE_LMS -#define WOLFSSL_WC_LMS /* XMSS / XMSS^MT Options (RFC 8391, NIST SP 800-208) */ #define WOLFSSL_HAVE_XMSS -#define WOLFSSL_WC_XMSS /* Ed25519 Options */ From 1978b60de57c5456fcf3e0470780a9c2e41d0781 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Wed, 17 Jun 2026 21:15:35 -0700 Subject: [PATCH 12/12] Surface errors from POST for LMS/XMSS --- src/wh_client_crypto.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 31923e045..a9ba5fc22 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -10437,6 +10437,7 @@ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, uint16_t label_len, uint8_t* label) { int ret = WH_ERROR_OK; + int postRet = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; uint8_t* dataPtr; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* req; @@ -10517,7 +10518,7 @@ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, } while (ret == WH_ERROR_NOTREADY); } - (void)wh_Client_DmaProcessClientAddress( + postRet = wh_Client_DmaProcessClientAddress( ctx, (uintptr_t)key->pub, (void**)&pubAddr, pubLen32, WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); @@ -10533,6 +10534,11 @@ int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, wh_Client_LmsSetKeyId(key, key_id); } } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } } return ret; @@ -10547,6 +10553,7 @@ int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, byte* sig, word32* sigSz, LmsKey* key) { int ret = WH_ERROR_OK; + int postRet = WH_ERROR_OK; uint8_t* dataPtr; whMessageCrypto_PqcStatefulSigSignDmaRequest* req; whMessageCrypto_PqcStatefulSigSignDmaResponse* res; @@ -10616,7 +10623,7 @@ int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, (void)wh_Client_DmaProcessClientAddress( ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); - (void)wh_Client_DmaProcessClientAddress( + postRet = wh_Client_DmaProcessClientAddress( ctx, (uintptr_t)sig, (void**)&sigAddr, sigCap, WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); @@ -10633,6 +10640,11 @@ int wh_Client_LmsSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, } } } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } } return ret; @@ -10854,6 +10866,7 @@ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, uint16_t label_len, uint8_t* label) { int ret = WH_ERROR_OK; + int postRet = WH_ERROR_OK; whKeyId key_id = WH_KEYID_ERASED; uint8_t* dataPtr; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest* req; @@ -10944,7 +10957,7 @@ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, } while (ret == WH_ERROR_NOTREADY); } - (void)wh_Client_DmaProcessClientAddress( + postRet = wh_Client_DmaProcessClientAddress( ctx, (uintptr_t)key->pk, (void**)&pubAddr, pubLen32, WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); @@ -10960,6 +10973,11 @@ int wh_Client_XmssMakeKeyDma(whClientContext* ctx, XmssKey* key, wh_Client_XmssSetKeyId(key, key_id); } } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } } return ret; @@ -10974,6 +10992,7 @@ int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, byte* sig, word32* sigSz, XmssKey* key) { int ret = WH_ERROR_OK; + int postRet = WH_ERROR_OK; uint8_t* dataPtr; whMessageCrypto_PqcStatefulSigSignDmaRequest* req; whMessageCrypto_PqcStatefulSigSignDmaResponse* res; @@ -11043,7 +11062,7 @@ int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, (void)wh_Client_DmaProcessClientAddress( ctx, (uintptr_t)msg, (void**)&msgAddr, msgSz, WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); - (void)wh_Client_DmaProcessClientAddress( + postRet = wh_Client_DmaProcessClientAddress( ctx, (uintptr_t)sig, (void**)&sigAddr, sigCap, WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); @@ -11060,6 +11079,11 @@ int wh_Client_XmssSignDma(whClientContext* ctx, const byte* msg, word32 msgSz, } } } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } } return ret;