From 5d96f6a3e91d1d84ad955a805c410566e8ea6720 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 26 May 2026 10:52:06 -0600 Subject: [PATCH] add SHA3 support --- src/wh_client_crypto.c | 724 ++++++++++++++++++++++++++++ src/wh_client_cryptocb.c | 92 ++++ src/wh_message_crypto.c | 96 ++++ src/wh_server_crypto.c | 275 +++++++++++ test/config/user_settings.h | 3 + test/wh_test_check_struct_padding.c | 9 + test/wh_test_crypto.c | 690 ++++++++++++++++++++++++++ wolfhsm/wh_client_crypto.h | 128 +++++ wolfhsm/wh_message_crypto.h | 184 +++++++ 9 files changed, 2201 insertions(+) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 712c534ab..0508d8706 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -7973,6 +7973,730 @@ int wh_Client_Sha512Dma(whClientContext* ctx, wc_Sha512* sha, const uint8_t* in, #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) +/* SHA3 - all four variants (224/256/384/512) share the wc_Sha3 struct and + * the same wire format. Per-variant differences (block size, digest size, + * Update/Final functions) are passed via a small dispatch table. */ + +typedef struct { + int hashType; /* WC_HASH_TYPE_SHA3_* (also algoType on the wire) */ + uint32_t blockSize; + uint32_t digestSize; + uint32_t maxInlineSz; + /* Only initFn is used client-side (context reset after Final). Update/Final + * run on the server, so no update/final pointers are carried here. */ + int (*initFn)(wc_Sha3* sha, void* heap, int devId); +} whSha3Variant; + +#ifndef WOLFSSL_NOSHA3_224 +static const whSha3Variant whSha3_224 = { + WC_HASH_TYPE_SHA3_224, WC_SHA3_224_BLOCK_SIZE, WC_SHA3_224_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_224_MAX_INLINE_UPDATE_SZ, wc_InitSha3_224}; +#endif +#ifndef WOLFSSL_NOSHA3_256 +static const whSha3Variant whSha3_256 = { + WC_HASH_TYPE_SHA3_256, WC_SHA3_256_BLOCK_SIZE, WC_SHA3_256_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_256_MAX_INLINE_UPDATE_SZ, wc_InitSha3_256}; +#endif +#ifndef WOLFSSL_NOSHA3_384 +static const whSha3Variant whSha3_384 = { + WC_HASH_TYPE_SHA3_384, WC_SHA3_384_BLOCK_SIZE, WC_SHA3_384_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_384_MAX_INLINE_UPDATE_SZ, wc_InitSha3_384}; +#endif +#ifndef WOLFSSL_NOSHA3_512 +static const whSha3Variant whSha3_512 = { + WC_HASH_TYPE_SHA3_512, WC_SHA3_512_BLOCK_SIZE, WC_SHA3_512_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_512_MAX_INLINE_UPDATE_SZ, wc_InitSha3_512}; +#endif + +/* Maximum input absorbable by a single UpdateRequest: inline wire capacity + * plus room left in the local partial-block buffer. */ +static uint32_t _Sha3UpdatePerCallCapacity(const wc_Sha3* sha, + const whSha3Variant* v) +{ + return v->maxInlineSz + (uint32_t)(v->blockSize - 1u - sha->i); +} + +/* Reject Keccak-mode contexts. The wire format carries only s[] and the + * server re-inits the context, applying standard SHA-3 0x06 padding; a + * Keccak-flagged context would silently produce a wrong digest. The cryptocb + * path falls back to software for this case; the direct API has no fallback + * so it must refuse the call. */ +static int _Sha3RejectKeccak(const wc_Sha3* sha) +{ +#ifdef WOLFSSL_HASH_FLAGS + if (sha != NULL && (sha->flags & WC_HASH_SHA3_KECCAK256) != 0u) { + return WH_ERROR_BADARGS; + } +#else + (void)sha; +#endif + return WH_ERROR_OK; +} + +static int _Sha3UpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v, const uint8_t* in, + uint32_t inLen, bool* requestSent) +{ + int ret = 0; + whMessageCrypto_Sha3Request* req = NULL; + uint8_t* inlineData; + uint8_t* dataPtr = NULL; + uint32_t capacity; + uint32_t wirePos = 0; + uint32_t i = 0; + /* Snapshot of partial buffer for rollback if SendRequest fails */ + uint32_t savedI; + uint8_t savedT[WC_SHA3_224_BLOCK_SIZE]; /* largest block size: 144 */ + int k; + + if (ctx == NULL || sha == NULL || requestSent == NULL || + (in == NULL && inLen != 0)) { + return WH_ERROR_BADARGS; + } + ret = _Sha3RejectKeccak(sha); + if (ret != WH_ERROR_OK) { + return ret; + } + *requestSent = false; + + if (sha->i >= v->blockSize) { + return WH_ERROR_BADARGS; + } + + capacity = _Sha3UpdatePerCallCapacity(sha, v); + if (inLen > capacity) { + return WH_ERROR_BADARGS; + } + if (inLen == 0) { + return WH_ERROR_OK; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_Sha3Request*)_createCryptoRequest( + dataPtr, v->hashType, ctx->cryptoAffinity); + inlineData = (uint8_t*)(req + 1); + + savedI = sha->i; + memcpy(savedT, sha->t, sha->i); + + /* Top up the local partial buffer. If it completes a full block, copy + * the assembled block as the first inline block. */ + if (sha->i > 0) { + while (i < inLen && sha->i < v->blockSize) { + sha->t[sha->i++] = in[i++]; + } + if (sha->i == v->blockSize) { + memcpy(inlineData + wirePos, sha->t, v->blockSize); + wirePos += v->blockSize; + sha->i = 0; + } + } + + /* Pack as many whole blocks from input as fit inline. */ + while ((inLen - i) >= v->blockSize && + (wirePos + v->blockSize) <= v->maxInlineSz) { + memcpy(inlineData + wirePos, in + i, v->blockSize); + wirePos += v->blockSize; + i += v->blockSize; + } + + /* Stash remaining tail bytes locally. */ + while (i < inLen) { + sha->t[sha->i++] = in[i++]; + } + + /* Pure buffer-fill update: nothing to send. */ + if (wirePos == 0) { + return WH_ERROR_OK; + } + + /* Zero resumeState so the WH_PAD bytes aren't sent with comm-buffer + * residue (the s[] array is fully written below). */ + memset(&req->resumeState, 0, sizeof(req->resumeState)); + req->isLastBlock = 0; + req->inSz = wirePos; + for (k = 0; k < 25; k++) { + req->resumeState.s[k] = sha->s[k]; + } + + ret = wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_HASH, + sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + wirePos, + dataPtr); + if (ret == 0) { + *requestSent = true; + } + else { + /* Restore partial buffer on failure so caller can retry. */ + sha->i = savedI; + memcpy(sha->t, savedT, savedI); + } + return ret; +} + +static int _Sha3UpdateResponse(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v) +{ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WH_MESSAGE_ACTION_NONE; + uint16_t dataSz = 0; + int ret = 0; + whMessageCrypto_Sha3Response* res = NULL; + uint8_t* dataPtr; + int k; + + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, dataPtr); + if (ret != WH_ERROR_OK) { + return ret; + } + + ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)&res); + if (ret >= 0) { + for (k = 0; k < 25; k++) { + sha->s[k] = res->resumeState.s[k]; + } + } + return ret; +} + +static int _Sha3FinalRequest(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v) +{ + int ret; + whMessageCrypto_Sha3Request* req; + uint8_t* inlineData; + uint8_t* dataPtr; + int k; + + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + ret = _Sha3RejectKeccak(sha); + if (ret != WH_ERROR_OK) { + return ret; + } + if (sha->i >= v->blockSize) { + return WH_ERROR_BADARGS; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_Sha3Request*)_createCryptoRequest( + dataPtr, v->hashType, ctx->cryptoAffinity); + inlineData = (uint8_t*)(req + 1); + + /* Zero resumeState so the WH_PAD bytes aren't sent with comm-buffer + * residue (the s[] array is fully written below). */ + memset(&req->resumeState, 0, sizeof(req->resumeState)); + req->isLastBlock = 1; + req->inSz = sha->i; + for (k = 0; k < 25; k++) { + req->resumeState.s[k] = sha->s[k]; + } + if (sha->i > 0) { + memcpy(inlineData, sha->t, sha->i); + } + + ret = wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_HASH, + sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + sha->i, + dataPtr); + return ret; +} + +static int _Sha3FinalResponse(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v, uint8_t* out) +{ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WH_MESSAGE_ACTION_NONE; + uint16_t dataSz = 0; + int ret; + whMessageCrypto_Sha3Response* res = NULL; + uint8_t* dataPtr; + void* savedHeap; + int savedDevId; + + if (ctx == NULL || sha == NULL || out == NULL) { + return WH_ERROR_BADARGS; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, dataPtr); + if (ret != 0) { + return ret; + } + + ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)&res); + if (ret >= 0) { + memcpy(out, res->hash, v->digestSize); + /* Reset state, preserving heap and devId. */ + savedHeap = sha->heap; + savedDevId = sha->devId; + (void)v->initFn(sha, savedHeap, savedDevId); + } + return ret; +} + +static int _Sha3Oneshot(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v, const uint8_t* in, + uint32_t inLen, uint8_t* out) +{ + int ret = WH_ERROR_OK; + + /* _Sha3UpdatePerCallCapacity (below) reads sha->i, so validate sha here + * rather than relying on the lower-level helper's NULL check. */ + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + + /* Mirror _Sha3UpdateRequest's invariant: skipping the update branch on + * (in == NULL && inLen != 0) would silently digest the current state. */ + if (in == NULL && inLen != 0) { + return WH_ERROR_BADARGS; + } + + if (in != NULL && inLen > 0) { + uint32_t consumed = 0; + while (ret == WH_ERROR_OK && consumed < inLen) { + uint32_t capacity = _Sha3UpdatePerCallCapacity(sha, v); + uint32_t remaining = inLen - consumed; + uint32_t chunk = (remaining < capacity) ? remaining : capacity; + bool sent = false; + + ret = _Sha3UpdateRequest(ctx, sha, v, in + consumed, chunk, &sent); + if (ret != WH_ERROR_OK) { + break; + } + if (sent) { + do { + ret = _Sha3UpdateResponse(ctx, sha, v); + } while (ret == WH_ERROR_NOTREADY); + if (ret != WH_ERROR_OK) { + break; + } + } + consumed += chunk; + } + } + + if (ret == WH_ERROR_OK && out != NULL) { + ret = _Sha3FinalRequest(ctx, sha, v); + if (ret == WH_ERROR_OK) { + do { + ret = _Sha3FinalResponse(ctx, sha, v, out); + } while (ret == WH_ERROR_NOTREADY); + } + } + return ret; +} + +/* Per-variant public APIs - thin wrappers over the shared helpers. */ + +#define WH_SHA3_VARIANT_API(NN) \ + int wh_Client_Sha3_##NN(whClientContext* ctx, wc_Sha3* sha, \ + const uint8_t* in, uint32_t inLen, uint8_t* out) \ + { \ + return _Sha3Oneshot(ctx, sha, &whSha3_##NN, in, inLen, out); \ + } \ + int wh_Client_Sha3_##NN##UpdateRequest(whClientContext* ctx, wc_Sha3* sha, \ + const uint8_t* in, uint32_t inLen, \ + bool* requestSent) \ + { \ + return _Sha3UpdateRequest(ctx, sha, &whSha3_##NN, in, inLen, \ + requestSent); \ + } \ + int wh_Client_Sha3_##NN##UpdateResponse(whClientContext* ctx, \ + wc_Sha3* sha) \ + { \ + return _Sha3UpdateResponse(ctx, sha, &whSha3_##NN); \ + } \ + int wh_Client_Sha3_##NN##FinalRequest(whClientContext* ctx, wc_Sha3* sha) \ + { \ + return _Sha3FinalRequest(ctx, sha, &whSha3_##NN); \ + } \ + int wh_Client_Sha3_##NN##FinalResponse(whClientContext* ctx, wc_Sha3* sha, \ + uint8_t* out) \ + { \ + return _Sha3FinalResponse(ctx, sha, &whSha3_##NN, out); \ + } + +#ifndef WOLFSSL_NOSHA3_224 +WH_SHA3_VARIANT_API(224) +#endif +#ifndef WOLFSSL_NOSHA3_256 +WH_SHA3_VARIANT_API(256) +#endif +#ifndef WOLFSSL_NOSHA3_384 +WH_SHA3_VARIANT_API(384) +#endif +#ifndef WOLFSSL_NOSHA3_512 +WH_SHA3_VARIANT_API(512) +#endif + +#undef WH_SHA3_VARIANT_API + +#ifdef WOLFHSM_CFG_DMA +/* SHA3 DMA helpers - inline first block (assembled from partial buffer) plus + * whole-block DMA input. Final goes inline-only. */ + +static int _Sha3DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v, const uint8_t* in, + uint32_t inLen, bool* requestSent) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_Sha3DmaRequest* req = NULL; + uint8_t* inlineData; + uint32_t wirePos = 0; + uint32_t i = 0; + uintptr_t inAddr = 0; + bool inAddrAcquired = false; + const uint8_t* dmaBase = NULL; + uint32_t dmaSz = 0; + uint32_t savedI; + uint8_t savedT[WC_SHA3_224_BLOCK_SIZE]; + int k; + + if (ctx == NULL || sha == NULL || requestSent == NULL || + (in == NULL && inLen != 0)) { + return WH_ERROR_BADARGS; + } + ret = _Sha3RejectKeccak(sha); + if (ret != WH_ERROR_OK) { + return ret; + } + if (wh_CommClient_IsRequestPending(ctx->comm) == 1) { + return WH_ERROR_REQUEST_PENDING; + } + *requestSent = false; + + if (sha->i >= v->blockSize) { + return WH_ERROR_BADARGS; + } + if (inLen == 0) { + return WH_ERROR_OK; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_Sha3DmaRequest*)_createCryptoRequest( + dataPtr, v->hashType, ctx->cryptoAffinity); + inlineData = (uint8_t*)(req + 1); + + savedI = sha->i; + memcpy(savedT, sha->t, sha->i); + + if (sha->i > 0) { + while (i < inLen && sha->i < v->blockSize) { + sha->t[sha->i++] = in[i++]; + } + if (sha->i == v->blockSize) { + memcpy(inlineData, sha->t, v->blockSize); + wirePos = v->blockSize; + sha->i = 0; + } + } + + if ((inLen - i) >= v->blockSize) { + dmaBase = in + i; + dmaSz = ((inLen - i) / v->blockSize) * v->blockSize; + i += dmaSz; + } + + while (i < inLen) { + sha->t[sha->i++] = in[i++]; + } + + if (wirePos == 0 && dmaSz == 0) { + return WH_ERROR_OK; + } + + /* Zero resumeState so the WH_PAD bytes aren't sent with comm-buffer + * residue (the s[] array is fully written below). */ + memset(&req->resumeState, 0, sizeof(req->resumeState)); + req->isLastBlock = 0; + req->inSz = wirePos; + for (k = 0; k < 25; k++) { + req->resumeState.s[k] = sha->s[k]; + } + req->input.sz = dmaSz; + req->input.addr = 0; + + if (dmaSz > 0) { + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)dmaBase, (void**)&inAddr, dmaSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + inAddrAcquired = true; + req->input.addr = inAddr; + } + } + + if (ret == WH_ERROR_OK) { + ctx->dma.asyncCtx.sha.ioAddr = inAddr; + ctx->dma.asyncCtx.sha.clientAddr = (uintptr_t)dmaBase; + ctx->dma.asyncCtx.sha.ioSz = dmaSz; + + ret = wh_Client_SendRequest( + ctx, WH_MESSAGE_GROUP_CRYPTO_DMA, WC_ALGO_TYPE_HASH, + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req) + + wirePos, + dataPtr); + } + + if (ret == WH_ERROR_OK) { + *requestSent = true; + } + else { + sha->i = savedI; + memcpy(sha->t, savedT, savedI); + if (inAddrAcquired) { + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)dmaBase, (void**)&inAddr, dmaSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + } + memset(&ctx->dma.asyncCtx.sha, 0, sizeof(ctx->dma.asyncCtx.sha)); + } + return ret; +} + +static int _Sha3DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_Sha3DmaResponse* resp = NULL; + uint16_t respSz = 0; + int k; + + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, dataPtr); + if (ret == WH_ERROR_NOTREADY) { + return ret; + } + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)&resp); + if (ret >= 0) { + for (k = 0; k < 25; k++) { + sha->s[k] = resp->resumeState.s[k]; + } + } + } + + if (ctx->dma.asyncCtx.sha.ioSz > 0) { + uintptr_t ioAddr = ctx->dma.asyncCtx.sha.ioAddr; + (void)wh_Client_DmaProcessClientAddress( + ctx, ctx->dma.asyncCtx.sha.clientAddr, (void**)&ioAddr, + ctx->dma.asyncCtx.sha.ioSz, WH_DMA_OPER_CLIENT_READ_POST, + (whDmaFlags){0}); + ctx->dma.asyncCtx.sha.ioSz = 0; + } + return ret; +} + +static int _Sha3DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_Sha3DmaRequest* req = NULL; + uint8_t* inlineData; + int k; + + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + ret = _Sha3RejectKeccak(sha); + if (ret != WH_ERROR_OK) { + return ret; + } + if (wh_CommClient_IsRequestPending(ctx->comm) == 1) { + return WH_ERROR_REQUEST_PENDING; + } + if (sha->i >= v->blockSize) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_Sha3DmaRequest*)_createCryptoRequest( + dataPtr, v->hashType, ctx->cryptoAffinity); + inlineData = (uint8_t*)(req + 1); + + /* Zero resumeState so the WH_PAD bytes aren't sent with comm-buffer + * residue (the s[] array is fully written below). */ + memset(&req->resumeState, 0, sizeof(req->resumeState)); + req->isLastBlock = 1; + req->inSz = sha->i; + for (k = 0; k < 25; k++) { + req->resumeState.s[k] = sha->s[k]; + } + req->input.sz = 0; + req->input.addr = 0; + + if (sha->i > 0) { + memcpy(inlineData, sha->t, sha->i); + } + + ret = wh_Client_SendRequest( + ctx, WH_MESSAGE_GROUP_CRYPTO_DMA, WC_ALGO_TYPE_HASH, + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req) + sha->i, + dataPtr); + return ret; +} + +static int _Sha3DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v, uint8_t* out) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_Sha3DmaResponse* resp = NULL; + uint16_t respSz = 0; + void* savedHeap; + int savedDevId; + + if (ctx == NULL || sha == NULL || out == NULL) { + return WH_ERROR_BADARGS; + } + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(ctx, NULL, NULL, &respSz, dataPtr); + if (ret == WH_ERROR_NOTREADY) { + return ret; + } + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)&resp); + if (ret >= 0) { + memcpy(out, resp->hash, v->digestSize); + savedHeap = sha->heap; + savedDevId = sha->devId; + (void)v->initFn(sha, savedHeap, savedDevId); + } + } + return ret; +} + +static int _Sha3DmaOneshot(whClientContext* ctx, wc_Sha3* sha, + const whSha3Variant* v, const uint8_t* in, + uint32_t inLen, uint8_t* out) +{ + int ret = WH_ERROR_OK; + + /* Mirror _Sha3DmaUpdateRequest's invariant: skipping the update branch on + * (in == NULL && inLen != 0) would silently digest the current state. */ + if (in == NULL && inLen != 0) { + return WH_ERROR_BADARGS; + } + + if (in != NULL && inLen > 0) { + bool sent = false; + ret = _Sha3DmaUpdateRequest(ctx, sha, v, in, inLen, &sent); + if (ret == WH_ERROR_OK && sent) { + do { + ret = _Sha3DmaUpdateResponse(ctx, sha, v); + } while (ret == WH_ERROR_NOTREADY); + } + } + if (ret == WH_ERROR_OK && out != NULL) { + ret = _Sha3DmaFinalRequest(ctx, sha, v); + if (ret == WH_ERROR_OK) { + do { + ret = _Sha3DmaFinalResponse(ctx, sha, v, out); + } while (ret == WH_ERROR_NOTREADY); + } + } + return ret; +} + +#define WH_SHA3_VARIANT_DMA_API(NN) \ + int wh_Client_Sha3_##NN##Dma(whClientContext* ctx, wc_Sha3* sha, \ + const uint8_t* in, uint32_t inLen, \ + uint8_t* out) \ + { \ + return _Sha3DmaOneshot(ctx, sha, &whSha3_##NN, in, inLen, out); \ + } \ + int wh_Client_Sha3_##NN##DmaUpdateRequest( \ + whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, uint32_t inLen, \ + bool* requestSent) \ + { \ + return _Sha3DmaUpdateRequest(ctx, sha, &whSha3_##NN, in, inLen, \ + requestSent); \ + } \ + int wh_Client_Sha3_##NN##DmaUpdateResponse(whClientContext* ctx, \ + wc_Sha3* sha) \ + { \ + return _Sha3DmaUpdateResponse(ctx, sha, &whSha3_##NN); \ + } \ + int wh_Client_Sha3_##NN##DmaFinalRequest(whClientContext* ctx, \ + wc_Sha3* sha) \ + { \ + return _Sha3DmaFinalRequest(ctx, sha, &whSha3_##NN); \ + } \ + int wh_Client_Sha3_##NN##DmaFinalResponse(whClientContext* ctx, \ + wc_Sha3* sha, uint8_t* out) \ + { \ + return _Sha3DmaFinalResponse(ctx, sha, &whSha3_##NN, out); \ + } + +#ifndef WOLFSSL_NOSHA3_224 +WH_SHA3_VARIANT_DMA_API(224) +#endif +#ifndef WOLFSSL_NOSHA3_256 +WH_SHA3_VARIANT_DMA_API(256) +#endif +#ifndef WOLFSSL_NOSHA3_384 +WH_SHA3_VARIANT_DMA_API(384) +#endif +#ifndef WOLFSSL_NOSHA3_512 +WH_SHA3_VARIANT_DMA_API(512) +#endif + +#undef WH_SHA3_VARIANT_DMA_API +#endif /* WOLFHSM_CFG_DMA */ + +#endif /* WOLFSSL_SHA3 */ + #ifdef WOLFSSL_HAVE_MLDSA int wh_Client_MlDsaSetKeyId(wc_MlDsaKey* key, whKeyId keyId) diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index cd3da69a7..614aa09c6 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -534,6 +534,53 @@ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx) ret = wh_Client_Sha512(ctx, sha, in, inLen, out); } break; #endif /* WOLFSSL_SHA512 && WOLFSSL_SHA512_HASHTYPE */ +#if defined(WOLFSSL_SHA3) + case WC_HASH_TYPE_SHA3_224: + case WC_HASH_TYPE_SHA3_256: + case WC_HASH_TYPE_SHA3_384: + case WC_HASH_TYPE_SHA3_512: { + wc_Sha3* sha = info->hash.sha3; +#ifdef WOLFSSL_HASH_FLAGS + /* Keccak-mode SHA3 (legacy 0x01-padding variant) is a software- + * only mode; fall through to wolfCrypt's software path. */ + if (sha != NULL && + (sha->flags & WC_HASH_SHA3_KECCAK256) != 0u) { + ret = CRYPTOCB_UNAVAILABLE; + break; + } +#endif + switch (info->hash.type) { +#ifndef WOLFSSL_NOSHA3_224 + case WC_HASH_TYPE_SHA3_224: + ret = wh_Client_Sha3_224(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif +#ifndef WOLFSSL_NOSHA3_256 + case WC_HASH_TYPE_SHA3_256: + ret = wh_Client_Sha3_256(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif +#ifndef WOLFSSL_NOSHA3_384 + case WC_HASH_TYPE_SHA3_384: + ret = wh_Client_Sha3_384(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif +#ifndef WOLFSSL_NOSHA3_512 + case WC_HASH_TYPE_SHA3_512: + ret = wh_Client_Sha3_512(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif + } + } break; +#endif /* WOLFSSL_SHA3 */ default: ret = CRYPTOCB_UNAVAILABLE; break; @@ -862,6 +909,51 @@ int wh_Client_CryptoCbDma(int devId, wc_CryptoInfo* info, void* inCtx) ret = wh_Client_Sha512Dma(ctx, sha, in, inLen, out); } break; #endif /* WOLFSSL_SHA512 && defined(WOLFSSL_SHA512_HASHTYPE) */ +#if defined(WOLFSSL_SHA3) + case WC_HASH_TYPE_SHA3_224: + case WC_HASH_TYPE_SHA3_256: + case WC_HASH_TYPE_SHA3_384: + case WC_HASH_TYPE_SHA3_512: { + wc_Sha3* sha = info->hash.sha3; +#ifdef WOLFSSL_HASH_FLAGS + if (sha != NULL && + (sha->flags & WC_HASH_SHA3_KECCAK256) != 0u) { + ret = CRYPTOCB_UNAVAILABLE; + break; + } +#endif + switch (info->hash.type) { +#ifndef WOLFSSL_NOSHA3_224 + case WC_HASH_TYPE_SHA3_224: + ret = wh_Client_Sha3_224Dma(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif +#ifndef WOLFSSL_NOSHA3_256 + case WC_HASH_TYPE_SHA3_256: + ret = wh_Client_Sha3_256Dma(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif +#ifndef WOLFSSL_NOSHA3_384 + case WC_HASH_TYPE_SHA3_384: + ret = wh_Client_Sha3_384Dma(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif +#ifndef WOLFSSL_NOSHA3_512 + case WC_HASH_TYPE_SHA3_512: + ret = wh_Client_Sha3_512Dma(ctx, sha, info->hash.in, + info->hash.inSz, + info->hash.digest); + break; +#endif + } + } break; +#endif /* WOLFSSL_SHA3 */ default: ret = CRYPTOCB_UNAVAILABLE; break; diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index 0ba44b342..ed69efd8d 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -692,6 +692,60 @@ int wh_MessageCrypto_TranslateSha2Response( return 0; } +/* SHA3 state translation - shared across all SHA3 variants and across the + * non-DMA and DMA wire formats. */ +int wh_MessageCrypto_TranslateSha3State(uint16_t magic, + const whMessageCrypto_Sha3State* src, + whMessageCrypto_Sha3State* dest) +{ + int k; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, i); + for (k = 0; k < 25; k++) { + WH_T64(magic, dest, src, s[k]); + } + return 0; +} + +/* SHA3 Request translation. Trailing input bytes are raw and need no + * translation. */ +int wh_MessageCrypto_TranslateSha3Request( + uint16_t magic, const whMessageCrypto_Sha3Request* src, + whMessageCrypto_Sha3Request* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, isLastBlock); + WH_T32(magic, dest, src, inSz); + return wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState, + &dest->resumeState); +} + +/* SHA3 Response translation */ +int wh_MessageCrypto_TranslateSha3Response( + uint16_t magic, const whMessageCrypto_Sha3Response* src, + whMessageCrypto_Sha3Response* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState, + &dest->resumeState); + if (ret != 0) { + return ret; + } + if (src != dest) { + memcpy(dest->hash, src->hash, sizeof(src->hash)); + } + return 0; +} + /* CMAC-AES State translation */ int wh_MessageCrypto_TranslateCmacAesState( @@ -940,6 +994,48 @@ int wh_MessageCrypto_TranslateSha2DmaResponse( &dest->dmaAddrStatus); } +/* SHA3 DMA Request translation */ +int wh_MessageCrypto_TranslateSha3DmaRequest( + uint16_t magic, const whMessageCrypto_Sha3DmaRequest* src, + whMessageCrypto_Sha3DmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->input, &dest->input); + if (ret != 0) { + return ret; + } + WH_T32(magic, dest, src, isLastBlock); + WH_T32(magic, dest, src, inSz); + return wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState, + &dest->resumeState); +} + +/* SHA3 DMA Response translation */ +int wh_MessageCrypto_TranslateSha3DmaResponse( + uint16_t magic, const whMessageCrypto_Sha3DmaResponse* src, + whMessageCrypto_Sha3DmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + ret = wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState, + &dest->resumeState); + if (ret != 0) { + return ret; + } + if (src != dest) { + memcpy(dest->hash, src->hash, sizeof(src->hash)); + } + return wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); +} + /* CMAC-AES DMA Request translation */ int wh_MessageCrypto_TranslateCmacAesDmaRequest( uint16_t magic, const whMessageCrypto_CmacAesDmaRequest* src, diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index ce2c94cb2..9906e8bcc 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -42,6 +42,9 @@ #include "wolfssl/wolfcrypt/aes.h" #include "wolfssl/wolfcrypt/sha256.h" #include "wolfssl/wolfcrypt/sha512.h" +#ifdef WOLFSSL_SHA3 +#include "wolfssl/wolfcrypt/sha3.h" +#endif #include "wolfssl/wolfcrypt/cmac.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/hmac.h" @@ -4066,6 +4069,146 @@ static int _HandleSha512(whServerContext* ctx, uint16_t magic, int devId, return ret; } #endif /* WOLFSSL_SHA512 */ + +#if defined(WOLFSSL_SHA3) +/* SHA3 - one handler dispatches all four variants on hashType. */ + +typedef struct { + uint32_t blockSize; + uint32_t digestSize; + int (*initFn)(wc_Sha3* sha, void* heap, int devId); + int (*updateFn)(wc_Sha3* sha, const byte* data, word32 len); + int (*finalFn)(wc_Sha3* sha, byte* hash); +} _Sha3VariantOps; + +static int _Sha3LookupOps(int hashType, _Sha3VariantOps* ops) +{ + switch (hashType) { +#ifndef WOLFSSL_NOSHA3_224 + case WC_HASH_TYPE_SHA3_224: + ops->blockSize = WC_SHA3_224_BLOCK_SIZE; + ops->digestSize = WC_SHA3_224_DIGEST_SIZE; + ops->initFn = wc_InitSha3_224; + ops->updateFn = wc_Sha3_224_Update; + ops->finalFn = wc_Sha3_224_Final; + return 0; +#endif +#ifndef WOLFSSL_NOSHA3_256 + case WC_HASH_TYPE_SHA3_256: + ops->blockSize = WC_SHA3_256_BLOCK_SIZE; + ops->digestSize = WC_SHA3_256_DIGEST_SIZE; + ops->initFn = wc_InitSha3_256; + ops->updateFn = wc_Sha3_256_Update; + ops->finalFn = wc_Sha3_256_Final; + return 0; +#endif +#ifndef WOLFSSL_NOSHA3_384 + case WC_HASH_TYPE_SHA3_384: + ops->blockSize = WC_SHA3_384_BLOCK_SIZE; + ops->digestSize = WC_SHA3_384_DIGEST_SIZE; + ops->initFn = wc_InitSha3_384; + ops->updateFn = wc_Sha3_384_Update; + ops->finalFn = wc_Sha3_384_Final; + return 0; +#endif +#ifndef WOLFSSL_NOSHA3_512 + case WC_HASH_TYPE_SHA3_512: + ops->blockSize = WC_SHA3_512_BLOCK_SIZE; + ops->digestSize = WC_SHA3_512_DIGEST_SIZE; + ops->initFn = wc_InitSha3_512; + ops->updateFn = wc_Sha3_512_Update; + ops->finalFn = wc_Sha3_512_Final; + return 0; +#endif + default: + return WH_ERROR_BADARGS; + } +} + +static int _HandleSha3(whServerContext* ctx, int hashType, uint16_t magic, + int devId, const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ + int ret = 0; + wc_Sha3 sha3[1]; + whMessageCrypto_Sha3Request req; + whMessageCrypto_Sha3Response res = {0}; + const uint8_t* inData; + _Sha3VariantOps ops; + int k; + + (void)ctx; + + ret = _Sha3LookupOps(hashType, &ops); + if (ret != 0) { + return ret; + } + + if (inSize < sizeof(whMessageCrypto_Sha3Request)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateSha3Request(magic, cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + if ((uint32_t)req.inSz > + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha3Request))) { + return WH_ERROR_BADARGS; + } + if (!req.isLastBlock && (req.inSz % ops.blockSize) != 0) { + return WH_ERROR_BADARGS; + } + if (req.isLastBlock && req.inSz >= ops.blockSize) { + return WH_ERROR_BADARGS; + } + + inData = (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha3Request); + + ret = ops.initFn(sha3, NULL, devId); + if (ret != 0) { + return ret; + } + + /* Restore Keccak state from client. initFn already zeroed t[] and i. */ + for (k = 0; k < 25; k++) { + sha3->s[k] = req.resumeState.s[k]; + } + + if (req.inSz > 0) { + ret = ops.updateFn(sha3, inData, req.inSz); + } + if (ret == 0) { + if (req.isLastBlock) { + ret = ops.finalFn(sha3, res.hash); + } + else { + /* Post-condition: whole-block input must leave i == 0. */ + if (sha3->i != 0) { + ret = WH_ERROR_ABORTED; + } + else { + for (k = 0; k < 25; k++) { + res.resumeState.s[k] = sha3->s[k]; + } + res.resumeState.i = 0; + } + } + } + + if (ret == 0) { + ret = + wh_MessageCrypto_TranslateSha3Response(magic, &res, cryptoDataOut); + if (ret == 0) { + *outSize = sizeof(res); + } + } + + return ret; +} +#endif /* WOLFSSL_SHA3 */ + #ifdef WOLFSSL_HAVE_MLDSA #ifndef WOLFSSL_MLDSA_NO_MAKE_KEY @@ -4788,6 +4931,21 @@ int wh_Server_HandleCryptoRequest(whServerContext* ctx, uint16_t magic, } break; #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) + case WC_HASH_TYPE_SHA3_224: + case WC_HASH_TYPE_SHA3_256: + case WC_HASH_TYPE_SHA3_384: + case WC_HASH_TYPE_SHA3_512: + WH_DEBUG_SERVER("SHA3 req recv. type:%u\n", + rqstHeader.algoType); + ret = _HandleSha3(ctx, rqstHeader.algoType, magic, devId, + cryptoDataIn, cryptoInSize, cryptoDataOut, + &cryptoOutSize); + if (ret != 0) { + WH_DEBUG_SERVER("SHA3 ret = %d\n", ret); + } + break; +#endif /* WOLFSSL_SHA3 */ default: ret = NOT_COMPILED_IN; break; @@ -5279,6 +5437,110 @@ static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId, } #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) +static int _HandleSha3Dma(whServerContext* ctx, int hashType, uint16_t magic, + int devId, uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)seq; + int ret = 0; + int preOk = 0; + whMessageCrypto_Sha3DmaRequest req; + whMessageCrypto_Sha3DmaResponse res = {0}; + wc_Sha3 sha3[1]; + const uint8_t* inlineData; + void* inAddr = NULL; + _Sha3VariantOps ops; + int k; + + ret = _Sha3LookupOps(hashType, &ops); + if (ret != 0) { + return ret; + } + + if (inSize < sizeof(whMessageCrypto_Sha3DmaRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateSha3DmaRequest( + magic, (const whMessageCrypto_Sha3DmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + if ((uint32_t)req.inSz > + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha3DmaRequest))) { + return WH_ERROR_BADARGS; + } + if (!req.isLastBlock && ((req.inSz % ops.blockSize) != 0 || + (req.input.sz % ops.blockSize) != 0)) { + return WH_ERROR_BADARGS; + } + if (req.isLastBlock && (req.inSz >= ops.blockSize || req.input.sz != 0)) { + return WH_ERROR_BADARGS; + } + + inlineData = + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha3DmaRequest); + + ret = ops.initFn(sha3, NULL, devId); + if (ret != 0) { + return ret; + } + + /* Restore Keccak state from client. initFn already zeroed t[] and i. */ + for (k = 0; k < 25; k++) { + sha3->s[k] = req.resumeState.s[k]; + } + + if (ret == 0 && req.inSz > 0) { + ret = ops.updateFn(sha3, inlineData, req.inSz); + } + + if (ret == 0 && req.input.sz > 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_OK) { + preOk = 1; + ret = ops.updateFn(sha3, inAddr, req.input.sz); + } + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.input; + } + } + if (preOk) { + (void)wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (ret == 0) { + if (req.isLastBlock) { + ret = ops.finalFn(sha3, res.hash); + } + else { + if (sha3->i != 0) { + ret = WH_ERROR_ABORTED; + } + else { + for (k = 0; k < 25; k++) { + res.resumeState.s[k] = sha3->s[k]; + } + res.resumeState.i = 0; + } + } + } + + (void)wh_MessageCrypto_TranslateSha3DmaResponse( + magic, &res, (whMessageCrypto_Sha3DmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + + return ret; +} +#endif /* WOLFSSL_SHA3 */ + #if defined(WOLFSSL_HAVE_MLDSA) static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, @@ -6074,6 +6336,19 @@ int wh_Server_HandleCryptoDmaRequest(whServerContext* ctx, uint16_t magic, } break; #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) + case WC_HASH_TYPE_SHA3_224: + case WC_HASH_TYPE_SHA3_256: + case WC_HASH_TYPE_SHA3_384: + case WC_HASH_TYPE_SHA3_512: + ret = _HandleSha3Dma(ctx, rqstHeader.algoType, magic, devId, + seq, cryptoDataIn, cryptoInSize, + cryptoDataOut, &cryptoOutSize); + if (ret != 0) { + WH_DEBUG_SERVER("DMA SHA3 ret = %d\n", ret); + } + break; +#endif /* WOLFSSL_SHA3 */ default: ret = NOT_COMPILED_IN; break; diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 40800de51..8211af4eb 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -137,6 +137,9 @@ #define WOLFSSL_SHA3 #define WOLFSSL_SHAKE128 #define WOLFSSL_SHAKE256 +/* Enables wc_Sha3_SetFlags so the SHA3 Keccak-mode reject/fallback paths are + * compiled and exercised by the test suite. */ +#define WOLFSSL_HASH_FLAGS /* Ed25519 Options */ #define HAVE_ED25519 diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index cbb22e30b..15af5d1b4 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -122,6 +122,11 @@ whMessageCrypto_CmacAesResponse cmacRes; whMessageCrypto_Sha256Request hashSha256Req; whMessageCrypto_Sha512Request hashSha512Req; whMessageCrypto_Sha2Response hashSha2Res; +#if defined(WOLFSSL_SHA3) +whMessageCrypto_Sha3State hashSha3State; +whMessageCrypto_Sha3Request hashSha3Req; +whMessageCrypto_Sha3Response hashSha3Res; +#endif whMessageCrypto_HkdfRequest hkdfReq; whMessageCrypto_HkdfResponse hkdfRes; @@ -130,6 +135,10 @@ whMessageCrypto_HkdfResponse hkdfRes; whMessageCrypto_Sha256DmaRequest hashSha256DmaReq; whMessageCrypto_Sha512DmaRequest hashSha512DmaReq; whMessageCrypto_Sha2DmaResponse hashSha2DmaRes; +#if defined(WOLFSSL_SHA3) +whMessageCrypto_Sha3DmaRequest hashSha3DmaReq; +whMessageCrypto_Sha3DmaResponse hashSha3DmaRes; +#endif whMessageCrypto_MlDsaKeyGenDmaRequest pqMldsaKeygenDmaReq; whMessageCrypto_MlDsaKeyGenDmaResponse pqMldsaKeygenDmaRes; whMessageCrypto_MlDsaSignDmaRequest pqMldsaSignDmaReq; diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 2f850bfbc..5900aaf6f 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -5281,6 +5281,678 @@ static int whTest_CryptoSha512DmaAsync(whClientContext* ctx, int devId, #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) +/* SHA3 native crypto tests. + * + * A single set of tests runs against all four variants (224/256/384/512) + * via a small dispatch table. Tests check both the wolfCrypt API path + * (devId-dispatched -> our cryptocb) and the native async API. + */ + +typedef struct { + const char* name; + int hashType; + uint32_t blockSize; + uint32_t digestSize; + uint32_t maxInlineSz; + int (*initFn)(wc_Sha3* sha, void* heap, int devId); + int (*updateFn)(wc_Sha3* sha, const byte* data, word32 len); + int (*finalFn)(wc_Sha3* sha, byte* hash); + /* native async client helpers */ + int (*asyncUpdateRequest)(whClientContext*, wc_Sha3*, const uint8_t*, + uint32_t, bool*); + int (*asyncUpdateResponse)(whClientContext*, wc_Sha3*); + int (*asyncFinalRequest)(whClientContext*, wc_Sha3*); + int (*asyncFinalResponse)(whClientContext*, wc_Sha3*, uint8_t*); + /* native one-shot helpers */ + int (*oneshotFn)(whClientContext*, wc_Sha3*, const uint8_t*, uint32_t, + uint8_t*); +#ifdef WOLFHSM_CFG_DMA + int (*dmaUpdateRequest)(whClientContext*, wc_Sha3*, const uint8_t*, + uint32_t, bool*); + int (*dmaUpdateResponse)(whClientContext*, wc_Sha3*); + int (*dmaFinalRequest)(whClientContext*, wc_Sha3*); + int (*dmaFinalResponse)(whClientContext*, wc_Sha3*, uint8_t*); + int (*dmaOneshotFn)(whClientContext*, wc_Sha3*, const uint8_t*, uint32_t, + uint8_t*); +#endif +} whTestSha3Variant; + +static const whTestSha3Variant whTestSha3Variants[] = { +#ifndef WOLFSSL_NOSHA3_224 + { + "SHA3-224", + WC_HASH_TYPE_SHA3_224, + WC_SHA3_224_BLOCK_SIZE, + WC_SHA3_224_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_224_MAX_INLINE_UPDATE_SZ, + wc_InitSha3_224, + wc_Sha3_224_Update, + wc_Sha3_224_Final, + wh_Client_Sha3_224UpdateRequest, + wh_Client_Sha3_224UpdateResponse, + wh_Client_Sha3_224FinalRequest, + wh_Client_Sha3_224FinalResponse, + wh_Client_Sha3_224, +#ifdef WOLFHSM_CFG_DMA + wh_Client_Sha3_224DmaUpdateRequest, + wh_Client_Sha3_224DmaUpdateResponse, + wh_Client_Sha3_224DmaFinalRequest, + wh_Client_Sha3_224DmaFinalResponse, + wh_Client_Sha3_224Dma, +#endif + }, +#endif +#ifndef WOLFSSL_NOSHA3_256 + { + "SHA3-256", + WC_HASH_TYPE_SHA3_256, + WC_SHA3_256_BLOCK_SIZE, + WC_SHA3_256_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_256_MAX_INLINE_UPDATE_SZ, + wc_InitSha3_256, + wc_Sha3_256_Update, + wc_Sha3_256_Final, + wh_Client_Sha3_256UpdateRequest, + wh_Client_Sha3_256UpdateResponse, + wh_Client_Sha3_256FinalRequest, + wh_Client_Sha3_256FinalResponse, + wh_Client_Sha3_256, +#ifdef WOLFHSM_CFG_DMA + wh_Client_Sha3_256DmaUpdateRequest, + wh_Client_Sha3_256DmaUpdateResponse, + wh_Client_Sha3_256DmaFinalRequest, + wh_Client_Sha3_256DmaFinalResponse, + wh_Client_Sha3_256Dma, +#endif + }, +#endif +#ifndef WOLFSSL_NOSHA3_384 + { + "SHA3-384", + WC_HASH_TYPE_SHA3_384, + WC_SHA3_384_BLOCK_SIZE, + WC_SHA3_384_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_384_MAX_INLINE_UPDATE_SZ, + wc_InitSha3_384, + wc_Sha3_384_Update, + wc_Sha3_384_Final, + wh_Client_Sha3_384UpdateRequest, + wh_Client_Sha3_384UpdateResponse, + wh_Client_Sha3_384FinalRequest, + wh_Client_Sha3_384FinalResponse, + wh_Client_Sha3_384, +#ifdef WOLFHSM_CFG_DMA + wh_Client_Sha3_384DmaUpdateRequest, + wh_Client_Sha3_384DmaUpdateResponse, + wh_Client_Sha3_384DmaFinalRequest, + wh_Client_Sha3_384DmaFinalResponse, + wh_Client_Sha3_384Dma, +#endif + }, +#endif +#ifndef WOLFSSL_NOSHA3_512 + { + "SHA3-512", + WC_HASH_TYPE_SHA3_512, + WC_SHA3_512_BLOCK_SIZE, + WC_SHA3_512_DIGEST_SIZE, + WH_MESSAGE_CRYPTO_SHA3_512_MAX_INLINE_UPDATE_SZ, + wc_InitSha3_512, + wc_Sha3_512_Update, + wc_Sha3_512_Final, + wh_Client_Sha3_512UpdateRequest, + wh_Client_Sha3_512UpdateResponse, + wh_Client_Sha3_512FinalRequest, + wh_Client_Sha3_512FinalResponse, + wh_Client_Sha3_512, +#ifdef WOLFHSM_CFG_DMA + wh_Client_Sha3_512DmaUpdateRequest, + wh_Client_Sha3_512DmaUpdateResponse, + wh_Client_Sha3_512DmaFinalRequest, + wh_Client_Sha3_512DmaFinalResponse, + wh_Client_Sha3_512Dma, +#endif + }, +#endif +}; + +/* Reference hash via software (INVALID_DEVID) for cross-checks. */ +static int whTest_Sha3Reference(const whTestSha3Variant* v, const uint8_t* in, + uint32_t inLen, uint8_t* out) +{ + wc_Sha3 sw[1]; + int ret = v->initFn(sw, NULL, INVALID_DEVID); + if (ret == 0 && inLen > 0) { + ret = v->updateFn(sw, in, inLen); + } + if (ret == 0) { + ret = v->finalFn(sw, out); + } + /* No Free variant function in our table — wc_Sha3 doesn't allocate by + * default on POSIX so this is fine. */ + return ret; +} + +/* Static buffer sized to cover all variants' max inline + tail slack. + * SHA3-512 has the smallest block (72) so its floor((P)/block)*block lands + * closest to the payload cap, making it the upper bound (possibly tied + * with SHA3-224) across variants. 2x multiplier + 256 tail adds margin. */ +#define WH_TEST_SHA3_BIGBUF_SZ \ + (2u * (WH_MESSAGE_CRYPTO_SHA3_512_MAX_INLINE_UPDATE_SZ + 256u)) +static uint8_t whTest_Sha3BigBuf[WH_TEST_SHA3_BIGBUF_SZ]; + +static void whTest_Sha3FillBuf(uint8_t* buf, uint32_t sz) +{ + uint32_t i; + for (i = 0; i < sz; i++) { + buf[i] = (uint8_t)((i * 31u + 7u) & 0xffu); + } +} + +/* Per-variant basic + large-input test using the wolfCrypt API. */ +static int whTest_CryptoSha3OneVariant(whClientContext* ctx, int devId, + const whTestSha3Variant* v) +{ + int ret = WH_ERROR_OK; + wc_Sha3 sha[1]; + uint8_t out[WC_SHA3_512_DIGEST_SIZE]; + uint8_t ref[WC_SHA3_512_DIGEST_SIZE]; + uint32_t cases[] = {0u, + 1u, + v->blockSize - 1u, + v->blockSize, + v->blockSize + 1u, + 3u * v->blockSize, + v->maxInlineSz, + v->maxInlineSz + 1u, + WH_TEST_SHA3_BIGBUF_SZ - 1u}; + size_t n; + (void)ctx; + + whTest_Sha3FillBuf(whTest_Sha3BigBuf, WH_TEST_SHA3_BIGBUF_SZ); + + for (n = 0; n < sizeof(cases) / sizeof(cases[0]) && ret == 0; n++) { + uint32_t len = cases[n]; + if (len > WH_TEST_SHA3_BIGBUF_SZ) + continue; + + ret = whTest_Sha3Reference(v, whTest_Sha3BigBuf, len, ref); + if (ret != 0) { + WH_ERROR_PRINT("%s reference failed: %d\n", v->name, ret); + break; + } + + ret = v->initFn(sha, NULL, devId); + if (ret == 0 && len > 0) { + ret = v->updateFn(sha, whTest_Sha3BigBuf, len); + } + if (ret == 0) { + ret = v->finalFn(sha, out); + } + if (ret == 0 && memcmp(out, ref, v->digestSize) != 0) { + WH_ERROR_PRINT("%s mismatch at len=%u devId=0x%X\n", v->name, + (unsigned)len, devId); + ret = -1; + } + } + + if (ret == 0) { + WH_TEST_PRINT("%s DEVID=0x%X SUCCESS\n", v->name, devId); + } + return ret; +} + +/* One-shot wrapper bad-arg coverage: (in == NULL, inLen != 0) must be + * rejected before any state mutation. Regression for HIGH-1: the oneshot + * wrappers previously skipped the update branch on this input and silently + * returned a digest of the (empty) state. */ +static int whTest_CryptoSha3OneshotBadArgs(whClientContext* ctx, + const whTestSha3Variant* v) +{ + int ret; + wc_Sha3 sha[1]; + uint8_t out[WC_SHA3_512_DIGEST_SIZE]; + uint8_t in[1] = {0}; + + ret = v->initFn(sha, NULL, INVALID_DEVID); + if (ret != 0) + return ret; + + ret = v->oneshotFn(ctx, sha, NULL, 1u, out); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s oneshot(NULL,1): expected BADARGS, got %d\n", + v->name, ret); + return -1; + } + + /* sha == NULL with valid input must be rejected, not dereferenced: the + * oneshot reads sha->i to size each chunk before the lower-level helper's + * NULL check runs. */ + ret = v->oneshotFn(ctx, NULL, in, sizeof(in), out); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s oneshot(sha=NULL): expected BADARGS, got %d\n", + v->name, ret); + return -1; + } + +#ifdef WOLFHSM_CFG_DMA + ret = v->initFn(sha, NULL, INVALID_DEVID); + if (ret != 0) + return ret; + ret = v->dmaOneshotFn(ctx, sha, NULL, 1u, out); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s dma oneshot(NULL,1): expected BADARGS, got %d\n", + v->name, ret); + return -1; + } + ret = v->dmaOneshotFn(ctx, NULL, in, sizeof(in), out); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s dma oneshot(sha=NULL): expected BADARGS, got %d\n", + v->name, ret); + return -1; + } +#endif + + WH_TEST_PRINT("%s ONESHOT BADARGS SUCCESS\n", v->name); + return 0; +} + +#if defined(WOLFSSL_HASH_FLAGS) && !defined(WOLFSSL_NOSHA3_256) +/* Keccak-mode (legacy 0x01-padding) SHA3-256 is a software-only mode. Lock in + * the deliberate behavioral split for a KECCAK256-flagged context: + * - wolfCrypt API path (HSM devId): the cryptocb returns CRYPTOCB_UNAVAILABLE + * so wolfCrypt falls back to software and still produces the Keccak digest + * (NOT a standard SHA3-256 digest, which is what a silent offload of the + * 0x06-padding wire format would yield). + * - direct client API (and DMA variant): must be rejected with + * WH_ERROR_BADARGS, since the wire format would re-pad as standard SHA3. */ +static int whTest_CryptoSha3Keccak(whClientContext* ctx, int devId) +{ + int ret; + wc_Sha3 sha[1]; + uint8_t out[WC_SHA3_256_DIGEST_SIZE]; + uint8_t keccakRef[WC_SHA3_256_DIGEST_SIZE]; + uint8_t sha3Ref[WC_SHA3_256_DIGEST_SIZE]; + const char in[] = "wolfHSM SHA3 Keccak-mode fallback vector"; + uint32_t inLen = (uint32_t)(sizeof(in) - 1u); + + /* Software Keccak reference (0x01 padding). */ + ret = wc_InitSha3_256(sha, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha3_SetFlags(sha, WC_HASH_SHA3_KECCAK256); + } + if (ret == 0) { + ret = wc_Sha3_256_Update(sha, (const byte*)in, inLen); + } + if (ret == 0) { + ret = wc_Sha3_256_Final(sha, keccakRef); + } + if (ret != 0) { + WH_ERROR_PRINT("Keccak reference failed: %d\n", ret); + return ret; + } + + /* Software standard SHA3-256 reference (0x06 padding). Must differ from the + * Keccak digest, else the comparisons below would prove nothing. */ + ret = wc_InitSha3_256(sha, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha3_256_Update(sha, (const byte*)in, inLen); + } + if (ret == 0) { + ret = wc_Sha3_256_Final(sha, sha3Ref); + } + if (ret != 0) { + WH_ERROR_PRINT("SHA3-256 reference failed: %d\n", ret); + return ret; + } + if (memcmp(keccakRef, sha3Ref, WC_SHA3_256_DIGEST_SIZE) == 0) { + WH_ERROR_PRINT("Keccak and SHA3-256 digests unexpectedly equal\n"); + return -1; + } + + /* cryptocb contract: HSM devId + KECCAK256 flag falls back to software and + * matches the Keccak (not the standard SHA3) reference. */ + ret = wc_InitSha3_256(sha, NULL, devId); + if (ret == 0) { + ret = wc_Sha3_SetFlags(sha, WC_HASH_SHA3_KECCAK256); + } + if (ret == 0) { + ret = wc_Sha3_256_Update(sha, (const byte*)in, inLen); + } + if (ret == 0) { + ret = wc_Sha3_256_Final(sha, out); + } + if (ret != 0) { + WH_ERROR_PRINT("Keccak cryptocb fallback failed: %d\n", ret); + return ret; + } + if (memcmp(out, keccakRef, WC_SHA3_256_DIGEST_SIZE) != 0) { + WH_ERROR_PRINT("Keccak cryptocb fallback mismatch (devId=0x%X)\n", + devId); + return -1; + } + + /* direct client API contract: KECCAK256 flag must be rejected. */ + ret = wc_InitSha3_256(sha, NULL, devId); + if (ret == 0) { + ret = wc_Sha3_SetFlags(sha, WC_HASH_SHA3_KECCAK256); + } + if (ret != 0) { + return ret; + } + ret = wh_Client_Sha3_256(ctx, sha, (const uint8_t*)in, inLen, out); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Keccak direct API: expected BADARGS, got %d\n", ret); + return -1; + } + +#ifdef WOLFHSM_CFG_DMA + ret = wc_InitSha3_256(sha, NULL, devId); + if (ret == 0) { + ret = wc_Sha3_SetFlags(sha, WC_HASH_SHA3_KECCAK256); + } + if (ret != 0) { + return ret; + } + ret = wh_Client_Sha3_256Dma(ctx, sha, (const uint8_t*)in, inLen, out); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("Keccak direct DMA API: expected BADARGS, got %d\n", + ret); + return -1; + } +#endif + + WH_TEST_PRINT("SHA3-256 KECCAK fallback/reject DEVID=0x%X SUCCESS\n", + devId); + return 0; +} +#endif /* WOLFSSL_HASH_FLAGS && !WOLFSSL_NOSHA3_256 */ + +static int whTest_CryptoSha3(whClientContext* ctx, int devId, WC_RNG* rng) +{ + int ret = WH_ERROR_OK; + size_t v; + (void)rng; + for (v = 0; v < sizeof(whTestSha3Variants) / sizeof(whTestSha3Variants[0]); + v++) { + ret = whTest_CryptoSha3OneVariant(ctx, devId, &whTestSha3Variants[v]); + if (ret != 0) + break; + ret = whTest_CryptoSha3OneshotBadArgs(ctx, &whTestSha3Variants[v]); + if (ret != 0) + break; + } +#if defined(WOLFSSL_HASH_FLAGS) && !defined(WOLFSSL_NOSHA3_256) + if (ret == 0) { + ret = whTest_CryptoSha3Keccak(ctx, devId); + } +#endif + return ret; +} + +/* Per-variant native async test. Exercises: basic single round, pure-buffer + * fill, multi-round, and oversized-input rejection. */ +static int whTest_CryptoSha3AsyncOneVariant(whClientContext* ctx, int devId, + const whTestSha3Variant* v) +{ + int ret = WH_ERROR_OK; + wc_Sha3 sha[1]; + uint8_t out[WC_SHA3_512_DIGEST_SIZE]; + uint8_t ref[WC_SHA3_512_DIGEST_SIZE]; + + whTest_Sha3FillBuf(whTest_Sha3BigBuf, WH_TEST_SHA3_BIGBUF_SZ); + + /* Case A: basic Update + Final via the async API */ + if (ret == 0) { + uint32_t len = 3u * v->blockSize + 17u; + bool sent = false; + ret = whTest_Sha3Reference(v, whTest_Sha3BigBuf, len, ref); + if (ret == 0) + ret = v->initFn(sha, NULL, devId); + if (ret == 0) + ret = + v->asyncUpdateRequest(ctx, sha, whTest_Sha3BigBuf, len, &sent); + if (ret == 0 && sent) { + do { + ret = v->asyncUpdateResponse(ctx, sha); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0) + ret = v->asyncFinalRequest(ctx, sha); + if (ret == 0) { + do { + ret = v->asyncFinalResponse(ctx, sha, out); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0 && memcmp(out, ref, v->digestSize) != 0) { + WH_ERROR_PRINT("%s async case A mismatch\n", v->name); + ret = -1; + } + } + + /* Case B: pure-buffer-fill update (less than one block), Final */ + if (ret == 0) { + uint32_t len = v->blockSize - 1u; + bool sent = false; + ret = whTest_Sha3Reference(v, whTest_Sha3BigBuf, len, ref); + if (ret == 0) + ret = v->initFn(sha, NULL, devId); + if (ret == 0) + ret = + v->asyncUpdateRequest(ctx, sha, whTest_Sha3BigBuf, len, &sent); + if (ret == 0 && sent) { + /* unexpected — should have been fully buffered */ + WH_ERROR_PRINT("%s case B: unexpected sent==true\n", v->name); + ret = -1; + } + if (ret == 0) + ret = v->asyncFinalRequest(ctx, sha); + if (ret == 0) { + do { + ret = v->asyncFinalResponse(ctx, sha, out); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0 && memcmp(out, ref, v->digestSize) != 0) { + WH_ERROR_PRINT("%s async case B mismatch\n", v->name); + ret = -1; + } + } + + /* Case C: multi-round, ~70% of per-call capacity per chunk */ + if (ret == 0) { + uint32_t total = WH_TEST_SHA3_BIGBUF_SZ; + uint32_t consumed = 0; + uint32_t cap; + ret = whTest_Sha3Reference(v, whTest_Sha3BigBuf, total, ref); + if (ret == 0) + ret = v->initFn(sha, NULL, devId); + while (ret == 0 && consumed < total) { + cap = v->maxInlineSz + (uint32_t)(v->blockSize - 1u - sha->i); + uint32_t chunk = (cap * 7u) / 10u; + uint32_t rem = total - consumed; + bool sent = false; + if (chunk == 0) + chunk = 1; + if (chunk > cap) + chunk = cap; + if (chunk > rem) + chunk = rem; + ret = v->asyncUpdateRequest(ctx, sha, whTest_Sha3BigBuf + consumed, + chunk, &sent); + if (ret == 0 && sent) { + do { + ret = v->asyncUpdateResponse(ctx, sha); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0) + consumed += chunk; + } + if (ret == 0) + ret = v->asyncFinalRequest(ctx, sha); + if (ret == 0) { + do { + ret = v->asyncFinalResponse(ctx, sha, out); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0 && memcmp(out, ref, v->digestSize) != 0) { + WH_ERROR_PRINT("%s async case C mismatch\n", v->name); + ret = -1; + } + } + + /* Case D: oversize input must be rejected without state mutation. */ + if (ret == 0) { + uint32_t cap, oversz; + wc_Sha3 saved; + bool sent = false; + ret = v->initFn(sha, NULL, devId); + cap = v->maxInlineSz + (uint32_t)(v->blockSize - 1u - sha->i); + oversz = cap + 1u; + if (oversz > WH_TEST_SHA3_BIGBUF_SZ) + oversz = WH_TEST_SHA3_BIGBUF_SZ; + if (ret == 0) { + saved = *sha; + ret = v->asyncUpdateRequest(ctx, sha, whTest_Sha3BigBuf, oversz, + &sent); + if (ret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s case D: expected BADARGS, got %d\n", v->name, + ret); + ret = -1; + } + else if (sent) { + WH_ERROR_PRINT("%s case D: sent should be false\n", v->name); + ret = -1; + } + else if (memcmp(&saved, sha, sizeof(saved)) != 0) { + WH_ERROR_PRINT("%s case D: sha state mutated\n", v->name); + ret = -1; + } + else { + ret = WH_ERROR_OK; + } + } + } + + if (ret == 0) { + WH_TEST_PRINT("%s ASYNC DEVID=0x%X SUCCESS\n", v->name, devId); + } + return ret; +} + +static int whTest_CryptoSha3Async(whClientContext* ctx, int devId, WC_RNG* rng) +{ + int ret = WH_ERROR_OK; + size_t v; + (void)rng; + for (v = 0; v < sizeof(whTestSha3Variants) / sizeof(whTestSha3Variants[0]); + v++) { + ret = whTest_CryptoSha3AsyncOneVariant(ctx, devId, + &whTestSha3Variants[v]); + if (ret != 0) + break; + } + return ret; +} + +#ifdef WOLFHSM_CFG_DMA +static int whTest_CryptoSha3DmaAsyncOneVariant(whClientContext* ctx, int devId, + const whTestSha3Variant* v) +{ + int ret = WH_ERROR_OK; + wc_Sha3 sha[1]; + uint8_t out[WC_SHA3_512_DIGEST_SIZE]; + uint8_t ref[WC_SHA3_512_DIGEST_SIZE]; + + whTest_Sha3FillBuf(whTest_Sha3BigBuf, WH_TEST_SHA3_BIGBUF_SZ); + + /* Case A: single large DMA update + final */ + if (ret == 0) { + uint32_t len = WH_TEST_SHA3_BIGBUF_SZ; + bool sent = false; + ret = whTest_Sha3Reference(v, whTest_Sha3BigBuf, len, ref); + if (ret == 0) + ret = v->initFn(sha, NULL, devId); + if (ret == 0) + ret = v->dmaUpdateRequest(ctx, sha, whTest_Sha3BigBuf, len, &sent); + if (ret == 0 && sent) { + do { + ret = v->dmaUpdateResponse(ctx, sha); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0) + ret = v->dmaFinalRequest(ctx, sha); + if (ret == 0) { + do { + ret = v->dmaFinalResponse(ctx, sha, out); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0 && memcmp(out, ref, v->digestSize) != 0) { + WH_ERROR_PRINT("%s DMA async case A mismatch\n", v->name); + ret = -1; + } + } + + /* Case B: multi-round DMA with 1024-byte chunks */ + if (ret == 0) { + uint32_t total = WH_TEST_SHA3_BIGBUF_SZ; + uint32_t consumed = 0; + const uint32_t chunkSz = 1024u; + ret = whTest_Sha3Reference(v, whTest_Sha3BigBuf, total, ref); + if (ret == 0) + ret = v->initFn(sha, NULL, devId); + while (ret == 0 && consumed < total) { + uint32_t rem = total - consumed; + uint32_t chunk = (rem < chunkSz) ? rem : chunkSz; + bool sent = false; + ret = v->dmaUpdateRequest(ctx, sha, whTest_Sha3BigBuf + consumed, + chunk, &sent); + if (ret == 0 && sent) { + do { + ret = v->dmaUpdateResponse(ctx, sha); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0) + consumed += chunk; + } + if (ret == 0) + ret = v->dmaFinalRequest(ctx, sha); + if (ret == 0) { + do { + ret = v->dmaFinalResponse(ctx, sha, out); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == 0 && memcmp(out, ref, v->digestSize) != 0) { + WH_ERROR_PRINT("%s DMA async case B mismatch\n", v->name); + ret = -1; + } + } + + if (ret == 0) { + WH_TEST_PRINT("%s DMA ASYNC DEVID=0x%X SUCCESS\n", v->name, devId); + } + return ret; +} + +static int whTest_CryptoSha3DmaAsync(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret = WH_ERROR_OK; + size_t v; + (void)rng; + for (v = 0; v < sizeof(whTestSha3Variants) / sizeof(whTestSha3Variants[0]); + v++) { + ret = whTest_CryptoSha3DmaAsyncOneVariant(ctx, devId, + &whTestSha3Variants[v]); + if (ret != 0) + break; + } + return ret; +} +#endif /* WOLFHSM_CFG_DMA */ + +#endif /* WOLFSSL_SHA3 */ + #ifdef HAVE_HKDF static int whTest_CryptoHkdf(whClientContext* ctx, int devId, WC_RNG* rng) { @@ -12072,6 +12744,24 @@ int whTest_CryptoClientConfig(whClientConfig* config) #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) + i = 0; + while ((ret == WH_ERROR_OK) && (i < WH_NUM_DEVIDS)) { + ret = whTest_CryptoSha3(client, WH_DEV_IDS_ARRAY[i], rng); + if (ret == WH_ERROR_OK) { + ret = whTest_CryptoSha3Async(client, WH_DEV_IDS_ARRAY[i], rng); + } + if (ret == WH_ERROR_OK) { + i++; + } + } +#ifdef WOLFHSM_CFG_DMA + if (ret == WH_ERROR_OK) { + ret = whTest_CryptoSha3DmaAsync(client, WH_DEV_ID_DMA, rng); + } +#endif /* WOLFHSM_CFG_DMA */ +#endif /* WOLFSSL_SHA3 */ + #ifdef HAVE_HKDF if (ret == WH_ERROR_OK) { ret = whTest_CryptoHkdf(client, WH_DEV_ID, rng); diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index e309c935a..dc2f33d7f 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -52,6 +52,9 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/hmac.h" +#ifdef WOLFSSL_SHA3 +#include "wolfssl/wolfcrypt/sha3.h" +#endif /** * @brief Generate random bytes @@ -2397,6 +2400,131 @@ int wh_Client_Sha512DmaFinalResponse(whClientContext* ctx, wc_Sha512* sha, #endif /* WOLFSSL_SHA512 */ +#if defined(WOLFSSL_SHA3) +/* SHA3 client API. + * + * SHA3 has 4 fixed-output variants (224/256/384/512), all backed by the + * single wolfCrypt wc_Sha3 struct. The client API mirrors the wolfCrypt + * surface (wc_Sha3__Update/Final), with per-variant public functions + * sharing internal helpers parameterized by hash type and block size. + * + * Block sizes: 144 / 136 / 104 / 72 for 224 / 256 / 384 / 512 + * Digest sizes: 28 / 32 / 48 / 64 + * + * State buffering: the client uses sha3->t[0..i-1] and sha3->i as the + * partial-block buffer (analogous to sha256->buffer/buffLen). Non-final + * Update requests carry whole blocks only; Final carries any partial + * tail inline. + * + * Async contract (per-variant): at most one outstanding async request + * may be in flight per whClientContext. If UpdateRequest returns + * *requestSent == true, the caller MUST call the matching UpdateResponse + * before issuing any other async request on the same ctx. + * + * Keccak-mode contexts (sha->flags & WC_HASH_SHA3_KECCAK256) are rejected + * with WH_ERROR_BADARGS: the server applies the standard SHA-3 0x06 + * padding and has no Keccak fallback. Use the cryptocb path, which routes + * Keccak to software. + */ + +#ifndef WOLFSSL_NOSHA3_224 +/* SHA3-224 */ +int wh_Client_Sha3_224(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_224UpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_224UpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_224FinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_224FinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#ifdef WOLFHSM_CFG_DMA +int wh_Client_Sha3_224Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_224DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_224DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_224DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_224DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#endif /* WOLFHSM_CFG_DMA */ +#endif /* !WOLFSSL_NOSHA3_224 */ + +#ifndef WOLFSSL_NOSHA3_256 +/* SHA3-256 */ +int wh_Client_Sha3_256(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_256UpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_256UpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_256FinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_256FinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#ifdef WOLFHSM_CFG_DMA +int wh_Client_Sha3_256Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_256DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_256DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_256DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_256DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#endif /* WOLFHSM_CFG_DMA */ +#endif /* !WOLFSSL_NOSHA3_256 */ + +#ifndef WOLFSSL_NOSHA3_384 +/* SHA3-384 */ +int wh_Client_Sha3_384(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_384UpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_384UpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_384FinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_384FinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#ifdef WOLFHSM_CFG_DMA +int wh_Client_Sha3_384Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_384DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_384DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_384DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_384DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#endif /* WOLFHSM_CFG_DMA */ +#endif /* !WOLFSSL_NOSHA3_384 */ + +#ifndef WOLFSSL_NOSHA3_512 +/* SHA3-512 */ +int wh_Client_Sha3_512(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_512UpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_512UpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_512FinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_512FinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#ifdef WOLFHSM_CFG_DMA +int wh_Client_Sha3_512Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in, + uint32_t inLen, uint8_t* out); +int wh_Client_Sha3_512DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha, + const uint8_t* in, uint32_t inLen, + bool* requestSent); +int wh_Client_Sha3_512DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_512DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha); +int wh_Client_Sha3_512DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha, + uint8_t* out); +#endif /* WOLFHSM_CFG_DMA */ +#endif /* !WOLFSSL_NOSHA3_512 */ + +#endif /* WOLFSSL_SHA3 */ + #ifdef WOLFSSL_HAVE_MLDSA /** diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index 69471bf7f..1fb221360 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -786,8 +786,12 @@ typedef struct { 64u) * \ 64u) +/* Needed whenever the SHA256-family wire format (block 64) is compiled: + * SHA256 (!NO_SHA256) or SHA224, which reuses it. */ +#if !defined(NO_SHA256) || defined(WOLFSSL_SHA224) WH_UTILS_STATIC_ASSERT(WH_MESSAGE_CRYPTO_SHA256_MAX_INLINE_UPDATE_SZ >= 64u, "Comm buffer too small to fit a SHA256 block"); +#endif /* SHA224 shares the SHA256 wire format and block size (64), so the same * per-call inline capacity applies. Exposed as a separate macro so SHA224 @@ -838,8 +842,12 @@ typedef struct { 128u) * \ 128u) +/* Needed whenever the SHA512-family wire format (block 128) is compiled: + * SHA512 or SHA384, which reuses it. */ +#if defined(WOLFSSL_SHA512) || defined(WOLFSSL_SHA384) WH_UTILS_STATIC_ASSERT(WH_MESSAGE_CRYPTO_SHA512_MAX_INLINE_UPDATE_SZ >= 128u, "Comm buffer too small to fit a SHA512 block"); +#endif /* SHA384 shares the SHA512 wire format and block size (128), so the same * per-call inline capacity applies. Exposed as a separate macro so SHA384 @@ -864,6 +872,116 @@ int wh_MessageCrypto_TranslateSha2Response( uint16_t magic, const whMessageCrypto_Sha2Response* src, whMessageCrypto_Sha2Response* dest); +/* + * SHA3 (all variants: 224/256/384/512) + * + * All SHA3 variants share the same wc_Sha3 struct and the same 200-byte + * Keccak state, so one wire format serves all four. The variant is + * communicated via the algoType field in the generic crypto request + * header (WC_HASH_TYPE_SHA3_224/256/384/512). Per-variant constraints + * (block size, digest size) are enforced by the server. + * + * Wire layout in the comm buffer: + * whMessageCrypto_GenericRequestHeader + * whMessageCrypto_Sha3Request + * uint8_t in[inSz] + * + * Non-final updates: inSz must be a multiple of the variant's block + * size (144/136/104/72). The client buffers any partial-block tail + * locally in sha3->t[] and only sends it on Final with isLastBlock=1. + */ + +/* SHA3 resume state - 200-byte Keccak state + buffer index, shared across + * SHA3-224/256/384/512. Layout chosen so the uint64_t array sits at the + * end (8-byte aligned). */ +typedef struct { + uint32_t i; /* reserved, always 0 — the partial-block buffer lives on + * the client; the wire only carries whole-block input */ + uint8_t WH_PAD[4]; + uint64_t s[25]; /* Keccak state */ +} whMessageCrypto_Sha3State; + +/* SHA3 Request (variable-length input data follows the struct). */ +typedef struct { + uint32_t isLastBlock; + uint32_t inSz; + whMessageCrypto_Sha3State resumeState; +} whMessageCrypto_Sha3Request; + +/* SHA3 Response. On non-final updates, carries the updated Keccak state. + * On Final, the hash[] field carries the digest (length implied by the + * variant). */ +typedef struct { + whMessageCrypto_Sha3State resumeState; + uint8_t hash[64]; /* WC_SHA3_512_DIGEST_SIZE - max across variants */ +} whMessageCrypto_Sha3Response; + +/* Per-variant max-inline update sizes (block sizes differ across variants + * so each gets its own macro, rounded down to a whole-block multiple). */ +#define WH_MESSAGE_CRYPTO_SHA3_224_MAX_INLINE_UPDATE_SZ \ + (((WOLFHSM_CFG_COMM_DATA_LEN - \ + (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) - \ + (uint32_t)sizeof(whMessageCrypto_Sha3Request)) / \ + 144u) * \ + 144u) + +#define WH_MESSAGE_CRYPTO_SHA3_256_MAX_INLINE_UPDATE_SZ \ + (((WOLFHSM_CFG_COMM_DATA_LEN - \ + (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) - \ + (uint32_t)sizeof(whMessageCrypto_Sha3Request)) / \ + 136u) * \ + 136u) + +#define WH_MESSAGE_CRYPTO_SHA3_384_MAX_INLINE_UPDATE_SZ \ + (((WOLFHSM_CFG_COMM_DATA_LEN - \ + (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) - \ + (uint32_t)sizeof(whMessageCrypto_Sha3Request)) / \ + 104u) * \ + 104u) + +#define WH_MESSAGE_CRYPTO_SHA3_512_MAX_INLINE_UPDATE_SZ \ + (((WOLFHSM_CFG_COMM_DATA_LEN - \ + (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) - \ + (uint32_t)sizeof(whMessageCrypto_Sha3Request)) / \ + 72u) * \ + 72u) + +/* Each enabled SHA3 variant must fit at least one block inline. Gated + * per-variant (mirroring the dispatch table and cryptocb cases) so a build that + * compiles out a variant isn't forced to size its comm buffer for it; SHA3-224 + * has the largest block (144) and sets the floor when enabled. */ +#if defined(WOLFSSL_SHA3) +#ifndef WOLFSSL_NOSHA3_224 +WH_UTILS_STATIC_ASSERT(WH_MESSAGE_CRYPTO_SHA3_224_MAX_INLINE_UPDATE_SZ >= 144u, + "Comm buffer too small to fit a SHA3-224 block"); +#endif +#ifndef WOLFSSL_NOSHA3_256 +WH_UTILS_STATIC_ASSERT(WH_MESSAGE_CRYPTO_SHA3_256_MAX_INLINE_UPDATE_SZ >= 136u, + "Comm buffer too small to fit a SHA3-256 block"); +#endif +#ifndef WOLFSSL_NOSHA3_384 +WH_UTILS_STATIC_ASSERT(WH_MESSAGE_CRYPTO_SHA3_384_MAX_INLINE_UPDATE_SZ >= 104u, + "Comm buffer too small to fit a SHA3-384 block"); +#endif +#ifndef WOLFSSL_NOSHA3_512 +WH_UTILS_STATIC_ASSERT(WH_MESSAGE_CRYPTO_SHA3_512_MAX_INLINE_UPDATE_SZ >= 72u, + "Comm buffer too small to fit a SHA3-512 block"); +#endif +#endif /* WOLFSSL_SHA3 */ + +int wh_MessageCrypto_TranslateSha3State(uint16_t magic, + const whMessageCrypto_Sha3State* src, + whMessageCrypto_Sha3State* dest); + +int wh_MessageCrypto_TranslateSha3Request( + uint16_t magic, const whMessageCrypto_Sha3Request* src, + whMessageCrypto_Sha3Request* dest); + +int wh_MessageCrypto_TranslateSha3Response( + uint16_t magic, const whMessageCrypto_Sha3Response* src, + whMessageCrypto_Sha3Response* dest); + + /* * CMAC (AES) */ @@ -1103,6 +1221,72 @@ int wh_MessageCrypto_TranslateSha2DmaResponse( uint16_t magic, const whMessageCrypto_Sha2DmaResponse* src, whMessageCrypto_Sha2DmaResponse* dest); +/* SHA3 DMA Request - state is passed inline (not via DMA) for + * cross-architecture safety. Only whole-block input data goes via DMA. + * Variant is conveyed in the generic request header's algoType field. + * + * Wire layout in the comm buffer: + * whMessageCrypto_GenericRequestHeader + * whMessageCrypto_Sha3DmaRequest + * uint8_t in[inSz] (inline trailing data: assembled first block from + * partial buffer, or partial tail on Final) + * + * Non-final: DMA input must be whole blocks. inSz is 0 or BLOCK_SIZE + * (assembled first block from client partial buffer). + * Final: inSz is 0..(BLOCK_SIZE-1), no DMA input. + */ +typedef struct { + whMessageCrypto_DmaBuffer input; + uint32_t isLastBlock; + uint32_t inSz; + whMessageCrypto_Sha3State resumeState; +} whMessageCrypto_Sha3DmaRequest; + +/* SHA3 DMA Response - carries updated state or final hash inline */ +typedef struct { + whMessageCrypto_Sha3State resumeState; + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint8_t hash[64]; /* WC_SHA3_512_DIGEST_SIZE */ +} whMessageCrypto_Sha3DmaResponse; + +/* Largest block among the *enabled* SHA3 variants; bounds the single assembled + * block the DMA path copies inline. Tracks the per-variant gating above so + * disabling SHA3-224 doesn't keep its 144-byte requirement. Left undefined when + * every variant is disabled, which drops the DMA assert below. */ +#if defined(WOLFSSL_SHA3) +#ifndef WOLFSSL_NOSHA3_224 +#define WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ 144u +#elif !defined(WOLFSSL_NOSHA3_256) +#define WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ 136u +#elif !defined(WOLFSSL_NOSHA3_384) +#define WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ 104u +#elif !defined(WOLFSSL_NOSHA3_512) +#define WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ 72u +#endif +#endif /* WOLFSSL_SHA3 */ + +#if defined(WOLFHSM_CFG_DMA) && defined(WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ) +/* The DMA update/final paths copy one assembled block inline immediately after + * the (larger) Sha3DmaRequest header, before wh_Client_SendRequest can reject + * an oversized message. The non-DMA asserts above bound only + * sizeof(Sha3Request), so the DMA wire size needs its own bound. The largest + * inline copy is one full block of the largest enabled variant. */ +WH_UTILS_STATIC_ASSERT( + (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) + + (uint32_t)sizeof(whMessageCrypto_Sha3DmaRequest) + + WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ <= + (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN, + "Comm buffer too small for a SHA3 DMA block"); +#endif /* WOLFHSM_CFG_DMA && WH_MESSAGE_CRYPTO_SHA3_MAX_BLOCK_SZ */ + +int wh_MessageCrypto_TranslateSha3DmaRequest( + uint16_t magic, const whMessageCrypto_Sha3DmaRequest* src, + whMessageCrypto_Sha3DmaRequest* dest); + +int wh_MessageCrypto_TranslateSha3DmaResponse( + uint16_t magic, const whMessageCrypto_Sha3DmaResponse* src, + whMessageCrypto_Sha3DmaResponse* dest); + /* CMAC-AES DMA Request - state, key, and output are passed inline in the * message for cross-architecture safety. Input may be carried via DMA * (input.sz bytes, whole-block aligned) AND/OR inline (inlineInSz bytes) —