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/.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/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 e5cefb5d6..0b2585729 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,890 @@ 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; + int postRet = 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; + } + + /* 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; + } + + 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); + } + + postRet = 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); + } + } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + + return ret; +} + +int wh_Client_LmsMakeExportKeyDma(whClientContext* ctx, LmsKey* key) +{ + return wh_Client_LmsMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_NONE, 0, NULL); +} + +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; + 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}); + postRet = 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; + } + } + } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + + 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) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* req; + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* res; + whKeyId key_id; + + if ((ctx == NULL) || (key == 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); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + 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) { + /* The server mirrors wc_LmsKey_SigsLeft(), which is a + * boolean. Normalize so the only nonzero return is 1. */ + ret = (res->sigsLeft != 0) ? 1 : 0; + } + } + } + + 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 + +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; + int postRet = 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; + } + + /* 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; + } + + 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); + } + + postRet = 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); + } + } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + + return ret; +} + +int wh_Client_XmssMakeExportKeyDma(whClientContext* ctx, XmssKey* key) +{ + return wh_Client_XmssMakeKeyDma(ctx, key, NULL, WH_NVM_FLAGS_NONE, 0, NULL); +} + +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; + 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}); + postRet = 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; + } + } + } + + /* Prioritize server errors over POST errors */ + if (ret == WH_ERROR_OK) { + ret = postRet; + } + } + + 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) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr; + whMessageCrypto_PqcStatefulSigSigsLeftDmaRequest* req; + whMessageCrypto_PqcStatefulSigSigsLeftDmaResponse* res; + whKeyId key_id; + + if ((ctx == NULL) || (key == 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); + + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + 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) { + /* The server mirrors wc_XmssKey_SigsLeft(), which is a + * boolean. Normalize so the only nonzero return is 1. */ + ret = (res->sigsLeft != 0) ? 1 : 0; + } + } + } + + 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 */ +#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..9da9f402e 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,285 @@ 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; + + /* 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; + } +#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; + + /* 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; + } +#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; + + /* 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; + } +#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 is the error code if negative, otherwise a boolean */ + ret = wh_Client_LmsSigsLeftDma( + 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 */ + { + 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 is the error code if negative, otherwise a boolean */ + ret = wh_Client_XmssSigsLeftDma( + 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 */ + { + 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 +1416,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..3c786c7ae 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,467 @@ 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: + * 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 + * 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' */ + +int wh_Crypto_IsStatefulSigPrivBlob(const uint8_t* buffer, uint16_t size) +{ + whCryptoStatefulSigHeader hdr; + + /* Need the full fixed header to inspect magic and privLen. */ + if ((buffer == NULL) || (size < sizeof(hdr))) { + return 0; + } + 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. */ + return (hdr.privLen > 0) ? 1 : 0; +} + +static int _StatefulSigEncodeHeader(uint8_t* buffer, uint32_t magic, + uint16_t pubLen, uint16_t privLen, + uint16_t paramLen) +{ + 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; +} + +static int _StatefulSigDecodeHeader(const uint8_t* buffer, uint16_t size, + uint32_t expectMagic, uint16_t* pubLen, + uint16_t* privLen, uint16_t* paramLen) +{ + whCryptoStatefulSigHeader hdr; + + if (size < sizeof(hdr)) { + return WH_ERROR_BADARGS; + } + 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; + } + 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 */ + +#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) +{ + 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; + } + if (pubLen32 > UINT16_MAX) { + 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; +} +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ + +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; + } + /* 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; +} + +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; + } + if (pubLen32 > UINT16_MAX) { + 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 */ + +#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 + * (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; + 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; + } + if (pubLen32 > UINT16_MAX) { + return WH_ERROR_BADARGS; + } + pubLen = (uint16_t)pubLen32; + + strLen = strlen(paramStr); + if (strLen >= UINT16_MAX) { + 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); + 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); +} +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ + +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; +#ifndef WOLFSSL_XMSS_VERIFY_ONLY + word32 expectPrivLen = 0; +#endif + 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; + } + /* 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); + /* The private key (if any) is left in the slot blob; downstream paths + * read it via the slot 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; + } + if (pubLen32 > UINT16_MAX) { + 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 */ + + #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..6d038986e 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,269 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, } #endif /* WOLFSSL_HAVE_MLKEM */ +/* 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. + * + * 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. + * + * 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 whServerStatefulSigCtx { + 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; /* fixed header size (offset to params) */ + uint16_t pubLen; /* priv begins at hdrSz + paramLen + pubLen */ + uint16_t paramLen; + uint16_t slotCapacity; +} whServerStatefulSigCtx; + +/* 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 _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) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) +static int _LmsSlotWriteCb(const byte* priv, word32 privSz, void* context) +{ + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)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 = _StatefulSigPrivOffset(b); + newLen = (uint32_t)privOff + privSz; + if (newLen > b->slotCapacity) { + return WC_LMS_RC_WRITE_FAIL; + } + + memcpy(b->slotBuf + privOff, priv, privSz); + _StatefulSigWritePrivLen(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 _LmsSlotReadCb(byte* priv, word32 privSz, void* context) +{ + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; + uint16_t privOff; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_LMS_RC_BAD_ARG; + } + + privOff = _StatefulSigPrivOffset(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 && !WOLFSSL_LMS_VERIFY_ONLY */ + +#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) +{ + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)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 = _StatefulSigPrivOffset(b); + newLen = (uint32_t)privOff + privSz; + if (newLen > b->slotCapacity) { + return WC_XMSS_RC_WRITE_FAIL; + } + + memcpy(b->slotBuf + privOff, priv, privSz); + _StatefulSigWritePrivLen(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 _XmssSlotReadCb(byte* priv, word32 privSz, + void* context) +{ + whServerStatefulSigCtx* b = (whServerStatefulSigCtx*)context; + uint16_t privOff; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_XMSS_RC_BAD_ARG; + } + + privOff = _StatefulSigPrivOffset(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 && !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) +{ + 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; + /* 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); + } + } + return ret; +} +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ + +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 +/* 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, + 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; + /* 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); + } + } + return ret; +} +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ + +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,116 +6942,1237 @@ 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 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, + uint8_t* slotBuf, whNvmMetadata* meta, + uint16_t slotCapacity) { - (void)seq; - - int ret = 0; - whMessageCrypto_CmacAesDmaRequest req; - whMessageCrypto_CmacAesDmaResponse res; + whCryptoStatefulSigHeader hdr; - if (inSize < sizeof(whMessageCrypto_CmacAesDmaRequest)) { + if ((b == NULL) || (server == NULL) || (slotBuf == NULL) || (meta == NULL)) { return WH_ERROR_BADARGS; } + memcpy(&hdr, slotBuf, sizeof(hdr)); - /* Translate request */ - ret = wh_MessageCrypto_TranslateCmacAesDmaRequest( - magic, (whMessageCrypto_CmacAesDmaRequest*)cryptoDataIn, &req); - if (ret != WH_ERROR_OK) { - return ret; - } + b->server = server; + b->keyId = keyId; + b->meta = meta; + b->slotBuf = slotBuf; + b->hdrSz = WH_CRYPTO_STATEFUL_SIG_HEADER_SZ; + b->paramLen = hdr.paramLen; + b->pubLen = hdr.pubLen; + 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 + * 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 { + 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 +#ifndef WOLFSSL_LMS_VERIFY_ONLY +/* 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) +{ + whServerStatefulSigKeygenCtx* b = + (whServerStatefulSigKeygenCtx*)context; - /* 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; + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_LMS_RC_BAD_ARG; } - word32 len; + /* 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) +{ + (void)priv; (void)privSz; (void)context; + return WC_LMS_RC_READ_TO_MEMORY; +} +#endif /* !WOLFSSL_LMS_VERIFY_ONLY */ - /* 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); +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; + 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; memset(&res, 0, sizeof(res)); - /* DMA translated address for input */ - void* inAddr = NULL; + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } - uint8_t tmpKey[AES_256_KEY_SIZE]; - uint32_t tmpKeyLen = sizeof(tmpKey); - Cmac cmac[1]; + ret = wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigKeyGenDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + return ret; + } - /* 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; + /* Reject EPHEMERAL keys since keygen itself is stateful */ + if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + return WH_ERROR_BADARGS; + } - /* Translate DMA address for input */ + ret = wc_LmsKey_Init(key, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wc_LmsKey_SetParameters(key, (int)req.lmsLevels, (int)req.lmsHeight, + (int)req.lmsWinternitz); + + /* Validate the buffer size and resolve the keyId before keygen. */ + 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, 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; + ctx, (uintptr_t)req.pub.addr, &clientPubAddr, pubLen32, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret != 0) { + res.dmaAddrStatus.badAddr = req.pub; } - - /* Resolve key */ - if (ret == WH_ERROR_OK) { - ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, - &tmpKeyLen); + } + /* 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); + } + 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 == WH_ERROR_OK && req.keySz != 0) { - /* Client-supplied key - direct one-shot */ - WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot\n"); + /* Grab the cache slot up front; the keygen write cb captures priv into it. */ + if (ret == 0) { + ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, slotCapacity, + &cacheBuf, &cacheMeta); + } - ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, req.input.sz, - tmpKey, (word32)tmpKeyLen, NULL, devId); + /* 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_SetContext(key, &sigCtx); + } + if (ret == 0) { + 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; } - 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); - } + /* Priv state survives in key->priv_raw, so serialize the full slot. */ + 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 (ret == 0) { - res.outSz = len; - res.keyId = WH_KEYID_ERASED; - } + if (locked) { + (void)WH_SERVER_NVM_UNLOCK(ctx); + locked = 0; } - else { - /* Streaming update/final with optional client-side assembled first - * block (inline) plus DMA whole blocks. Final carries partial tail - * inline only. */ - WH_DEBUG_SERVER_VERBOSE( - "dma cmac begin keySz:%d inlineInSz:%d dmaInSz:%d outSz:%d " - "keyId:%x\n", + + /* 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); + 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_LmsKey_Free(key); + + (void)wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaResponse( + magic, &res, + (whMessageCrypto_PqcStatefulSigKeyGenDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + return ret; +#endif /* WOLFSSL_LMS_VERIFY_ONLY */ +} + +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; + whServerStatefulSigCtx sigCtx; + 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; + + /* 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 = _StatefulSigFromSlot( + &sigCtx, ctx, keyId, cacheBuf, cacheMeta, + WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE); + } + if (ret == WH_ERROR_OK) { + (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) { + 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: + * 1. wc_hss_sign computes the signature into sig and advances + * key->priv_raw in memory. + * 2. write_private_key (our slot write cb) is called with the new + * priv_raw and atomically commits it to NVM. + * 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 + * 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; + /* 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 + * 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; +} + +/* 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, + 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; + /* 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); + } + + 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 +#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) +{ + whServerStatefulSigKeygenCtx* b = + (whServerStatefulSigKeygenCtx*)context; + + if ((b == NULL) || (priv == NULL) || (b->slotBuf == NULL)) { + return WC_XMSS_RC_BAD_ARG; + } + + /* 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; + } + 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) +{ + (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, + 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; + 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; + + memset(&res, 0, sizeof(res)); + memset(&sigCtx, 0, sizeof(sigCtx)); + + if (inSize < sizeof(req)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslatePqcStatefulSigKeyGenDmaRequest( + magic, (whMessageCrypto_PqcStatefulSigKeyGenDmaRequest*)cryptoDataIn, + &req); + if (ret != WH_ERROR_OK) { + 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. */ + 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); + + /* Validate the buffer size and resolve the keyId before keygen. */ + 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; + } + } + /* 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); + } + 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); + } + } + /* 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); + } + + /* 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) { + 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); + } + if (ret == 0) { + ret = wc_XmssKey_SetContext(key, &sigCtx); + } + if (ret == 0) { + ret = wc_XmssKey_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 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; + } + + /* 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); + 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); + 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; + whServerStatefulSigCtx sigCtx; + 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 = _StatefulSigFromSlot( + &sigCtx, ctx, keyId, cacheBuf, cacheMeta, + WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE); + } + if (ret == WH_ERROR_OK) { + (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) { + 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; + /* 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 + * 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; + whServerStatefulSigCtx sigCtx; + 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; + } + + /* 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); + 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 = _StatefulSigFromSlot( + &sigCtx, ctx, keyId, cacheBuf, cacheMeta, + WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE); + } + if (ret == WH_ERROR_OK) { + /* 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, _XmssSlotWriteCb); + (void)wc_XmssKey_SetReadCb(key, _XmssSlotReadCb); + (void)wc_XmssKey_SetContext(key, &sigCtx); + 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_SERVER_NVM_UNLOCK(ctx); + + (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); + } + } + + if (ret == 0) { + res.outSz = len; + res.keyId = WH_KEYID_ERASED; + } + } + else { + /* Streaming update/final with optional client-side assembled first + * block (inline) plus DMA whole blocks. Final carries partial tail + * inline only. */ + WH_DEBUG_SERVER_VERBOSE( + "dma cmac begin keySz:%d inlineInSz:%d dmaInSz:%d outSz:%d " + "keyId:%x\n", (int)req.keySz, (int)req.inlineInSz, (int)req.input.sz, (int)req.outSz, req.keyId); @@ -7078,6 +8468,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/src/wh_server_keystore.c b/src/wh_server_keystore.c index e7529ab3d..f9d7f79af 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; @@ -712,6 +778,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_IsStatefulSigPrivBlob(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 +1824,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_IsStatefulSigPrivBlob(key, (uint16_t)metadata.len)) { + return WH_ERROR_ACCESS; + } +#endif + /* Cache the key */ return wh_Server_KeystoreCacheKey(server, &metadata, key); } @@ -2180,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; @@ -2383,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; @@ -2815,6 +2920,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_IsStatefulSigPrivBlob(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..9481db048 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_IsStatefulSigPrivBlob(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_IsStatefulSigPrivBlob((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-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_lms.c b/test-refactor/client-server/wh_test_crypto_lms.c new file mode 100644 index 000000000..a1bde8c07 --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_lms.c @@ -0,0 +1,460 @@ +/* + * 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; + } + } + + /* 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; + 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-refactor/client-server/wh_test_crypto_xmss.c b/test-refactor/client-server/wh_test_crypto_xmss.c new file mode 100644 index 000000000..d6ae392e8 --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_xmss.c @@ -0,0 +1,448 @@ +/* + * 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; + } + } + + /* 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); + 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 }, 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 diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 478edfe2b..83d03e364 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -140,6 +140,12 @@ /* ML-KEM Options */ #define WOLFSSL_HAVE_MLKEM +/* LMS / HSS Options (RFC 8554, NIST SP 800-208) */ +#define WOLFSSL_HAVE_LMS + +/* XMSS / XMSS^MT Options (RFC 8391, NIST SP 800-208) */ +#define WOLFSSL_HAVE_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..2e5126c21 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -32,6 +32,12 @@ #include "wolfssl/wolfcrypt/types.h" #include "wolfssl/wolfcrypt/kdf.h" #include "wolfssl/wolfcrypt/ed25519.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" @@ -13212,6 +13218,758 @@ static int whTestCrypto_MlKemDmaClient(whClientContext* ctx, int devId, !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: 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; + } + } + + /* 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; + 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; +} +#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: 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; + } + } + + /* 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); + 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; +} +#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) { @@ -14969,6 +15727,20 @@ int whTest_CryptoClientConfig(whClientConfig* config) !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) { (void)whTest_ShowNvmAvailable(client); 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..ed1843033 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3208,5 +3208,280 @@ 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 + +/* 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 + +/** + * @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); + +/** + * @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 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; 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. + */ +int wh_Client_LmsMakeKeyDma(whClientContext* ctx, LmsKey* key, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Convenience wrapper for keygen that returns the public key via DMA. + * + * 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. + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_LmsMakeExportKeyDma(whClientContext* ctx, LmsKey* key); + +/** + * @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); + +/** + * @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); + +/** + * @brief Report whether an HSM-resident LMS key can still produce signatures. + * + * 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); + +/** + * @brief 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. + * + * @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); + +#endif /* WOLFSSL_HAVE_LMS */ + +#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); + +/** + * @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 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; 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. + */ +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 keygen that returns the public key via DMA. + * + * 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. + * @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 Report whether an HSM-resident XMSS key can still produce signatures. + * + * 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); + +/** + * @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); + +#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_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_ */ diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index 88094b9c8..c1a97ae51 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -129,6 +129,99 @@ 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) +/* 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 + * 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 +/* 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. + * + * @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. */ +#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. + * + * @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); + +/* 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 +/* 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, + 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); +#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */ + +/* 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 */ #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..739172d37 100644 --- a/wolfhsm/wh_server_crypto.h +++ b/wolfhsm/wh_server_crypto.h @@ -121,6 +121,33 @@ 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. 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. */ +int wh_Server_LmsKeyCacheExport(whServerContext* ctx, whKeyId keyId, + LmsKey* key); +#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 */ #ifdef HAVE_HKDF /* Store HKDF output into a server key cache with optional metadata */ 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 */