From de1579180eba0fc15ab02efaee50fac2074ef255 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 22 May 2026 13:37:03 -0700 Subject: [PATCH 01/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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; From b5b0e06d4ff1b8156750374f44b8425b71c8a7fd Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 16:31:28 -0700 Subject: [PATCH 13/27] Redesign LMS/XMSS MakeKey callbacks to perform the NVM write --- src/wh_server_crypto.c | 218 +++++++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 104 deletions(-) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 2bd85a43d..d541d58e7 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -6959,17 +6959,50 @@ static int _StatefulBridgeFromSlot(whServerStatefulSigBridge* b, b->slotCapacity = slotCapacity; return WH_ERROR_OK; } + +/* Keygen persistence bridge. The sign bridge rewrites the priv region of an + * existing slot; keygen instead builds a brand-new slot. Its write cb + * serializes the full key and commits it to NVM while the private key is + * still live, honoring wolfCrypt's "persist before returning success" + * contract. The cb can only return wolfCrypt's coarse pass/fail code, so + * status carries the WH_ERROR_* detail back to the handler. */ +typedef struct whServerStatefulSigKeygenBridge { + whServerContext* server; + void* key; /* LmsKey* or XmssKey*, alg-specific */ + const char* paramStr; /* XMSS param string; NULL for LMS */ + uint8_t* label; + whKeyId keyId; + whNvmFlags flags; + uint16_t labelSize; + int status; /* WH_ERROR_* from import/commit */ +} whServerStatefulSigKeygenBridge; #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) +/* Keygen write cb: wc_*_MakeKey calls this with the context we set up. + * Ignore the private key passed in, and fetch fields from the context. + * Write pub and priv keys together into the keystore. And capture any error + * codes into the context for later use by the caller of wc_*_MakeKey() */ +static int _LmsKeygenWriteCb(const byte* priv, word32 privSz, void* context) { - (void)priv; (void)privSz; (void)context; - return WC_LMS_RC_SAVED_TO_NV_MEMORY; + whServerStatefulSigKeygenBridge* b = + (whServerStatefulSigKeygenBridge*)context; + int rc; + + (void)priv; + (void)privSz; + if ((b == NULL) || (b->key == NULL)) { + return WC_LMS_RC_BAD_ARG; + } + + rc = wh_Server_LmsKeyCacheImport(b->server, (LmsKey*)b->key, b->keyId, + b->flags, b->labelSize, b->label); + if (rc == WH_ERROR_OK) { + rc = wh_Server_KeystoreCommitKey(b->server, b->keyId); + } + b->status = rc; + return (rc == WH_ERROR_OK) ? WC_LMS_RC_SAVED_TO_NV_MEMORY + : WC_LMS_RC_WRITE_FAIL; } static int _LmsDummyReadCb(byte* priv, word32 privSz, void* context) { @@ -6991,10 +7024,12 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, void* clientPubAddr = NULL; word32 pubLen32 = 0; whKeyId keyId; + whServerStatefulSigKeygenBridge bridge; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; memset(&res, 0, sizeof(res)); + memset(&bridge, 0, sizeof(bridge)); if (inSize < sizeof(req)) { return WH_ERROR_BADARGS; @@ -7019,25 +7054,8 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, ret = wc_LmsKey_SetParameters(key, (int)req.lmsLevels, (int)req.lmsHeight, (int)req.lmsWinternitz); - if (ret == 0) { - ret = wc_LmsKey_SetWriteCb(key, _LmsDummyWriteCb); - } - 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); - } - /* 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. */ + /* Validate the buffer size and resolve the keyId before keygen. */ if (ret == 0) { ret = wc_LmsKey_GetPubLen(key, &pubLen32); } @@ -7052,7 +7070,6 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, res.dmaAddrStatus.badAddr = req.pub; } } - if (ret == 0) { keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); @@ -7061,19 +7078,34 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, } } + /* Setup context for the write callback */ if (ret == 0) { - ret = wh_Server_LmsKeyCacheImport(ctx, key, keyId, req.flags, - (uint16_t)req.labelSize, req.label); + bridge.server = ctx; + bridge.key = key; + bridge.keyId = keyId; + bridge.flags = req.flags; + bridge.label = req.label; + bridge.labelSize = (uint16_t)req.labelSize; + bridge.status = WH_ERROR_OK; + ret = wc_LmsKey_SetWriteCb(key, _LmsKeygenWriteCb); + } + if (ret == 0) { + ret = wc_LmsKey_SetReadCb(key, _LmsDummyReadCb); } - - /* Write-through before responding */ if (ret == 0) { - ret = wh_Server_KeystoreCommitKey(ctx, keyId); + ret = wc_LmsKey_SetContext(key, &bridge); + } + if (ret == 0) { + ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); + /* MakeKey fails if the cb could not persist; surface the specific + * import/commit error the bridge captured. */ + if ((ret != 0) && (bridge.status != WH_ERROR_OK)) { + ret = bridge.status; + } } - /* 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. */ + /* Key is committed. Stream the public key out via the pre-validated DMA + * buffer; the copy cannot fail, so the client always receives its keyId. */ if (ret == 0) { memcpy(clientPubAddr, key->pub, pubLen32); res.keyId = wh_KeyId_TranslateToClient(keyId); @@ -7383,26 +7415,32 @@ static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, #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; - +/* Keygen write cb: wc_*_MakeKey calls this with the context we set up. + * Ignore the private key passed in, and fetch fields from the context. + * Write pub and priv keys together into the keystore. And capture any error + * codes into the context for later use by the caller of wc_*_MakeKey() */ 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; + whServerStatefulSigKeygenBridge* b = + (whServerStatefulSigKeygenBridge*)context; + int rc; + + (void)priv; + (void)privSz; + if ((b == NULL) || (b->key == NULL)) { + return WC_XMSS_RC_BAD_ARG; + } + + rc = wh_Server_XmssKeyCacheImport(b->server, (XmssKey*)b->key, b->paramStr, + b->keyId, b->flags, b->labelSize, + b->label); + if (rc == WH_ERROR_OK) { + rc = wh_Server_KeystoreCommitKey(b->server, b->keyId); } - memcpy(cap->buf, priv, privSz); - cap->len = privSz; - return WC_XMSS_RC_SAVED_TO_NV_MEMORY; + b->status = rc; + return (rc == WH_ERROR_OK) ? WC_XMSS_RC_SAVED_TO_NV_MEMORY + : WC_XMSS_RC_WRITE_FAIL; } static enum wc_XmssRc _XmssDummyReadCb(byte* priv, word32 privSz, void* context) @@ -7425,21 +7463,13 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, XmssKey key[1]; void* clientPubAddr = NULL; word32 pubLen32 = 0; - word32 privLen32 = 0; whKeyId keyId; + whServerStatefulSigKeygenBridge bridge; 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); + memset(&bridge, 0, sizeof(bridge)); if (inSize < sizeof(req)) { return WH_ERROR_BADARGS; @@ -7467,39 +7497,8 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, } 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). */ + /* Validate the buffer size and resolve the keyId before keygen. */ if (ret == 0) { ret = wc_XmssKey_GetPubLen(key, &pubLen32); } @@ -7514,7 +7513,6 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, res.dmaAddrStatus.badAddr = req.pub; } } - if (ret == 0) { keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); @@ -7523,19 +7521,34 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, } } + /* Setup context for the write callback */ if (ret == 0) { - ret = wh_Server_XmssKeyCacheImport(ctx, key, req.xmssParamStr, keyId, - req.flags, (uint16_t)req.labelSize, - req.label); + bridge.server = ctx; + bridge.key = key; + bridge.paramStr = req.xmssParamStr; + bridge.keyId = keyId; + bridge.flags = req.flags; + bridge.label = req.label; + bridge.labelSize = (uint16_t)req.labelSize; + bridge.status = WH_ERROR_OK; + ret = wc_XmssKey_SetWriteCb(key, _XmssKeygenWriteCb); } - - /* Write-through before responding */ if (ret == 0) { - ret = wh_Server_KeystoreCommitKey(ctx, keyId); + ret = wc_XmssKey_SetReadCb(key, _XmssDummyReadCb); + } + if (ret == 0) { + ret = wc_XmssKey_SetContext(key, &bridge); + } + if (ret == 0) { + ret = wc_XmssKey_MakeKey(key, ctx->crypto->rng); + /* When wolfcrypt reports a failure, prioritize the callback status */ + if ((ret != 0) && (bridge.status != WH_ERROR_OK)) { + ret = bridge.status; + } } - /* 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. */ + /* Key is committed. Stream the public key out via the pre-validated DMA + * buffer; the copy cannot fail, so the client always receives its keyId. */ if (ret == 0) { memcpy(clientPubAddr, key->pk, pubLen32); res.keyId = wh_KeyId_TranslateToClient(keyId); @@ -7553,9 +7566,6 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, 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 */ } From 516d5d3fed71562e143f843d4e451588802bdb9a Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 21:06:48 -0700 Subject: [PATCH 14/27] Add whCryptoStatefulSigHeader in place of fixed offsets, add check for reserved field being 0 --- src/wh_crypto.c | 64 ++++++++++++++++++++---------------------- src/wh_server_crypto.c | 23 +++++++-------- wolfhsm/wh_crypto.h | 17 ++++++++--- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/wh_crypto.c b/src/wh_crypto.c index aaf4c5454..298326532 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -498,14 +498,10 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, /* 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]; + * whCryptoStatefulSigHeader header; (fixed fields, see wh_crypto.h) + * uint8_t paramDescriptor[header.paramLen]; + * uint8_t pub[header.pubLen]; + * uint8_t priv[header.privLen]; * * paramDescriptor encodes the parameter set: * LMS : 3 bytes (levels, height, winternitz) - paramLen == 3 @@ -519,37 +515,37 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, int wh_Crypto_IsStatefulSigPrivBlob(const uint8_t* buffer, uint16_t size) { - uint32_t magic; - uint16_t privLen; + whCryptoStatefulSigHeader hdr; - /* Need the full fixed header to read privLen at offset 6. */ - if ((buffer == NULL) || (size < WH_CRYPTO_STATEFUL_SIG_HEADER_SZ)) { + /* Need the full fixed header to inspect magic and privLen. */ + if ((buffer == NULL) || (size < sizeof(hdr))) { 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)); - if ((magic != WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS) && - (magic != WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_XMSS)) { + memcpy(&hdr, buffer, sizeof(hdr)); + /* Match what deserialize requires before it would accept the blob. */ + if ((hdr.magic != WH_CRYPTO_STATEFUL_SIG_BLOB_MAGIC_LMS) && + (hdr.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; + return (hdr.privLen > 0) ? 1 : 0; } 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)); + whCryptoStatefulSigHeader hdr; + hdr.magic = magic; + hdr.pubLen = pubLen; + hdr.privLen = privLen; + hdr.paramLen = paramLen; + hdr.reserved = 0; + /* Copy via a local struct so the on-blob bytes are not assumed to be + * struct-aligned. */ + memcpy(buffer, &hdr, sizeof(hdr)); return WH_ERROR_OK; } @@ -557,22 +553,22 @@ 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; + whCryptoStatefulSigHeader hdr; - if (size < WH_CRYPTO_STATEFUL_SIG_HEADER_SZ) { + if (size < sizeof(hdr)) { return WH_ERROR_BADARGS; } - memcpy(&magic, buffer + 0, sizeof(magic)); - if (magic != expectMagic) { + memcpy(&hdr, buffer, sizeof(hdr)); + /* Magic and the zeroed reserved field together validate the header. */ + if ((hdr.magic != expectMagic) || (hdr.reserved != 0)) { 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) { + if ((uint32_t)sizeof(hdr) + hdr.paramLen + hdr.pubLen + hdr.privLen > size) { return WH_ERROR_BADARGS; } + *pubLen = hdr.pubLen; + *privLen = hdr.privLen; + *paramLen = hdr.paramLen; return WH_ERROR_OK; } #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index d541d58e7..3bee78d1c 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -903,8 +903,8 @@ typedef struct whServerStatefulSigBridge { 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 hdrSz; /* fixed header size (offset to params) */ + uint16_t pubLen; /* priv begins at hdrSz + paramLen + pubLen */ uint16_t paramLen; uint16_t slotCapacity; } whServerStatefulSigBridge; @@ -915,11 +915,11 @@ 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). */ +/* Update the slot blob's privLen header field in place. */ 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)); + uint8_t* p = slotBuf + offsetof(whCryptoStatefulSigHeader, privLen); + memcpy(p, &privLen, sizeof(privLen)); } #if defined(WOLFSSL_HAVE_LMS) && defined(WOLFHSM_CFG_DMA) @@ -6932,30 +6932,27 @@ static int _HandlePqcKemAlgorithmDma(whServerContext* ctx, uint16_t magic, #endif /* WOLFSSL_HAVE_MLKEM */ #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. */ +/* Decode the slot blob's header lengths into the bridge struct. */ static int _StatefulBridgeFromSlot(whServerStatefulSigBridge* b, whServerContext* server, whKeyId keyId, uint8_t* slotBuf, whNvmMetadata* meta, uint16_t slotCapacity) { - uint16_t pubLen, paramLen; + whCryptoStatefulSigHeader hdr; 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)); + memcpy(&hdr, slotBuf, sizeof(hdr)); 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->paramLen = hdr.paramLen; + b->pubLen = hdr.pubLen; b->slotCapacity = slotCapacity; return WH_ERROR_OK; } diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 82dbad7bb..29328f60f 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -130,11 +130,20 @@ int wh_Crypto_MlKemDeserializeKey(const uint8_t* buffer, uint16_t size, #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. */ +/* Fixed header of a stateful-sig (LMS/XMSS) slot blob. + * The full blob layout is documented in wh_crypto.c. */ +typedef struct { + uint32_t magic; + uint16_t pubLen; + uint16_t privLen; + uint16_t paramLen; + uint16_t reserved; /* must be 0 */ +} whCryptoStatefulSigHeader; + +/* Ensure the header stays a fixed size for ABI compatibility. */ #define WH_CRYPTO_STATEFUL_SIG_HEADER_SZ 12 +WH_UTILS_STATIC_ASSERT( + sizeof(whCryptoStatefulSigHeader) == WH_CRYPTO_STATEFUL_SIG_HEADER_SZ, ""); /* 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 From 6601aeb65f85e927f5f820c3b5a913c4eb87c865 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 21:09:02 -0700 Subject: [PATCH 15/27] Remove "post-patch" comment --- src/wh_server_crypto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 3bee78d1c..ee3ffeaff 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -7213,7 +7213,7 @@ static int _HandleLmsSignDma(whServerContext* ctx, uint16_t magic, int devId, } } if (ret == WH_ERROR_OK) { - /* wolfCrypt's flow (verified against wc_lms.c:1439-1474 post-patch): + /* wolfCrypt's flow: * 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 From 4935746c8ac92d58fbcf664dea6d887a82488738 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 21:16:55 -0700 Subject: [PATCH 16/27] Add req_len checks in LMS/XMSS SigsLeft routines --- src/wh_client_crypto.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index a9ba5fc22..0b2585729 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -10773,6 +10773,10 @@ int wh_Client_LmsSigsLeftDma(whClientContext* ctx, LmsKey* key) 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; @@ -11213,6 +11217,10 @@ int wh_Client_XmssSigsLeftDma(whClientContext* ctx, XmssKey* key) 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; From 1a5ef03f7a1a5d8eb0f0b0521b25945265b055cb Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 21:38:28 -0700 Subject: [PATCH 17/27] Increase WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE when LMS or XMSS is enabled --- wolfhsm/wh_settings.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 5a1e3bce5..4c9453333 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -245,8 +245,8 @@ /* Size in bytes of each big key cache buffer */ #ifndef WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE -#ifdef WOLFSSL_HAVE_MLDSA -/* If MLDSA enabled, default to large enough to hold the largest MLDSA key */ +#if defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_XMSS) || \ + defined(WOLFSSL_HAVE_LMS) #define WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE 8192 #else /* Sane default to hold ASN.1 RSA 4096 public+private key */ From d9bdb8e5dc5c471e369b8cfe6da1687756bcd64e Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 22:36:57 -0700 Subject: [PATCH 18/27] Add NVM lock/unlock in LMS/XMSS --- src/wh_server_crypto.c | 53 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index ee3ffeaff..dd3df4ed0 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -7021,6 +7021,7 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, void* clientPubAddr = NULL; word32 pubLen32 = 0; whKeyId keyId; + int locked = 0; whServerStatefulSigKeygenBridge bridge; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; @@ -7067,6 +7068,11 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, res.dmaAddrStatus.badAddr = req.pub; } } + /* Lock from keyID allocation until NVM commit (inside _LmsKeygenWriteCb) */ + if (ret == 0) { + ret = WH_SERVER_NVM_LOCK(ctx); + locked = (ret == WH_ERROR_OK); + } if (ret == 0) { keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); @@ -7101,6 +7107,11 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, } } + if (locked) { + (void)WH_SERVER_NVM_UNLOCK(ctx); + locked = 0; + } + /* Key is committed. Stream the public key out via the pre-validated DMA * buffer; the copy cannot fail, so the client always receives its keyId. */ if (ret == 0) { @@ -7294,7 +7305,12 @@ static int _HandleLmsVerifyDma(whServerContext* ctx, uint16_t magic, int devId, ret = wc_LmsKey_Init(key, NULL, devId); if (ret == 0) { keyInited = 1; - ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); + /* Lock while reading the key in case of concurrent sign op */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); + (void)WH_SERVER_NVM_UNLOCK(ctx); + } } if (ret == WH_ERROR_OK) { /* Deserialize leaves the key in PARMSET; wc_LmsKey_Verify needs @@ -7354,6 +7370,9 @@ static int _HandleLmsVerifyDma(whServerContext* ctx, uint16_t magic, int devId, return ret; } +/* wc_LmsKey_SigsLeft reads key->priv_raw directly - no key state machine and no + * read callback - so deserializing the cached blob into the key is enough; no + * Reload is needed. Contrast the heavier _HandleXmssSigsLeftDma. */ static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t inSize, void* cryptoDataOut, @@ -7392,7 +7411,12 @@ static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, ret = wc_LmsKey_Init(key, NULL, devId); if (ret == 0) { keyInited = 1; - ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); + /* Lock while reading the key in case of concurrent sign op */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + ret = wh_Server_LmsKeyCacheExport(ctx, keyId, key); + (void)WH_SERVER_NVM_UNLOCK(ctx); + } } if (ret == WH_ERROR_OK) { res.sigsLeft = (uint32_t)wc_LmsKey_SigsLeft(key); @@ -7461,6 +7485,7 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, void* clientPubAddr = NULL; word32 pubLen32 = 0; whKeyId keyId; + int locked = 0; whServerStatefulSigKeygenBridge bridge; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; @@ -7510,6 +7535,11 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, res.dmaAddrStatus.badAddr = req.pub; } } + /* Lock from keyID allocation until NVM commit (inside _LmsKeygenWriteCb) */ + if (ret == 0) { + ret = WH_SERVER_NVM_LOCK(ctx); + locked = (ret == WH_ERROR_OK); + } if (ret == 0) { keyId = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); @@ -7543,6 +7573,10 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, ret = bridge.status; } } + if (locked) { + (void)WH_SERVER_NVM_UNLOCK(ctx); + locked = 0; + } /* Key is committed. Stream the public key out via the pre-validated DMA * buffer; the copy cannot fail, so the client always receives its keyId. */ @@ -7724,7 +7758,12 @@ static int _HandleXmssVerifyDma(whServerContext* ctx, uint16_t magic, ret = wc_XmssKey_Init(key, NULL, devId); if (ret == 0) { keyInited = 1; - ret = wh_Server_XmssKeyCacheExport(ctx, keyId, key); + /* Lock while reading the key in case of concurrent sign op */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + ret = wh_Server_XmssKeyCacheExport(ctx, keyId, key); + (void)WH_SERVER_NVM_UNLOCK(ctx); + } } if (ret == WH_ERROR_OK) { /* Deserialize leaves the key in PARMSET; wc_XmssKey_Verify needs @@ -7822,6 +7861,12 @@ static int _HandleXmssSigsLeftDma(whServerContext* ctx, uint16_t magic, return WH_ERROR_BADARGS; } + /* Lock during load+reload in case of concurrent sign op */ + 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); @@ -7854,6 +7899,8 @@ static int _HandleXmssSigsLeftDma(whServerContext* ctx, uint16_t magic, wc_XmssKey_Free(key); } + (void)WH_SERVER_NVM_UNLOCK(ctx); + (void)wh_MessageCrypto_TranslatePqcStatefulSigSigsLeftDmaResponse( magic, &res, (whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse*)cryptoDataOut); From 50a127eb610dad981af6080c0987691092f9bca8 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 23:47:45 -0700 Subject: [PATCH 19/27] Fail build when WOLFSSL_WC_LMS_SERIALIZE_STATE is defined --- wolfhsm/wh_crypto.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 29328f60f..4cbd476fc 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -154,6 +154,12 @@ int wh_Crypto_IsStatefulSigPrivBlob(const uint8_t* buffer, uint16_t size); #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS +/* WOLFSSL_WC_LMS_SERIALIZE_STATE makes the key size much larger and is + * not supported by wolfHSM */ +#ifdef WOLFSSL_WC_LMS_SERIALIZE_STATE +#error "wolfHSM LMS key storage does not support WOLFSSL_WC_LMS_SERIALIZE_STATE" +#endif + /* Store an LmsKey (parameter set + public key + priv_raw) into a byte * sequence. * From 8c45517225f4ed451d53e5c1217f9a810574f2cd Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Thu, 18 Jun 2026 23:48:21 -0700 Subject: [PATCH 20/27] Add comment explaining duplicate copy possibility in wh_Crypto_LmsDeserializeKey --- src/wh_crypto.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 298326532..5e8da90f7 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -672,6 +672,8 @@ int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, memcpy(key->pub, p, pubLen); if (privLen > 0) { p += pubLen; + /* SigsLeft path does not reload, so copy priv_raw into the key. + * For the Sign path in software, this is a duplicate read. */ memcpy(key->priv_raw, p, privLen); } From bde9641d37889293c0018e578b8aec19809888fb Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 19 Jun 2026 00:14:10 -0700 Subject: [PATCH 21/27] Rename StatefulSig Bridge to Context for consistency with the rest of wolfHSM --- src/wh_crypto.c | 2 +- src/wh_server_crypto.c | 154 ++++++++++++++++++++--------------------- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 5e8da90f7..b2cbd5c18 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -833,7 +833,7 @@ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; memcpy(key->pk, p, pubLen); /* 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 + * read it via the slot ReadCb against the cached slot (sk is allocated * by Reload, not by deserialize). */ (void)privLen; diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index dd3df4ed0..8dcadaca2 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -887,7 +887,7 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, #if (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) && \ defined(WOLFHSM_CFG_DMA) -/* Stateful-key persistence bridge. +/* Stateful-key persistence context. * * 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 @@ -895,10 +895,10 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, * 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. * - * The bridge keeps a pointer into the server's cache slot blob (laid out by + * This context 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 { +typedef struct whServerStatefulSigCtx { whServerContext* server; whKeyId keyId; whNvmMetadata* meta; /* points at the cache slot's metadata */ @@ -907,25 +907,25 @@ typedef struct whServerStatefulSigBridge { uint16_t pubLen; /* priv begins at hdrSz + paramLen + pubLen */ uint16_t paramLen; uint16_t slotCapacity; -} whServerStatefulSigBridge; +} whServerStatefulSigCtx; -/* Compute the priv-region offset inside the slot blob from a bridge. */ -static uint16_t _StatefulBridgePrivOffset(const whServerStatefulSigBridge* b) +/* Compute the priv-region offset inside the slot blob from the context. */ +static uint16_t _StatefulSigPrivOffset(const whServerStatefulSigCtx* b) { return (uint16_t)(b->hdrSz + b->paramLen + b->pubLen); } /* Update the slot blob's privLen header field in place. */ -static void _StatefulBridgeWritePrivLen(uint8_t* slotBuf, uint16_t privLen) +static void _StatefulSigWritePrivLen(uint8_t* slotBuf, uint16_t privLen) { uint8_t* p = slotBuf + offsetof(whCryptoStatefulSigHeader, privLen); memcpy(p, &privLen, sizeof(privLen)); } #if defined(WOLFSSL_HAVE_LMS) && defined(WOLFHSM_CFG_DMA) -static int _LmsBridgeWriteCb(const byte* priv, word32 privSz, void* context) +static int _LmsSlotWriteCb(const byte* priv, word32 privSz, void* context) { - whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; uint16_t privOff; uint32_t newLen; int rc; @@ -935,14 +935,14 @@ static int _LmsBridgeWriteCb(const byte* priv, word32 privSz, void* context) return WC_LMS_RC_BAD_ARG; } - privOff = _StatefulBridgePrivOffset(b); + privOff = _StatefulSigPrivOffset(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); + _StatefulSigWritePrivLen(b->slotBuf, (uint16_t)privSz); b->meta->len = (whNvmSize)newLen; /* Atomic dual-partition commit. Wolfcrypt aborts the sign if this @@ -954,16 +954,16 @@ static int _LmsBridgeWriteCb(const byte* priv, word32 privSz, void* context) : WC_LMS_RC_WRITE_FAIL; } -static int _LmsBridgeReadCb(byte* priv, word32 privSz, void* context) +static int _LmsSlotReadCb(byte* priv, word32 privSz, void* context) { - whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; uint16_t privOff; if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { return WC_LMS_RC_BAD_ARG; } - privOff = _StatefulBridgePrivOffset(b); + privOff = _StatefulSigPrivOffset(b); if ((uint32_t)privOff + privSz > b->meta->len) { return WC_LMS_RC_READ_FAIL; } @@ -974,10 +974,10 @@ static int _LmsBridgeReadCb(byte* priv, word32 privSz, void* context) #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, +static enum wc_XmssRc _XmssSlotWriteCb(const byte* priv, word32 privSz, void* context) { - whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; uint16_t privOff; uint32_t newLen; int rc; @@ -987,14 +987,14 @@ static enum wc_XmssRc _XmssBridgeWriteCb(const byte* priv, word32 privSz, return WC_XMSS_RC_BAD_ARG; } - privOff = _StatefulBridgePrivOffset(b); + privOff = _StatefulSigPrivOffset(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); + _StatefulSigWritePrivLen(b->slotBuf, (uint16_t)privSz); b->meta->len = (whNvmSize)newLen; rc = wh_Nvm_AddObjectWithReclaim(b->server->nvm, b->meta, b->meta->len, @@ -1003,17 +1003,17 @@ static enum wc_XmssRc _XmssBridgeWriteCb(const byte* priv, word32 privSz, : WC_XMSS_RC_WRITE_FAIL; } -static enum wc_XmssRc _XmssBridgeReadCb(byte* priv, word32 privSz, +static enum wc_XmssRc _XmssSlotReadCb(byte* priv, word32 privSz, void* context) { - whServerStatefulSigBridge* b = (whServerStatefulSigBridge*)context; + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; uint16_t privOff; if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { return WC_XMSS_RC_BAD_ARG; } - privOff = _StatefulBridgePrivOffset(b); + privOff = _StatefulSigPrivOffset(b); if ((uint32_t)privOff + privSz > b->meta->len) { return WC_XMSS_RC_READ_FAIL; } @@ -6932,8 +6932,8 @@ static int _HandlePqcKemAlgorithmDma(whServerContext* ctx, uint16_t magic, #endif /* WOLFSSL_HAVE_MLKEM */ #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) -/* Decode the slot blob's header lengths into the bridge struct. */ -static int _StatefulBridgeFromSlot(whServerStatefulSigBridge* b, +/* Decode the slot blob's header lengths into the context struct. */ +static int _StatefulSigFromSlot(whServerStatefulSigCtx* b, whServerContext* server, whKeyId keyId, uint8_t* slotBuf, whNvmMetadata* meta, @@ -6957,13 +6957,13 @@ static int _StatefulBridgeFromSlot(whServerStatefulSigBridge* b, return WH_ERROR_OK; } -/* Keygen persistence bridge. The sign bridge rewrites the priv region of an +/* Keygen persistence context. The sign context rewrites the priv region of an * existing slot; keygen instead builds a brand-new slot. Its write cb * serializes the full key and commits it to NVM while the private key is * still live, honoring wolfCrypt's "persist before returning success" * contract. The cb can only return wolfCrypt's coarse pass/fail code, so * status carries the WH_ERROR_* detail back to the handler. */ -typedef struct whServerStatefulSigKeygenBridge { +typedef struct whServerStatefulSigKeygenCtx { whServerContext* server; void* key; /* LmsKey* or XmssKey*, alg-specific */ const char* paramStr; /* XMSS param string; NULL for LMS */ @@ -6972,7 +6972,7 @@ typedef struct whServerStatefulSigKeygenBridge { whNvmFlags flags; uint16_t labelSize; int status; /* WH_ERROR_* from import/commit */ -} whServerStatefulSigKeygenBridge; +} whServerStatefulSigKeygenCtx; #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS @@ -6982,8 +6982,8 @@ typedef struct whServerStatefulSigKeygenBridge { * codes into the context for later use by the caller of wc_*_MakeKey() */ static int _LmsKeygenWriteCb(const byte* priv, word32 privSz, void* context) { - whServerStatefulSigKeygenBridge* b = - (whServerStatefulSigKeygenBridge*)context; + whServerStatefulSigKeygenCtx* b = + (whServerStatefulSigKeygenCtx*)context; int rc; (void)priv; @@ -7022,12 +7022,12 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, word32 pubLen32 = 0; whKeyId keyId; int locked = 0; - whServerStatefulSigKeygenBridge bridge; + whServerStatefulSigKeygenCtx sigCtx; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; memset(&res, 0, sizeof(res)); - memset(&bridge, 0, sizeof(bridge)); + memset(&sigCtx, 0, sizeof(sigCtx)); if (inSize < sizeof(req)) { return WH_ERROR_BADARGS; @@ -7083,27 +7083,27 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, /* Setup context for the write callback */ if (ret == 0) { - bridge.server = ctx; - bridge.key = key; - bridge.keyId = keyId; - bridge.flags = req.flags; - bridge.label = req.label; - bridge.labelSize = (uint16_t)req.labelSize; - bridge.status = WH_ERROR_OK; + sigCtx.server = ctx; + sigCtx.key = key; + sigCtx.keyId = keyId; + sigCtx.flags = req.flags; + sigCtx.label = req.label; + sigCtx.labelSize = (uint16_t)req.labelSize; + sigCtx.status = WH_ERROR_OK; ret = wc_LmsKey_SetWriteCb(key, _LmsKeygenWriteCb); } if (ret == 0) { ret = wc_LmsKey_SetReadCb(key, _LmsDummyReadCb); } if (ret == 0) { - ret = wc_LmsKey_SetContext(key, &bridge); + ret = wc_LmsKey_SetContext(key, &sigCtx); } if (ret == 0) { ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); /* MakeKey fails if the cb could not persist; surface the specific - * import/commit error the bridge captured. */ - if ((ret != 0) && (bridge.status != WH_ERROR_OK)) { - ret = bridge.status; + * import/commit error the context captured. */ + if ((ret != 0) && (sigCtx.status != WH_ERROR_OK)) { + ret = sigCtx.status; } } @@ -7153,7 +7153,7 @@ static int _HandleLmsSignDma(whServerContext* ctx, uint16_t magic, int devId, whKeyId keyId; uint8_t* cacheBuf; whNvmMetadata* cacheMeta; - whServerStatefulSigBridge bridge; + whServerStatefulSigCtx sigCtx; whMessageCrypto_PqcStatefulSigSignDmaRequest req; whMessageCrypto_PqcStatefulSigSignDmaResponse res; @@ -7197,14 +7197,14 @@ static int _HandleLmsSignDma(whServerContext* ctx, uint16_t magic, int devId, key); } if (ret == WH_ERROR_OK) { - ret = _StatefulBridgeFromSlot( - &bridge, ctx, keyId, cacheBuf, cacheMeta, + ret = _StatefulSigFromSlot( + &sigCtx, 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); + (void)wc_LmsKey_SetWriteCb(key, _LmsSlotWriteCb); + (void)wc_LmsKey_SetReadCb(key, _LmsSlotReadCb); + (void)wc_LmsKey_SetContext(key, &sigCtx); ret = wc_LmsKey_Reload(key); } if (ret == WH_ERROR_OK) { @@ -7227,9 +7227,9 @@ static int _HandleLmsSignDma(whServerContext* ctx, uint16_t magic, int devId, /* wolfCrypt's flow: * 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 + * 2. write_private_key (our slot write cb) is called with the new * priv_raw and atomically commits it to NVM. - * 3. If the bridge returns anything other than + * 3. If the cb 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 @@ -7443,8 +7443,8 @@ static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, static enum wc_XmssRc _XmssKeygenWriteCb(const byte* priv, word32 privSz, void* context) { - whServerStatefulSigKeygenBridge* b = - (whServerStatefulSigKeygenBridge*)context; + whServerStatefulSigKeygenCtx* b = + (whServerStatefulSigKeygenCtx*)context; int rc; (void)priv; @@ -7486,12 +7486,12 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, word32 pubLen32 = 0; whKeyId keyId; int locked = 0; - whServerStatefulSigKeygenBridge bridge; + whServerStatefulSigKeygenCtx sigCtx; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; memset(&res, 0, sizeof(res)); - memset(&bridge, 0, sizeof(bridge)); + memset(&sigCtx, 0, sizeof(sigCtx)); if (inSize < sizeof(req)) { return WH_ERROR_BADARGS; @@ -7550,27 +7550,27 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, /* Setup context for the write callback */ if (ret == 0) { - bridge.server = ctx; - bridge.key = key; - bridge.paramStr = req.xmssParamStr; - bridge.keyId = keyId; - bridge.flags = req.flags; - bridge.label = req.label; - bridge.labelSize = (uint16_t)req.labelSize; - bridge.status = WH_ERROR_OK; + sigCtx.server = ctx; + sigCtx.key = key; + sigCtx.paramStr = req.xmssParamStr; + sigCtx.keyId = keyId; + sigCtx.flags = req.flags; + sigCtx.label = req.label; + sigCtx.labelSize = (uint16_t)req.labelSize; + sigCtx.status = WH_ERROR_OK; ret = wc_XmssKey_SetWriteCb(key, _XmssKeygenWriteCb); } if (ret == 0) { ret = wc_XmssKey_SetReadCb(key, _XmssDummyReadCb); } if (ret == 0) { - ret = wc_XmssKey_SetContext(key, &bridge); + ret = wc_XmssKey_SetContext(key, &sigCtx); } if (ret == 0) { ret = wc_XmssKey_MakeKey(key, ctx->crypto->rng); /* When wolfcrypt reports a failure, prioritize the callback status */ - if ((ret != 0) && (bridge.status != WH_ERROR_OK)) { - ret = bridge.status; + if ((ret != 0) && (sigCtx.status != WH_ERROR_OK)) { + ret = sigCtx.status; } } if (locked) { @@ -7619,7 +7619,7 @@ static int _HandleXmssSignDma(whServerContext* ctx, uint16_t magic, int devId, whKeyId keyId; uint8_t* cacheBuf; whNvmMetadata* cacheMeta; - whServerStatefulSigBridge bridge; + whServerStatefulSigCtx sigCtx; whMessageCrypto_PqcStatefulSigSignDmaRequest req; whMessageCrypto_PqcStatefulSigSignDmaResponse res; @@ -7661,14 +7661,14 @@ static int _HandleXmssSignDma(whServerContext* ctx, uint16_t magic, int devId, key); } if (ret == WH_ERROR_OK) { - ret = _StatefulBridgeFromSlot( - &bridge, ctx, keyId, cacheBuf, cacheMeta, + ret = _StatefulSigFromSlot( + &sigCtx, 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); + (void)wc_XmssKey_SetWriteCb(key, _XmssSlotWriteCb); + (void)wc_XmssKey_SetReadCb(key, _XmssSlotReadCb); + (void)wc_XmssKey_SetContext(key, &sigCtx); ret = wc_XmssKey_Reload(key); } if (ret == WH_ERROR_OK) { @@ -7839,7 +7839,7 @@ static int _HandleXmssSigsLeftDma(whServerContext* ctx, uint16_t magic, whKeyId keyId; uint8_t* cacheBuf; whNvmMetadata* cacheMeta; - whServerStatefulSigBridge bridge; + whServerStatefulSigCtx sigCtx; whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest req; whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse res; @@ -7879,16 +7879,16 @@ static int _HandleXmssSigsLeftDma(whServerContext* ctx, uint16_t magic, key); } if (ret == WH_ERROR_OK) { - ret = _StatefulBridgeFromSlot( - &bridge, ctx, keyId, cacheBuf, cacheMeta, + ret = _StatefulSigFromSlot( + &sigCtx, 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, + /* Reload uses the slot 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); + (void)wc_XmssKey_SetWriteCb(key, _XmssSlotWriteCb); + (void)wc_XmssKey_SetReadCb(key, _XmssSlotReadCb); + (void)wc_XmssKey_SetContext(key, &sigCtx); ret = wc_XmssKey_Reload(key); } if (ret == WH_ERROR_OK) { From f5210864c5919f15842dfd999060c8199c5b1e61 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 19 Jun 2026 14:54:32 -0700 Subject: [PATCH 22/27] Redesign LMS/XMSS MakeKey per review discussion --- src/wh_crypto.c | 67 ++++++++++++--- src/wh_server_crypto.c | 191 ++++++++++++++++++++++++----------------- wolfhsm/wh_crypto.h | 10 +++ 3 files changed, 176 insertions(+), 92 deletions(-) diff --git a/src/wh_crypto.c b/src/wh_crypto.c index b2cbd5c18..6f10eeb15 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -593,6 +593,9 @@ int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, if (ret != 0) { return WH_ERROR_BADARGS; } + if (pubLen32 > UINT16_MAX) { + return WH_ERROR_BADARGS; + } pubLen = (uint16_t)pubLen32; privLen = (uint16_t)HSS_PRIVATE_KEY_LEN(key->params->hash_len); @@ -698,6 +701,9 @@ int wh_Crypto_LmsSerializePubKey(LmsKey* key, uint16_t max_size, if (ret != 0) { return WH_ERROR_BADARGS; } + if (pubLen32 > UINT16_MAX) { + return WH_ERROR_BADARGS; + } pubLen = (uint16_t)pubLen32; totalLen = (uint32_t)WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen; @@ -723,21 +729,24 @@ int wh_Crypto_LmsSerializePubKey(LmsKey* key, uint16_t max_size, #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) +/* Serialize an XMSS slot blob: header, parameter string, and public key, plus + * the private key when priv is non-NULL. The keygen path passes priv == NULL + * because its write callback stores the private key into the buffer separately + * (wolfCrypt zeroizes key->sk right after that callback). */ +static int _XmssSerializeSlot(XmssKey* key, const char* paramStr, + const uint8_t* priv, uint16_t privLen, + 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)) { + (out_size == NULL) || (key->params == NULL)) { return WH_ERROR_BADARGS; } @@ -745,15 +754,13 @@ int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, if (ret != 0) { return WH_ERROR_BADARGS; } - ret = wc_XmssKey_GetPrivLen(key, &privLen32); - if (ret != 0) { + if (pubLen32 > UINT16_MAX) { return WH_ERROR_BADARGS; } - pubLen = (uint16_t)pubLen32; - privLen = (uint16_t)privLen32; + pubLen = (uint16_t)pubLen32; strLen = strlen(paramStr); - if (strLen >= 0xFFFFu) { + if (strLen >= UINT16_MAX) { return WH_ERROR_BADARGS; } paramLen = (uint16_t)(strLen + 1); /* include NUL */ @@ -771,13 +778,44 @@ int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, 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); + if (priv != NULL) { + memcpy(buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen + pubLen, + priv, privLen); + } *out_size = (uint16_t)totalLen; return WH_ERROR_OK; } +int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, + uint16_t max_size, uint8_t* buffer, + uint16_t* out_size) +{ + word32 privLen32 = 0; + int ret; + + if ((key == NULL) || (key->sk == NULL)) { + return WH_ERROR_BADARGS; + } + ret = wc_XmssKey_GetPrivLen(key, &privLen32); + if (ret != 0) { + return WH_ERROR_BADARGS; + } + if (privLen32 > UINT16_MAX) { + return WH_ERROR_BADARGS; + } + return _XmssSerializeSlot(key, paramStr, key->sk, (uint16_t)privLen32, + max_size, buffer, out_size); +} + +int wh_Crypto_XmssSerializeKeyNoPriv(XmssKey* key, const char* paramStr, + uint16_t privLen, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size) +{ + return _XmssSerializeSlot(key, paramStr, NULL, privLen, max_size, buffer, + out_size); +} + int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, XmssKey* key) { @@ -860,6 +898,9 @@ int wh_Crypto_XmssSerializePubKey(XmssKey* key, const char* paramStr, if (ret != 0) { return WH_ERROR_BADARGS; } + if (pubLen32 > UINT16_MAX) { + return WH_ERROR_BADARGS; + } pubLen = (uint16_t)pubLen32; strLen = strlen(paramStr); diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 8dcadaca2..f073ea810 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -6957,49 +6957,32 @@ static int _StatefulSigFromSlot(whServerStatefulSigCtx* b, return WH_ERROR_OK; } -/* Keygen persistence context. The sign context rewrites the priv region of an - * existing slot; keygen instead builds a brand-new slot. Its write cb - * serializes the full key and commits it to NVM while the private key is - * still live, honoring wolfCrypt's "persist before returning success" - * contract. The cb can only return wolfCrypt's coarse pass/fail code, so - * status carries the WH_ERROR_* detail back to the handler. */ +/* Keygen persistence context for XMSS. wolfCrypt zeroizes key->sk right after + * the keygen write callback returns, so the callback is the only point where + * the private key is live. The callback copies the private key it is handed + * into the private-key region of the handler-owned cache buffer; the handler + * then fills in the public portion and commits to NVM. The cb can only return + * wolfCrypt's coarse pass/fail code, so status carries the WH_ERROR_* detail + * back to the handler. LMS does not use this: its private state survives + * MakeKey, so its handler serializes directly. */ typedef struct whServerStatefulSigKeygenCtx { - whServerContext* server; - void* key; /* LmsKey* or XmssKey*, alg-specific */ - const char* paramStr; /* XMSS param string; NULL for LMS */ - uint8_t* label; - whKeyId keyId; - whNvmFlags flags; - uint16_t labelSize; - int status; /* WH_ERROR_* from import/commit */ + uint8_t* slotBuf; /* handler-owned cache slot buffer */ + uint16_t slotCapacity; + uint16_t privOff; /* offset of the private-key region in slotBuf */ + uint16_t privLen; /* out: private key length the cb received */ + int status; /* WH_ERROR_* from the cb */ } whServerStatefulSigKeygenCtx; #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS -/* Keygen write cb: wc_*_MakeKey calls this with the context we set up. - * Ignore the private key passed in, and fetch fields from the context. - * Write pub and priv keys together into the keystore. And capture any error - * codes into the context for later use by the caller of wc_*_MakeKey() */ -static int _LmsKeygenWriteCb(const byte* priv, word32 privSz, void* context) +/* Keygen callbacks: wc_LmsKey_MakeKey requires a write cb to be set and to + * report success, but for keygen the generated private state stays live in + * key->priv_raw. We serialize and commit the full slot after MakeKey returns, + * so these callbacks are no-ops that only satisfy wolfCrypt's contract. */ +static int _LmsDummyWriteCb(const byte* priv, word32 privSz, void* context) { - whServerStatefulSigKeygenCtx* b = - (whServerStatefulSigKeygenCtx*)context; - int rc; - - (void)priv; - (void)privSz; - if ((b == NULL) || (b->key == NULL)) { - return WC_LMS_RC_BAD_ARG; - } - - rc = wh_Server_LmsKeyCacheImport(b->server, (LmsKey*)b->key, b->keyId, - b->flags, b->labelSize, b->label); - if (rc == WH_ERROR_OK) { - rc = wh_Server_KeystoreCommitKey(b->server, b->keyId); - } - b->status = rc; - return (rc == WH_ERROR_OK) ? WC_LMS_RC_SAVED_TO_NV_MEMORY - : WC_LMS_RC_WRITE_FAIL; + (void)priv; (void)privSz; (void)context; + return WC_LMS_RC_SAVED_TO_NV_MEMORY; } static int _LmsDummyReadCb(byte* priv, word32 privSz, void* context) { @@ -7022,12 +7005,14 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, word32 pubLen32 = 0; whKeyId keyId; int locked = 0; - whServerStatefulSigKeygenCtx sigCtx; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + uint16_t slotCapacity = WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE; + uint16_t blobSize; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; memset(&res, 0, sizeof(res)); - memset(&sigCtx, 0, sizeof(sigCtx)); if (inSize < sizeof(req)) { return WH_ERROR_BADARGS; @@ -7068,7 +7053,11 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, res.dmaAddrStatus.badAddr = req.pub; } } - /* Lock from keyID allocation until NVM commit (inside _LmsKeygenWriteCb) */ + /* Reject labels that won't fit the slot metadata. */ + if (ret == 0 && req.labelSize > sizeof(cacheMeta->label)) { + ret = WH_ERROR_BADARGS; + } + /* Lock from keyID allocation until the slot is committed to NVM. */ if (ret == 0) { ret = WH_SERVER_NVM_LOCK(ctx); locked = (ret == WH_ERROR_OK); @@ -7081,30 +7070,38 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, } } - /* Setup context for the write callback */ + /* MakeKey requires write/read callbacks, but for keygen the private state + * stays live in key->priv_raw; the callbacks are no-ops and we serialize + * the full slot ourselves once MakeKey returns. */ if (ret == 0) { - sigCtx.server = ctx; - sigCtx.key = key; - sigCtx.keyId = keyId; - sigCtx.flags = req.flags; - sigCtx.label = req.label; - sigCtx.labelSize = (uint16_t)req.labelSize; - sigCtx.status = WH_ERROR_OK; - ret = wc_LmsKey_SetWriteCb(key, _LmsKeygenWriteCb); + ret = wc_LmsKey_SetWriteCb(key, _LmsDummyWriteCb); } if (ret == 0) { ret = wc_LmsKey_SetReadCb(key, _LmsDummyReadCb); } if (ret == 0) { - ret = wc_LmsKey_SetContext(key, &sigCtx); + ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); } + + /* Build the slot blob in RAM from the generated key, then commit it. */ if (ret == 0) { - ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); - /* MakeKey fails if the cb could not persist; surface the specific - * import/commit error the context captured. */ - if ((ret != 0) && (sigCtx.status != WH_ERROR_OK)) { - ret = sigCtx.status; + ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, + &cacheBuf, &cacheMeta); + } + if (ret == 0) { + ret = wh_Crypto_LmsSerializeKey(key, slotCapacity, cacheBuf, &blobSize); + } + if (ret == 0) { + 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 = req.flags | WH_NVM_FLAGS_NONEXPORTABLE; + cacheMeta->access = WH_NVM_ACCESS_ANY; + if (req.labelSize > 0) { + memcpy(cacheMeta->label, req.label, req.labelSize); } + ret = wh_Server_KeystoreCommitKey(ctx, keyId); } if (locked) { @@ -7445,23 +7442,21 @@ static enum wc_XmssRc _XmssKeygenWriteCb(const byte* priv, word32 privSz, { whServerStatefulSigKeygenCtx* b = (whServerStatefulSigKeygenCtx*)context; - int rc; - (void)priv; - (void)privSz; - if ((b == NULL) || (b->key == NULL)) { + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { return WC_XMSS_RC_BAD_ARG; } - rc = wh_Server_XmssKeyCacheImport(b->server, (XmssKey*)b->key, b->paramStr, - b->keyId, b->flags, b->labelSize, - b->label); - if (rc == WH_ERROR_OK) { - rc = wh_Server_KeystoreCommitKey(b->server, b->keyId); + /* Copy the private key wolfCrypt handed us into the slot's priv region; + * the key object is zeroized once this returns, so use priv/privSz here. */ + if ((uint32_t)b->privOff + privSz > b->slotCapacity) { + b->status = WH_ERROR_BUFFER_SIZE; + return WC_XMSS_RC_WRITE_FAIL; } - b->status = rc; - return (rc == WH_ERROR_OK) ? WC_XMSS_RC_SAVED_TO_NV_MEMORY - : WC_XMSS_RC_WRITE_FAIL; + memcpy(b->slotBuf + b->privOff, priv, privSz); + b->privLen = (uint16_t)privSz; + b->status = WH_ERROR_OK; + return WC_XMSS_RC_SAVED_TO_NV_MEMORY; } static enum wc_XmssRc _XmssDummyReadCb(byte* priv, word32 privSz, void* context) @@ -7486,6 +7481,10 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, word32 pubLen32 = 0; whKeyId keyId; int locked = 0; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + uint16_t slotCapacity = WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE; + uint16_t blobSize; whServerStatefulSigKeygenCtx sigCtx; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; @@ -7535,7 +7534,11 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, res.dmaAddrStatus.badAddr = req.pub; } } - /* Lock from keyID allocation until NVM commit (inside _LmsKeygenWriteCb) */ + /* Reject labels that won't fit the slot metadata. */ + if (ret == 0 && req.labelSize > sizeof(cacheMeta->label)) { + ret = WH_ERROR_BADARGS; + } + /* Lock from keyID allocation until the slot is committed to NVM. */ if (ret == 0) { ret = WH_SERVER_NVM_LOCK(ctx); locked = (ret == WH_ERROR_OK); @@ -7547,18 +7550,28 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId); } } + /* Grab the cache slot up front; the write cb writes priv into it. */ + if (ret == 0) { + ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, + &cacheBuf, &cacheMeta); + } - /* Setup context for the write callback */ + /* The write cb copies the private key into the slot's priv region (key->sk + * is valid only during that callback). Tell it where that region begins: + * after the header, parameter string, and public key. */ if (ret == 0) { - sigCtx.server = ctx; - sigCtx.key = key; - sigCtx.paramStr = req.xmssParamStr; - sigCtx.keyId = keyId; - sigCtx.flags = req.flags; - sigCtx.label = req.label; - sigCtx.labelSize = (uint16_t)req.labelSize; - sigCtx.status = WH_ERROR_OK; - ret = wc_XmssKey_SetWriteCb(key, _XmssKeygenWriteCb); + size_t pStrLen = strlen(req.xmssParamStr); + if (pStrLen >= 0xFFFFu) { + ret = WH_ERROR_BADARGS; + } + else { + sigCtx.slotBuf = cacheBuf; + sigCtx.slotCapacity = slotCapacity; + sigCtx.privOff = (uint16_t)(WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + + (pStrLen + 1) + pubLen32); + sigCtx.status = WH_ERROR_OK; + ret = wc_XmssKey_SetWriteCb(key, _XmssKeygenWriteCb); + } } if (ret == 0) { ret = wc_XmssKey_SetReadCb(key, _XmssDummyReadCb); @@ -7568,11 +7581,31 @@ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, } if (ret == 0) { ret = wc_XmssKey_MakeKey(key, ctx->crypto->rng); - /* When wolfcrypt reports a failure, prioritize the callback status */ + /* MakeKey fails if the cb could not store priv; surface that error. */ if ((ret != 0) && (sigCtx.status != WH_ERROR_OK)) { ret = sigCtx.status; } } + + /* Priv is in the slot; fill in the header and public key, then commit. */ + if (ret == 0) { + ret = wh_Crypto_XmssSerializeKeyNoPriv(key, req.xmssParamStr, + sigCtx.privLen, slotCapacity, + cacheBuf, &blobSize); + } + if (ret == 0) { + 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 = req.flags | WH_NVM_FLAGS_NONEXPORTABLE; + cacheMeta->access = WH_NVM_ACCESS_ANY; + if (req.labelSize > 0) { + memcpy(cacheMeta->label, req.label, req.labelSize); + } + ret = wh_Server_KeystoreCommitKey(ctx, keyId); + } + if (locked) { (void)WH_SERVER_NVM_UNLOCK(ctx); locked = 0; diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 4cbd476fc..21f5d9c9f 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -195,6 +195,16 @@ int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, uint16_t max_size, uint8_t* buffer, uint16_t* out_size); +/* Write the header, parameter string, and public key of an XmssKey slot blob, + * leaving the trailing privLen-byte private-key region untouched. Used by the + * keygen path: wolfCrypt hands the private key to a write callback and then + * zeroizes key->sk, so the secret state is written by that callback and only + * the public portion is filled in here. out_size returns the full blob length + * including the private-key region. */ +int wh_Crypto_XmssSerializeKeyNoPriv(XmssKey* key, const char* paramStr, + uint16_t privLen, 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); From 1c479a1b780aaa59c51531268c71d3b207979e66 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 19 Jun 2026 16:25:47 -0700 Subject: [PATCH 23/27] Add precompiler checks for VERIFY_ONLY and CFG_DMA --- src/wh_client_cryptocb.c | 15 +++++++++++--- src/wh_crypto.c | 26 +++++++++++++++++++++++++ src/wh_server_crypto.c | 40 +++++++++++++++++++++++++++----------- wolfhsm/wh_crypto.h | 5 +++++ wolfhsm/wh_server_crypto.h | 7 ++++++- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 94e0aa2fd..9da9f402e 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -869,8 +869,11 @@ static int _handlePqcStatefulSigKeyGen(whClientContext* ctx, int ret = CRYPTOCB_UNAVAILABLE; int type = info->pk.pqc_stateful_sig_kg.type; -#ifndef WOLFHSM_CFG_DMA + /* Unused when all enabled algorithms are verify-only (no keygen path). */ (void)ctx; + (void)useDma; + +#ifndef WOLFHSM_CFG_DMA if (useDma) { return WC_HW_E; } @@ -929,8 +932,11 @@ static int _handlePqcStatefulSigSign(whClientContext* ctx, wc_CryptoInfo* info, int ret = CRYPTOCB_UNAVAILABLE; int type = info->pk.pqc_stateful_sig_sign.type; -#ifndef WOLFHSM_CFG_DMA + /* Unused when all enabled algorithms are verify-only (no sign path). */ (void)ctx; + (void)useDma; + +#ifndef WOLFHSM_CFG_DMA if (useDma) { return WC_HW_E; } @@ -1067,8 +1073,11 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, int ret = CRYPTOCB_UNAVAILABLE; int type = info->pk.pqc_stateful_sig_sigs_left.type; -#ifndef WOLFHSM_CFG_DMA + /* Unused when all enabled algorithms are verify-only (no sigsLeft path). */ (void)ctx; + (void)useDma; + +#ifndef WOLFHSM_CFG_DMA if (useDma) { return WC_HW_E; } diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 6f10eeb15..3c786c7ae 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -574,6 +574,9 @@ static int _StatefulSigDecodeHeader(const uint8_t* buffer, uint16_t size, #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS +/* Serializing the private key is meaningless without it; gate out verify-only, + * where wolfCrypt omits LmsKey.priv_raw. */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, uint16_t* out_size) { @@ -622,6 +625,7 @@ int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, *out_size = (uint16_t)totalLen; return WH_ERROR_OK; } +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, LmsKey* key) @@ -666,19 +670,28 @@ int wh_Crypto_LmsDeserializeKey(const uint8_t* buffer, uint16_t size, return WH_ERROR_BADARGS; } /* privLen == 0 denotes a public-only (verify) key: load pub, no priv. */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY if ((privLen != 0) && (privLen != (uint16_t)HSS_PRIVATE_KEY_LEN(key->params->hash_len))) { return WH_ERROR_BADARGS; } +#else + /* Verify-only builds have no private key storage. */ + if (privLen != 0) { + return WH_ERROR_BADARGS; + } +#endif p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; memcpy(key->pub, p, pubLen); +#ifndef WOLFSSL_LMS_VERIFY_ONLY if (privLen > 0) { p += pubLen; /* SigsLeft path does not reload, so copy priv_raw into the key. * For the Sign path in software, this is a duplicate read. */ memcpy(key->priv_raw, p, privLen); } +#endif return WH_ERROR_OK; } @@ -729,6 +742,9 @@ int wh_Crypto_LmsSerializePubKey(LmsKey* key, uint16_t max_size, #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS +/* The private-key serializers are unavailable in verify-only, where wolfCrypt + * omits XmssKey.sk and wc_XmssKey_GetPrivLen. */ +#ifndef WOLFSSL_XMSS_VERIFY_ONLY /* Serialize an XMSS slot blob: header, parameter string, and public key, plus * the private key when priv is non-NULL. The keygen path passes priv == NULL * because its write callback stores the private key into the buffer separately @@ -815,6 +831,7 @@ int wh_Crypto_XmssSerializeKeyNoPriv(XmssKey* key, const char* paramStr, return _XmssSerializeSlot(key, paramStr, NULL, privLen, max_size, buffer, out_size); } +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, XmssKey* key) @@ -823,7 +840,9 @@ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, uint16_t privLen; uint16_t paramLen; word32 expectPubLen = 0; +#ifndef WOLFSSL_XMSS_VERIFY_ONLY word32 expectPrivLen = 0; +#endif int ret; const char* paramStr; const uint8_t* p; @@ -861,12 +880,19 @@ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, return WH_ERROR_BADARGS; } /* privLen == 0 denotes a public-only (verify) key. */ +#ifndef WOLFSSL_XMSS_VERIFY_ONLY if (privLen != 0) { ret = wc_XmssKey_GetPrivLen(key, &expectPrivLen); if ((ret != 0) || (expectPrivLen != privLen)) { return WH_ERROR_BADARGS; } } +#else + /* Verify-only builds have no private key storage. */ + if (privLen != 0) { + return WH_ERROR_BADARGS; + } +#endif p = buffer + WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + paramLen; memcpy(key->pk, p, pubLen); diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index f073ea810..b3f73cf01 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -885,7 +885,10 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, } #endif /* WOLFSSL_HAVE_MLKEM */ -#if (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) && \ +/* The sign path (and its slot callbacks) is unavailable in verify-only builds; + * gate on at least one non-verify-only stateful algorithm being enabled. */ +#if ((defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ + (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY))) && \ defined(WOLFHSM_CFG_DMA) /* Stateful-key persistence context. * @@ -922,7 +925,8 @@ static void _StatefulSigWritePrivLen(uint8_t* slotBuf, uint16_t privLen) memcpy(p, &privLen, sizeof(privLen)); } -#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFHSM_CFG_DMA) +#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFHSM_CFG_DMA) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) static int _LmsSlotWriteCb(const byte* priv, word32 privSz, void* context) { whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; @@ -971,9 +975,10 @@ static int _LmsSlotReadCb(byte* priv, word32 privSz, void* context) memcpy(priv, b->slotBuf + privOff, privSz); return WC_LMS_RC_READ_TO_MEMORY; } -#endif /* WOLFSSL_HAVE_LMS && WOLFHSM_CFG_DMA */ +#endif /* WOLFSSL_HAVE_LMS && WOLFHSM_CFG_DMA && !WOLFSSL_LMS_VERIFY_ONLY */ -#if defined(WOLFSSL_HAVE_XMSS) && defined(WOLFHSM_CFG_DMA) +#if defined(WOLFSSL_HAVE_XMSS) && defined(WOLFHSM_CFG_DMA) && \ + !defined(WOLFSSL_XMSS_VERIFY_ONLY) static enum wc_XmssRc _XmssSlotWriteCb(const byte* priv, word32 privSz, void* context) { @@ -1021,10 +1026,12 @@ static enum wc_XmssRc _XmssSlotReadCb(byte* priv, word32 privSz, 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 */ +#endif /* WOLFSSL_HAVE_XMSS && WOLFHSM_CFG_DMA && !WOLFSSL_XMSS_VERIFY_ONLY */ +#endif /* stateful sign path enabled && WOLFHSM_CFG_DMA */ #ifdef WOLFSSL_HAVE_LMS +/* Import serializes the private key, so it is unavailable in verify-only. */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY int wh_Server_LmsKeyCacheImport(whServerContext* ctx, LmsKey* key, whKeyId keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label) @@ -1058,6 +1065,7 @@ int wh_Server_LmsKeyCacheImport(whServerContext* ctx, LmsKey* key, } return ret; } +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ int wh_Server_LmsKeyCacheExport(whServerContext* ctx, whKeyId keyId, LmsKey* key) @@ -1080,6 +1088,8 @@ int wh_Server_LmsKeyCacheExport(whServerContext* ctx, whKeyId keyId, #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS +/* Import serializes the private key, so it is unavailable in verify-only. */ +#ifndef WOLFSSL_XMSS_VERIFY_ONLY int wh_Server_XmssKeyCacheImport(whServerContext* ctx, XmssKey* key, const char* paramStr, whKeyId keyId, whNvmFlags flags, uint16_t label_len, @@ -1116,6 +1126,7 @@ int wh_Server_XmssKeyCacheImport(whServerContext* ctx, XmssKey* key, } return ret; } +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ int wh_Server_XmssKeyCacheExport(whServerContext* ctx, whKeyId keyId, XmssKey* key) @@ -6932,7 +6943,10 @@ static int _HandlePqcKemAlgorithmDma(whServerContext* ctx, uint16_t magic, #endif /* WOLFSSL_HAVE_MLKEM */ #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) -/* Decode the slot blob's header lengths into the context struct. */ +/* Decode the slot blob's header lengths into the context struct. Sign-path + * only, so it is gated out of verify-only builds. */ +#if (defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ + (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY)) static int _StatefulSigFromSlot(whServerStatefulSigCtx* b, whServerContext* server, whKeyId keyId, @@ -6956,6 +6970,7 @@ static int _StatefulSigFromSlot(whServerStatefulSigCtx* b, b->slotCapacity = slotCapacity; return WH_ERROR_OK; } +#endif /* stateful sign path enabled */ /* Keygen persistence context for XMSS. wolfCrypt zeroizes key->sk right after * the keygen write callback returns, so the callback is the only point where @@ -6975,6 +6990,7 @@ typedef struct whServerStatefulSigKeygenCtx { #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_LMS +#ifndef WOLFSSL_LMS_VERIFY_ONLY /* Keygen callbacks: wc_LmsKey_MakeKey requires a write cb to be set and to * report success, but for keygen the generated private state stays live in * key->priv_raw. We serialize and commit the full slot after MakeKey returns, @@ -6989,6 +7005,7 @@ static int _LmsDummyReadCb(byte* priv, word32 privSz, void* context) (void)priv; (void)privSz; (void)context; return WC_LMS_RC_READ_TO_MEMORY; } +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t inSize, @@ -7433,10 +7450,10 @@ static int _HandleLmsSigsLeftDma(whServerContext* ctx, uint16_t magic, #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS -/* Keygen write cb: wc_*_MakeKey calls this with the context we set up. - * Ignore the private key passed in, and fetch fields from the context. - * Write pub and priv keys together into the keystore. And capture any error - * codes into the context for later use by the caller of wc_*_MakeKey() */ +#ifndef WOLFSSL_XMSS_VERIFY_ONLY +/* Keygen write cb: wc_XmssKey_MakeKey hands us the private key, which it + * zeroizes immediately after. Copy it into the slot's priv region and capture + * any error into the context for the caller of MakeKey to surface. */ static enum wc_XmssRc _XmssKeygenWriteCb(const byte* priv, word32 privSz, void* context) { @@ -7464,6 +7481,7 @@ static enum wc_XmssRc _XmssDummyReadCb(byte* priv, word32 privSz, (void)priv; (void)privSz; (void)context; return WC_XMSS_RC_READ_TO_MEMORY; } +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ static int _HandleXmssKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 21f5d9c9f..c1a97ae51 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -169,8 +169,10 @@ int wh_Crypto_IsStatefulSigPrivBlob(const uint8_t* buffer, uint16_t size); * @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. */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY int wh_Crypto_LmsSerializeKey(LmsKey* key, uint16_t max_size, uint8_t* buffer, uint16_t* out_size); +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ /* Restore an LmsKey from a byte sequence. * @@ -189,6 +191,8 @@ int wh_Crypto_LmsSerializePubKey(LmsKey* key, uint16_t max_size, #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS +/* The private-key serializers are unavailable in verify-only builds. */ +#ifndef WOLFSSL_XMSS_VERIFY_ONLY /* Store an XmssKey (param string + public key + secret state) into a byte * sequence. */ int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, @@ -204,6 +208,7 @@ int wh_Crypto_XmssSerializeKey(XmssKey* key, const char* paramStr, int wh_Crypto_XmssSerializeKeyNoPriv(XmssKey* key, const char* paramStr, uint16_t privLen, uint16_t max_size, uint8_t* buffer, uint16_t* out_size); +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ /* Restore an XmssKey from a byte sequence */ int wh_Crypto_XmssDeserializeKey(const uint8_t* buffer, uint16_t size, diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h index 98d12bbc7..739172d37 100644 --- a/wolfhsm/wh_server_crypto.h +++ b/wolfhsm/wh_server_crypto.h @@ -124,10 +124,12 @@ int wh_Server_KeyCacheImportRaw(whServerContext* ctx, const uint8_t* keyData, #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. */ + * wh_Server_LmsKeyCacheExport. Unavailable in verify-only builds. */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY int wh_Server_LmsKeyCacheImport(whServerContext* ctx, LmsKey* key, whKeyId keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label); +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ /* 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. */ @@ -136,10 +138,13 @@ int wh_Server_LmsKeyCacheExport(whServerContext* ctx, whKeyId keyId, #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS +/* Persist an XmssKey into the server key cache. Unavailable in verify-only. */ +#ifndef WOLFSSL_XMSS_VERIFY_ONLY int wh_Server_XmssKeyCacheImport(whServerContext* ctx, XmssKey* key, const char* paramStr, whKeyId keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label); +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ int wh_Server_XmssKeyCacheExport(whServerContext* ctx, whKeyId keyId, XmssKey* key); #endif /* WOLFSSL_HAVE_XMSS */ From df6d498cd277b12c42183153fd10d5076d8786cf Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 19 Jun 2026 17:08:28 -0700 Subject: [PATCH 24/27] Add LMS/XMSS VERIFY_ONLY matrix entry in CI --- .github/workflows/build-and-test.yml | 8 + .../client-server/wh_test_crypto_lms.c | 422 ++++++++++++++++++ test/Makefile | 10 + 3 files changed, 440 insertions(+) create mode 100644 test-refactor/client-server/wh_test_crypto_lms.c diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9c8fa7eb8..1820f03e1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -45,6 +45,14 @@ jobs: - name: Build and test DMA ASAN run: cd test && make clean && make -j DMA=1 ASAN=1 WOLFSSL_DIR=../wolfssl && make run + # Build and test with LMS and XMSS both in verify-only mode + - name: Build and test DMA ASAN LMS/XMSS verify-only + run: cd test && make clean && make -j DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../wolfssl && make run + + # Build and test mixed: LMS verify-only, XMSS full (exercises shared gating) + - name: Build and test DMA ASAN LMS verify-only XMSS full + run: cd test && make clean && make -j DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 WOLFSSL_DIR=../wolfssl && make run + # Build and test ASAN build, with wolfCrypt tests enabled. - name: Build and test ASAN TESTWOLFCRYPT run: cd test && make clean && make -j ASAN=1 TESTWOLFCRYPT=1 WOLFSSL_DIR=../wolfssl && make run diff --git a/test-refactor/client-server/wh_test_crypto_lms.c b/test-refactor/client-server/wh_test_crypto_lms.c new file mode 100644 index 000000000..31d3cad60 --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_lms.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/client-server/wh_test_crypto_lms.c + * + * LMS (stateful hash-based) tests routed through the server over DMA: + * _whTest_CryptoLmsCryptoCb - generate / durability / sign / verify, public + * key export and import, and enforcement of the + * no-private-export / no-private-import policy + */ + +#include "wolfhsm/wh_settings.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/error-crypt.h" +#if defined(WOLFSSL_HAVE_LMS) +#include "wolfssl/wolfcrypt/wc_lms.h" +#endif + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#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 _whTest_CryptoLmsCryptoCb(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: 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) { + 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; + } + } + + /* 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; + 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 { + /* 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; + } + } + } + } + + /* 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; + 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; + } + } + + /* 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; + } + } + } + + /* 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. */ + 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; + } + } + } + + /* 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)); + 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) { + 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)); + 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, + &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) && + !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; +} + +int whTest_Crypto_Lms(whClientContext* ctx) +{ + int ret = 0; + int rngInited = 0; + WC_RNG rng[1]; + + ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(ctx)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + rngInited = 1; + + /* LMS dispatches through the DMA-only cryptocb. */ + (void)wh_Client_SetDmaMode(ctx, 1); + ret = _whTest_CryptoLmsCryptoCb(ctx, WH_DEV_ID_DMA, rng); + (void)wh_Client_SetDmaMode(ctx, 0); + + if (rngInited) { + (void)wc_FreeRng(rng); + } + + return ret; +} + +#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test/Makefile b/test/Makefile index ed2bbed88..c51920729 100644 --- a/test/Makefile +++ b/test/Makefile @@ -142,6 +142,16 @@ ifeq ($(DMA),1) DEF += -DWOLFHSM_CFG_DMA endif +# Build LMS/XMSS in verify-only mode (omits private-key, sign, and keygen +# paths). May be combined to exercise the mixed (one verify-only) case. +ifeq ($(LMS_VERIFY_ONLY),1) + DEF += -DWOLFSSL_LMS_VERIFY_ONLY +endif + +ifeq ($(XMSS_VERIFY_ONLY),1) + DEF += -DWOLFSSL_XMSS_VERIFY_ONLY +endif + # Support a SHE-capable build ifeq ($(SHE),1) DEF += -DWOLFHSM_CFG_SHE_EXTENSION From 8a041f8137eb3efde7a638651bff968af3d8433b Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 19 Jun 2026 18:06:40 -0700 Subject: [PATCH 25/27] Add LMS/XMSS test cases to test-refactor. Coverage is equivalent with test/ --- .github/workflows/build-and-test-refactor.yml | 8 + test-refactor/README.md | 2 + .../client-server/wh_test_crypto_xmss.c | 412 ++++++++++++++++++ test-refactor/posix/Makefile | 10 + test-refactor/wh_test_list.c | 4 + 5 files changed, 436 insertions(+) create mode 100644 test-refactor/client-server/wh_test_crypto_xmss.c diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index ede56b24c..4656b4a5a 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -45,6 +45,14 @@ jobs: - name: Build and test refactor DMA ASAN run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test with LMS and XMSS both in verify-only mode + - name: Build and test refactor DMA ASAN LMS/XMSS verify-only + run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + + # Build and test mixed: LMS verify-only, XMSS full (exercises shared gating) + - name: Build and test refactor DMA ASAN LMS verify-only XMSS full + run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test ASAN build, with wolfCrypt tests enabled. - name: Build and test refactor ASAN TESTWOLFCRYPT run: cd test-refactor/posix && make clean && make -j ASAN=1 TESTWOLFCRYPT=1 WOLFSSL_DIR=../../wolfssl && make run diff --git a/test-refactor/README.md b/test-refactor/README.md index 6de6046f9..e5582d97d 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -83,6 +83,8 @@ Translated tests: | `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto_{aes,cmac,curve25519,ecc,ed25519,kdf,keypolicy,mldsa,rng,rsa,sha}.c::whTest_Crypto_*` | Client | Split into per-algorithm suites; key revocation is gated by `WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS`. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_crypto.c::whTest_CryptoKeyUsagePolicies` (AES CTR/ECB/GCM subset) | `client-server/wh_test_crypto_aes.c::whTest_CryptoAesKeyUsagePolicies` | Client | AES-CTR/ECB/GCM key usage enforcement (non-DMA and DMA variants) | +| `wh_test_crypto.c::whTestCrypto_LmsCryptoCb` | `client-server/wh_test_crypto_lms.c::whTest_Crypto_Lms` | Client | DMA-only LMS generate/durability/sign/verify, public-key export+import, and private export/import rejection. Gated by `WOLFHSM_CFG_DMA && WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY`; reports SKIPPED otherwise | +| `wh_test_crypto.c::whTestCrypto_XmssCryptoCb` | `client-server/wh_test_crypto_xmss.c::whTest_Crypto_Xmss` | Client | DMA-only XMSS generate/durability/sign/verify, public-key export+import, and private export/import rejection. Gated by `WOLFHSM_CFG_DMA && WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY`; reports SKIPPED otherwise | | `wh_test_clientserver.c` (echo and server-info paths) | `client-server/wh_test_echo.c::whTest_Echo`, `client-server/wh_test_server_info.c::whTest_ServerInfo` | Client | pthread test ported, sequential test dropped | | `wh_test_wolfcrypt_test.c::whTest_WolfCryptTest` | `client-server/wh_test_wolfcrypt.c::whTest_WolfCryptTest` | Client | | | `wh_test_flash_ramsim.c::whTest_Flash_RamSim` | `posix/wh_test_flash_ramsim.c::{whTest_FlashWriteLock, whTest_FlashEraseProgramVerify, whTest_FlashUnitOps}` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group | diff --git a/test-refactor/client-server/wh_test_crypto_xmss.c b/test-refactor/client-server/wh_test_crypto_xmss.c new file mode 100644 index 000000000..b60322369 --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_xmss.c @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/client-server/wh_test_crypto_xmss.c + * + * XMSS (stateful hash-based) tests routed through the server over DMA: + * _whTest_CryptoXmssCryptoCb - generate / durability / sign / verify, public + * key export and import, and enforcement of the + * no-private-export / no-private-import policy + */ + +#include "wolfhsm/wh_settings.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/error-crypt.h" +#if defined(WOLFSSL_HAVE_XMSS) +#include "wolfssl/wolfcrypt/wc_xmss.h" +#endif + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#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 _whTest_CryptoXmssCryptoCb(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: 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) { + 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; + } + } + + /* 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; + 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 { + /* 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; + } + } + } + } + + /* 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); + 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; + } + } + + /* 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; + } + } + } + + /* 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. */ + 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; + } + } + } + + /* 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)); + 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) { + 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)); + 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, + &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) && + !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; +} + +int whTest_Crypto_Xmss(whClientContext* ctx) +{ + int ret = 0; + int rngInited = 0; + WC_RNG rng[1]; + + ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(ctx)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + rngInited = 1; + + /* XMSS dispatches through the DMA-only cryptocb. */ + (void)wh_Client_SetDmaMode(ctx, 1); + ret = _whTest_CryptoXmssCryptoCb(ctx, WH_DEV_ID_DMA, rng); + (void)wh_Client_SetDmaMode(ctx, 0); + + if (rngInited) { + (void)wc_FreeRng(rng); + } + + return ret; +} + +#endif /* WOLFHSM_CFG_DMA && WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index 66869da18..3169736ad 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -100,6 +100,16 @@ ifeq ($(DMA),1) DEF += -DWOLFHSM_CFG_DMA endif +# Build LMS/XMSS in verify-only mode (omits private-key, sign, and keygen +# paths). May be combined to exercise the mixed (one verify-only) case. +ifeq ($(LMS_VERIFY_ONLY),1) + DEF += -DWOLFSSL_LMS_VERIFY_ONLY +endif + +ifeq ($(XMSS_VERIFY_ONLY),1) + DEF += -DWOLFSSL_XMSS_VERIFY_ONLY +endif + # Support a SHE-capable build ifeq ($(SHE),1) DEF += -DWOLFHSM_CFG_SHE_EXTENSION diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index a7ff07128..641937019 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -51,10 +51,12 @@ WH_TEST_DECL(whTest_Crypto_Ecc); WH_TEST_DECL(whTest_Crypto_Ed25519); WH_TEST_DECL(whTest_Crypto_Kdf); WH_TEST_DECL(whTest_Crypto_KeyPolicy); +WH_TEST_DECL(whTest_Crypto_Lms); WH_TEST_DECL(whTest_Crypto_MlDsa); WH_TEST_DECL(whTest_Crypto_Rng); WH_TEST_DECL(whTest_Crypto_Rsa); WH_TEST_DECL(whTest_Crypto_Sha); +WH_TEST_DECL(whTest_Crypto_Xmss); WH_TEST_DECL(whTest_Echo); WH_TEST_DECL(whTest_ServerInfo); WH_TEST_DECL(whTest_WolfCryptTest); @@ -91,10 +93,12 @@ const whTestCase whTestsClient[] = { { "whTest_Crypto_Ed25519", whTest_Crypto_Ed25519 }, { "whTest_Crypto_Kdf", whTest_Crypto_Kdf }, { "whTest_Crypto_KeyPolicy", whTest_Crypto_KeyPolicy }, + { "whTest_Crypto_Lms", whTest_Crypto_Lms }, { "whTest_Crypto_MlDsa", whTest_Crypto_MlDsa }, { "whTest_Crypto_Rng", whTest_Crypto_Rng }, { "whTest_Crypto_Rsa", whTest_Crypto_Rsa }, { "whTest_Crypto_Sha", whTest_Crypto_Sha }, + { "whTest_Crypto_Xmss", whTest_Crypto_Xmss }, { "whTest_Echo", whTest_Echo }, { "whTest_ServerInfo", whTest_ServerInfo }, { "whTest_WolfCryptTest", whTest_WolfCryptTest }, From c0e3937579d5e9687ba07baaf8e81b486628f511 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 22 Jun 2026 10:24:41 -0700 Subject: [PATCH 26/27] Add testing for LMX/XMSS direct DMA keygen for coverage --- .../client-server/wh_test_crypto_lms.c | 38 ++++++++++ .../client-server/wh_test_crypto_xmss.c | 36 +++++++++ test/wh_test_crypto.c | 74 +++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/test-refactor/client-server/wh_test_crypto_lms.c b/test-refactor/client-server/wh_test_crypto_lms.c index 31d3cad60..a1bde8c07 100644 --- a/test-refactor/client-server/wh_test_crypto_lms.c +++ b/test-refactor/client-server/wh_test_crypto_lms.c @@ -159,6 +159,44 @@ static int _whTest_CryptoLmsCryptoCb(whClientContext* ctx, int devId, } } + /* Direct DMA keygen with a label and a caller-visible keyId. The cryptocb + * keygen above passes neither, so this drives the label-copy and keyId + * write-back paths on both client and server. */ + if (ret == 0) { + LmsKey lblKey[1]; + int lblInited = 0; + whKeyId lblId = WH_KEYID_ERASED; + byte label[] = "wolfHSM LMS key"; + + ret = wc_LmsKey_Init(lblKey, NULL, devId); + if (ret == 0) { + lblInited = 1; + ret = wc_LmsKey_SetParameters(lblKey, WH_TEST_LMS_LEVELS, + WH_TEST_LMS_HEIGHT, + WH_TEST_LMS_WINTERNITZ); + } + if (ret == 0) { + ret = wh_Client_LmsMakeKeyDma(ctx, lblKey, &lblId, + WH_NVM_FLAGS_NONE, + (uint16_t)(sizeof(label) - 1), label); + if (ret != 0) { + WH_ERROR_PRINT("LMS labeled keygen failed: ret=%d\n", ret); + } + } + if ((ret == 0) && WH_KEYID_ISERASED(lblId)) { + WH_ERROR_PRINT("LMS labeled keygen returned no keyId\n"); + ret = WH_ERROR_ABORTED; + } + /* Keygen is write-through, so erase (cache + NVM) to avoid leaking a + * committed key. */ + if (!WH_KEYID_ISERASED(lblId)) { + (void)wh_Client_KeyErase(ctx, lblId); + } + if (lblInited) { + wc_LmsKey_Free(lblKey); + } + } + /* Sign via cryptocb. */ if (ret == 0) { sigLen = sigCap; diff --git a/test-refactor/client-server/wh_test_crypto_xmss.c b/test-refactor/client-server/wh_test_crypto_xmss.c index b60322369..d6ae392e8 100644 --- a/test-refactor/client-server/wh_test_crypto_xmss.c +++ b/test-refactor/client-server/wh_test_crypto_xmss.c @@ -155,6 +155,42 @@ static int _whTest_CryptoXmssCryptoCb(whClientContext* ctx, int devId, } } + /* Direct DMA keygen with a label and a caller-visible keyId. The cryptocb + * keygen above passes neither, so this drives the label-copy and keyId + * write-back paths on both client and server. */ + if (ret == 0) { + XmssKey lblKey[1]; + int lblInited = 0; + whKeyId lblId = WH_KEYID_ERASED; + byte label[] = "wolfHSM XMSS key"; + + ret = wc_XmssKey_Init(lblKey, NULL, devId); + if (ret == 0) { + lblInited = 1; + ret = wc_XmssKey_SetParamStr(lblKey, WH_TEST_XMSS_PARAM_STR); + } + if (ret == 0) { + ret = wh_Client_XmssMakeKeyDma(ctx, lblKey, &lblId, + WH_NVM_FLAGS_NONE, + (uint16_t)(sizeof(label) - 1), label); + if (ret != 0) { + WH_ERROR_PRINT("XMSS labeled keygen failed: ret=%d\n", ret); + } + } + if ((ret == 0) && WH_KEYID_ISERASED(lblId)) { + WH_ERROR_PRINT("XMSS labeled keygen returned no keyId\n"); + ret = WH_ERROR_ABORTED; + } + /* Keygen is write-through, so erase (cache + NVM) to avoid leaking a + * committed key. */ + if (!WH_KEYID_ISERASED(lblId)) { + (void)wh_Client_KeyErase(ctx, lblId); + } + if (lblInited) { + wc_XmssKey_Free(lblKey); + } + } + if (ret == 0) { sigLen = sigCap; ret = wc_XmssKey_Sign(key, whTest_XmssSigBuf, &sigLen, msg, (int)msgSz); diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 7a8668869..2e5126c21 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -13328,6 +13328,44 @@ static int whTestCrypto_LmsCryptoCb(whClientContext* ctx, int devId, } } + /* Direct DMA keygen with a label and a caller-visible keyId. The cryptocb + * keygen above passes neither, so this drives the label-copy and keyId + * write-back paths on both client and server. */ + if (ret == 0) { + LmsKey lblKey[1]; + int lblInited = 0; + whKeyId lblId = WH_KEYID_ERASED; + byte label[] = "wolfHSM LMS key"; + + ret = wc_LmsKey_Init(lblKey, NULL, devId); + if (ret == 0) { + lblInited = 1; + ret = wc_LmsKey_SetParameters(lblKey, WH_TEST_LMS_LEVELS, + WH_TEST_LMS_HEIGHT, + WH_TEST_LMS_WINTERNITZ); + } + if (ret == 0) { + ret = wh_Client_LmsMakeKeyDma(ctx, lblKey, &lblId, + WH_NVM_FLAGS_NONE, + (uint16_t)(sizeof(label) - 1), label); + if (ret != 0) { + WH_ERROR_PRINT("LMS labeled keygen failed: ret=%d\n", ret); + } + } + if ((ret == 0) && WH_KEYID_ISERASED(lblId)) { + WH_ERROR_PRINT("LMS labeled keygen returned no keyId\n"); + ret = WH_ERROR_ABORTED; + } + /* Keygen is write-through, so erase (cache + NVM) to avoid leaking a + * committed key. */ + if (!WH_KEYID_ISERASED(lblId)) { + (void)wh_Client_KeyErase(ctx, lblId); + } + if (lblInited) { + wc_LmsKey_Free(lblKey); + } + } + /* Sign via cryptocb. */ if (ret == 0) { sigLen = sigCap; @@ -13668,6 +13706,42 @@ static int whTestCrypto_XmssCryptoCb(whClientContext* ctx, int devId, } } + /* Direct DMA keygen with a label and a caller-visible keyId. The cryptocb + * keygen above passes neither, so this drives the label-copy and keyId + * write-back paths on both client and server. */ + if (ret == 0) { + XmssKey lblKey[1]; + int lblInited = 0; + whKeyId lblId = WH_KEYID_ERASED; + byte label[] = "wolfHSM XMSS key"; + + ret = wc_XmssKey_Init(lblKey, NULL, devId); + if (ret == 0) { + lblInited = 1; + ret = wc_XmssKey_SetParamStr(lblKey, WH_TEST_XMSS_PARAM_STR); + } + if (ret == 0) { + ret = wh_Client_XmssMakeKeyDma(ctx, lblKey, &lblId, + WH_NVM_FLAGS_NONE, + (uint16_t)(sizeof(label) - 1), label); + if (ret != 0) { + WH_ERROR_PRINT("XMSS labeled keygen failed: ret=%d\n", ret); + } + } + if ((ret == 0) && WH_KEYID_ISERASED(lblId)) { + WH_ERROR_PRINT("XMSS labeled keygen returned no keyId\n"); + ret = WH_ERROR_ABORTED; + } + /* Keygen is write-through, so erase (cache + NVM) to avoid leaking a + * committed key. */ + if (!WH_KEYID_ISERASED(lblId)) { + (void)wh_Client_KeyErase(ctx, lblId); + } + if (lblInited) { + wc_XmssKey_Free(lblKey); + } + } + if (ret == 0) { sigLen = sigCap; ret = wc_XmssKey_Sign(key, whTest_XmssSigBuf, &sigLen, msg, (int)msgSz); From 4ed2c8a38b7192e8028872a6073a4b8913831ef7 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 22 Jun 2026 10:49:07 -0700 Subject: [PATCH 27/27] Rework LMS MakeKey writeCb to look like XMSS, per review --- src/wh_server_crypto.c | 57 +++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index b3f73cf01..6d038986e 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -6991,13 +6991,26 @@ typedef struct whServerStatefulSigKeygenCtx { #ifdef WOLFSSL_HAVE_LMS #ifndef WOLFSSL_LMS_VERIFY_ONLY -/* Keygen callbacks: wc_LmsKey_MakeKey requires a write cb to be set and to - * report success, but for keygen the generated private state stays live in - * key->priv_raw. We serialize and commit the full slot after MakeKey returns, - * so these callbacks are no-ops that only satisfy wolfCrypt's contract. */ -static int _LmsDummyWriteCb(const byte* priv, word32 privSz, void* context) +/* Capture the private key from wc_LmsKey_MakeKey to mirror the XMSS path. + * For LMS, additional private state resides in key->priv_raw, so that is + * serialized after the call to wc_LmsKey_MakeKey. */ +static int _LmsKeygenWriteCb(const byte* priv, word32 privSz, void* context) { - (void)priv; (void)privSz; (void)context; + whServerStatefulSigKeygenCtx* b = + (whServerStatefulSigKeygenCtx*)context; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_LMS_RC_BAD_ARG; + } + + /* Copy the private key wolfCrypt handed us into the slot's priv region. */ + if ((uint32_t)b->privOff + privSz > b->slotCapacity) { + b->status = WH_ERROR_BUFFER_SIZE; + return WC_LMS_RC_WRITE_FAIL; + } + memcpy(b->slotBuf + b->privOff, priv, privSz); + b->privLen = (uint16_t)privSz; + b->status = WH_ERROR_OK; return WC_LMS_RC_SAVED_TO_NV_MEMORY; } static int _LmsDummyReadCb(byte* priv, word32 privSz, void* context) @@ -7026,6 +7039,7 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, whNvmMetadata* cacheMeta; uint16_t slotCapacity = WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE; uint16_t blobSize; + whServerStatefulSigKeygenCtx sigCtx; whMessageCrypto_PqcStatefulSigKeyGenDmaRequest req; whMessageCrypto_PqcStatefulSigKeyGenDmaResponse res; @@ -7087,24 +7101,37 @@ static int _HandleLmsKeyGenDma(whServerContext* ctx, uint16_t magic, int devId, } } - /* MakeKey requires write/read callbacks, but for keygen the private state - * stays live in key->priv_raw; the callbacks are no-ops and we serialize - * the full slot ourselves once MakeKey returns. */ + /* Grab the cache slot up front; the keygen write cb captures priv into it. */ if (ret == 0) { - ret = wc_LmsKey_SetWriteCb(key, _LmsDummyWriteCb); + ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, + &cacheBuf, &cacheMeta); + } + + /* The write cb copies the private key into the slot's priv region: after + * the header, the 3-byte parameter descriptor, and the public key. */ + if (ret == 0) { + sigCtx.slotBuf = cacheBuf; + sigCtx.slotCapacity = slotCapacity; + sigCtx.privOff = (uint16_t)(WH_CRYPTO_STATEFUL_SIG_HEADER_SZ + 3 + + pubLen32); + sigCtx.status = WH_ERROR_OK; + ret = wc_LmsKey_SetWriteCb(key, _LmsKeygenWriteCb); } if (ret == 0) { ret = wc_LmsKey_SetReadCb(key, _LmsDummyReadCb); } if (ret == 0) { - ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); + ret = wc_LmsKey_SetContext(key, &sigCtx); } - - /* Build the slot blob in RAM from the generated key, then commit it. */ if (ret == 0) { - ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, - &cacheBuf, &cacheMeta); + ret = wc_LmsKey_MakeKey(key, ctx->crypto->rng); + /* MakeKey fails if the cb could not store priv; surface that error. */ + if ((ret != 0) && (sigCtx.status != WH_ERROR_OK)) { + ret = sigCtx.status; + } } + + /* Priv state survives in key->priv_raw, so serialize the full slot. */ if (ret == 0) { ret = wh_Crypto_LmsSerializeKey(key, slotCapacity, cacheBuf, &blobSize); }