diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c
index 0b2585729..d11e64609 100644
--- a/src/wh_client_crypto.c
+++ b/src/wh_client_crypto.c
@@ -8416,6 +8416,882 @@ 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 */
+
+ if (ctx == NULL || sha == NULL || requestSent == NULL ||
+ (in == NULL && inLen != 0)) {
+ return WH_ERROR_BADARGS;
+ }
+ *requestSent = false;
+
+ ret = _Sha3RejectKeccak(sha);
+ if (ret != WH_ERROR_OK) {
+ return ret;
+ }
+
+ 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 input blocks as will 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;
+ }
+
+ req->isLastBlock = 0;
+ req->inSz = wirePos;
+ memcpy(req->resumeState.s, sha->s, sizeof(req->resumeState.s));
+
+ 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;
+
+ 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) {
+ if (dataSz <
+ sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res)) {
+ return WH_ERROR_ABORTED;
+ }
+ memcpy(sha->s, res->resumeState.s, sizeof(sha->s));
+ }
+ return ret;
+}
+
+static int _Sha3FinalRequest(whClientContext* ctx, wc_Sha3* sha,
+ const whSha3Variant* v)
+{
+ int ret;
+ whMessageCrypto_Sha3Request* req;
+ uint8_t* inlineData;
+ uint8_t* dataPtr;
+
+ 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);
+
+ req->isLastBlock = 1;
+ req->inSz = sha->i;
+ memcpy(req->resumeState.s, sha->s, sizeof(req->resumeState.s));
+ 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) {
+ if (dataSz <
+ sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res)) {
+ return WH_ERROR_ABORTED;
+ }
+ 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. */
+
+#ifndef WOLFSSL_NOSHA3_224
+int wh_Client_Sha3_224(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3Oneshot(ctx, sha, &whSha3_224, in, inLen, out);
+}
+
+int wh_Client_Sha3_224UpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3UpdateRequest(ctx, sha, &whSha3_224, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_224UpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3UpdateResponse(ctx, sha, &whSha3_224);
+}
+
+int wh_Client_Sha3_224FinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3FinalRequest(ctx, sha, &whSha3_224);
+}
+
+int wh_Client_Sha3_224FinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3FinalResponse(ctx, sha, &whSha3_224, out);
+}
+#endif /* !WOLFSSL_NOSHA3_224 */
+
+#ifndef WOLFSSL_NOSHA3_256
+int wh_Client_Sha3_256(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3Oneshot(ctx, sha, &whSha3_256, in, inLen, out);
+}
+
+int wh_Client_Sha3_256UpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3UpdateRequest(ctx, sha, &whSha3_256, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_256UpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3UpdateResponse(ctx, sha, &whSha3_256);
+}
+
+int wh_Client_Sha3_256FinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3FinalRequest(ctx, sha, &whSha3_256);
+}
+
+int wh_Client_Sha3_256FinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3FinalResponse(ctx, sha, &whSha3_256, out);
+}
+#endif /* !WOLFSSL_NOSHA3_256 */
+
+#ifndef WOLFSSL_NOSHA3_384
+int wh_Client_Sha3_384(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3Oneshot(ctx, sha, &whSha3_384, in, inLen, out);
+}
+
+int wh_Client_Sha3_384UpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3UpdateRequest(ctx, sha, &whSha3_384, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_384UpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3UpdateResponse(ctx, sha, &whSha3_384);
+}
+
+int wh_Client_Sha3_384FinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3FinalRequest(ctx, sha, &whSha3_384);
+}
+
+int wh_Client_Sha3_384FinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3FinalResponse(ctx, sha, &whSha3_384, out);
+}
+#endif /* !WOLFSSL_NOSHA3_384 */
+
+#ifndef WOLFSSL_NOSHA3_512
+int wh_Client_Sha3_512(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3Oneshot(ctx, sha, &whSha3_512, in, inLen, out);
+}
+
+int wh_Client_Sha3_512UpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3UpdateRequest(ctx, sha, &whSha3_512, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_512UpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3UpdateResponse(ctx, sha, &whSha3_512);
+}
+
+int wh_Client_Sha3_512FinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3FinalRequest(ctx, sha, &whSha3_512);
+}
+
+int wh_Client_Sha3_512FinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3FinalResponse(ctx, sha, &whSha3_512, out);
+}
+#endif /* !WOLFSSL_NOSHA3_512 */
+
+#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];
+
+ if (ctx == NULL || sha == NULL || requestSent == NULL ||
+ (in == NULL && inLen != 0)) {
+ return WH_ERROR_BADARGS;
+ }
+ *requestSent = false;
+
+ 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;
+ }
+ 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;
+ }
+
+ req->isLastBlock = 0;
+ req->inSz = wirePos;
+ memcpy(req->resumeState.s, sha->s, sizeof(req->resumeState.s));
+ 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;
+
+ 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) {
+ if (respSz <
+ sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else {
+ memcpy(sha->s, resp->resumeState.s, sizeof(sha->s));
+ }
+ }
+ }
+
+ 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;
+
+ 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);
+
+ req->isLastBlock = 1;
+ req->inSz = sha->i;
+ memcpy(req->resumeState.s, sha->s, sizeof(req->resumeState.s));
+ 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) {
+ if (respSz <
+ sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*resp)) {
+ return WH_ERROR_ABORTED;
+ }
+ 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;
+}
+
+/* Per-variant DMA public APIs - thin wrappers over the shared DMA helpers. */
+
+#ifndef WOLFSSL_NOSHA3_224
+int wh_Client_Sha3_224Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3DmaOneshot(ctx, sha, &whSha3_224, in, inLen, out);
+}
+
+int wh_Client_Sha3_224DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3DmaUpdateRequest(ctx, sha, &whSha3_224, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_224DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaUpdateResponse(ctx, sha, &whSha3_224);
+}
+
+int wh_Client_Sha3_224DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaFinalRequest(ctx, sha, &whSha3_224);
+}
+
+int wh_Client_Sha3_224DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3DmaFinalResponse(ctx, sha, &whSha3_224, out);
+}
+#endif /* !WOLFSSL_NOSHA3_224 */
+
+#ifndef WOLFSSL_NOSHA3_256
+int wh_Client_Sha3_256Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3DmaOneshot(ctx, sha, &whSha3_256, in, inLen, out);
+}
+
+int wh_Client_Sha3_256DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3DmaUpdateRequest(ctx, sha, &whSha3_256, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_256DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaUpdateResponse(ctx, sha, &whSha3_256);
+}
+
+int wh_Client_Sha3_256DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaFinalRequest(ctx, sha, &whSha3_256);
+}
+
+int wh_Client_Sha3_256DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3DmaFinalResponse(ctx, sha, &whSha3_256, out);
+}
+#endif /* !WOLFSSL_NOSHA3_256 */
+
+#ifndef WOLFSSL_NOSHA3_384
+int wh_Client_Sha3_384Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3DmaOneshot(ctx, sha, &whSha3_384, in, inLen, out);
+}
+
+int wh_Client_Sha3_384DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3DmaUpdateRequest(ctx, sha, &whSha3_384, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_384DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaUpdateResponse(ctx, sha, &whSha3_384);
+}
+
+int wh_Client_Sha3_384DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaFinalRequest(ctx, sha, &whSha3_384);
+}
+
+int wh_Client_Sha3_384DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3DmaFinalResponse(ctx, sha, &whSha3_384, out);
+}
+#endif /* !WOLFSSL_NOSHA3_384 */
+
+#ifndef WOLFSSL_NOSHA3_512
+int wh_Client_Sha3_512Dma(whClientContext* ctx, wc_Sha3* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out)
+{
+ return _Sha3DmaOneshot(ctx, sha, &whSha3_512, in, inLen, out);
+}
+
+int wh_Client_Sha3_512DmaUpdateRequest(whClientContext* ctx, wc_Sha3* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _Sha3DmaUpdateRequest(ctx, sha, &whSha3_512, in, inLen, requestSent);
+}
+
+int wh_Client_Sha3_512DmaUpdateResponse(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaUpdateResponse(ctx, sha, &whSha3_512);
+}
+
+int wh_Client_Sha3_512DmaFinalRequest(whClientContext* ctx, wc_Sha3* sha)
+{
+ return _Sha3DmaFinalRequest(ctx, sha, &whSha3_512);
+}
+
+int wh_Client_Sha3_512DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha,
+ uint8_t* out)
+{
+ return _Sha3DmaFinalResponse(ctx, sha, &whSha3_512, out);
+}
+#endif /* !WOLFSSL_NOSHA3_512 */
+#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 9da9f402e..71158d3ba 100644
--- a/src/wh_client_cryptocb.c
+++ b/src/wh_client_cryptocb.c
@@ -628,6 +628,53 @@ int wh_Client_CryptoCbStd(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;
@@ -1396,6 +1443,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 205065c9a..8012252ec 100644
--- a/src/wh_message_crypto.c
+++ b/src/wh_message_crypto.c
@@ -704,6 +704,59 @@ 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;
+ }
+ 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(
@@ -1037,6 +1090,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 e5f538915..f2942fbc2 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"
#if defined(WOLFSSL_HAVE_LMS)
@@ -4540,6 +4543,140 @@ 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;
+
+ (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. */
+ memcpy(sha3->s, req.resumeState.s, sizeof(sha3->s));
+
+ 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 {
+ memcpy(res.resumeState.s, sha3->s, sizeof(res.resumeState.s));
+ }
+ }
+ }
+
+ 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
@@ -5614,6 +5751,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;
@@ -6104,6 +6256,104 @@ 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;
+
+ 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. */
+ memcpy(sha3->s, req.resumeState.s, sizeof(sha3->s));
+
+ 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 {
+ memcpy(res.resumeState.s, sha3->s, sizeof(res.resumeState.s));
+ }
+ }
+ }
+
+ (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,
@@ -8421,6 +8671,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-refactor/client-server/wh_test_crypto_sha3.c b/test-refactor/client-server/wh_test_crypto_sha3.c
new file mode 100644
index 000000000..835d079da
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_sha3.c
@@ -0,0 +1,750 @@
+/*
+ * 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_sha3.c
+ *
+ * SHA3-224/256/384/512 routed through the server via the per-client devId
+ * (WH_CLIENT_DEVID). A single set of tests runs against all compiled
+ * variants via a small dispatch table:
+ * sync wolfCrypt API across length edge cases vs software reference,
+ * plus one-shot wrapper bad-arg rejection and the Keccak-mode
+ * fallback/reject contract
+ * async direct exercise of the wh_Client_Sha3_* request/response
+ * primitives
+ * dma async same, via the DMA messaging path
+ */
+
+#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/sha3.h"
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_common.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_client_crypto.h"
+#include "wolfhsm/wh_message_crypto.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+#if defined(WOLFSSL_SHA3)
+
+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 check: 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 */
+
+/* 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;
+}
+
+#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;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+/* Run the cryptocb-routed suites (wolfCrypt API, one-shot bad-args, Keccak
+ * contract) for every compiled variant against the given devId. */
+static int _whTest_CryptoSha3Sync(whClientContext* ctx, int devId)
+{
+ int ret = WH_ERROR_OK;
+ size_t v;
+ 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;
+}
+
+static int _whTest_CryptoSha3Async(whClientContext* ctx)
+{
+ int devId = WH_CLIENT_DEVID(ctx);
+ int ret = WH_ERROR_OK;
+ size_t v;
+ 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_CryptoSha3DmaAsync(whClientContext* ctx)
+{
+ int devId = WH_CLIENT_DEVID(ctx);
+ int ret = WH_ERROR_OK;
+ size_t v;
+
+ /* Prefer DMA dispatch so wolfCrypt-routed ops take the DMA path alongside
+ * the wh_Client_*Dma request API driven by the test. */
+ (void)wh_Client_SetDmaMode(ctx, 1);
+ for (v = 0; v < sizeof(whTestSha3Variants) / sizeof(whTestSha3Variants[0]);
+ v++) {
+ ret = whTest_CryptoSha3DmaAsyncOneVariant(ctx, devId,
+ &whTestSha3Variants[v]);
+ if (ret != 0)
+ break;
+ }
+ /* Restore the standard (non-DMA) dispatch mode */
+ (void)wh_Client_SetDmaMode(ctx, 0);
+ return ret;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+int whTest_Crypto_Sha3(whClientContext* ctx)
+{
+ int i;
+
+ /* The wolfCrypt API path dispatches through the cryptocb, so run it once
+ * per dispatch mode on the per-client devId to cover the normal and DMA
+ * server paths. The explicit request/response suites are
+ * transport-specific (comm-buffer Async vs DmaAsync), not devId-routed --
+ * run each once. */
+ for (i = 0; i < WH_TEST_DMA_MODE_CNT; i++) {
+ (void)wh_Client_SetDmaMode(ctx, i);
+ WH_TEST_RETURN_ON_FAIL(
+ _whTest_CryptoSha3Sync(ctx, WH_CLIENT_DEVID(ctx)));
+ }
+ (void)wh_Client_SetDmaMode(ctx, 0);
+
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha3Async(ctx));
+#ifdef WOLFHSM_CFG_DMA
+ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSha3DmaAsync(ctx));
+#endif
+ return 0;
+}
+
+#endif /* WOLFSSL_SHA3 */
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c
index 1e6bf04b7..a4b5a3aa8 100644
--- a/test-refactor/misc/wh_test_check_struct_padding.c
+++ b/test-refactor/misc/wh_test_check_struct_padding.c
@@ -124,6 +124,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;
whMessageCrypto_MlDsaKeyGenRequest pkMldsaKeygenReq;
@@ -144,6 +149,10 @@ whMessageCrypto_MlKemDecapsResponse pkMlkemDecapsRes;
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-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index 2dae351f3..93c000dab 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -64,6 +64,7 @@ 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_Sha3);
WH_TEST_DECL(whTest_Crypto_Xmss);
WH_TEST_DECL(whTest_CryptoEcc256);
WH_TEST_DECL(whTest_CryptoEd25519BufferTooSmall);
@@ -107,41 +108,41 @@ const whTestCase whTestsServer[] = {
const size_t whTestsServerCount = ARRAY_SIZE(whTestsServer);
const whTestCase whTestsClient[] = {
- { "whTest_ClientCerts", whTest_ClientCerts },
- { "whTest_Counter", whTest_Counter },
- { "whTest_Crypto_Aes", whTest_Crypto_Aes },
- { "whTest_CryptoAesKeyUsagePolicies", whTest_CryptoAesKeyUsagePolicies },
- { "whTest_Crypto_Cmac", whTest_Crypto_Cmac },
- { "whTest_Crypto_Curve25519", whTest_Crypto_Curve25519 },
- { "whTest_Crypto_Ecc", whTest_Crypto_Ecc },
- { "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_CryptoEcc256", whTest_CryptoEcc256 },
- { "whTest_CryptoEd25519BufferTooSmall",
- whTest_CryptoEd25519BufferTooSmall },
- { "whTest_CryptoMlDsaBufferTooSmall", whTest_CryptoMlDsaBufferTooSmall },
- { "whTest_CryptoRsaBufferTooSmall", whTest_CryptoRsaBufferTooSmall },
- { "whTest_CryptoSha256", whTest_CryptoSha256 },
- { "whTest_She", whTest_She },
- { "whTest_Echo", whTest_Echo },
- { "whTest_NvmDma", whTest_NvmDma },
- { "whTest_NvmOps", whTest_NvmOps },
- { "whTest_ServerInfo", whTest_ServerInfo },
- { "whTest_WolfCryptTest", whTest_WolfCryptTest },
- { "whTest_AuthBadArgs", whTest_AuthBadArgs },
- { "whTest_AuthLogin", whTest_AuthLogin },
- { "whTest_AuthLogout", whTest_AuthLogout },
- { "whTest_AuthAddUser", whTest_AuthAddUser },
- { "whTest_AuthDeleteUser", whTest_AuthDeleteUser },
- { "whTest_AuthSetPermissions", whTest_AuthSetPermissions },
- { "whTest_AuthSetCredentials", whTest_AuthSetCredentials },
- { "whTest_AuthRequestAuthorization", whTest_AuthRequestAuthorization },
+ {"whTest_ClientCerts", whTest_ClientCerts},
+ {"whTest_Counter", whTest_Counter},
+ {"whTest_Crypto_Aes", whTest_Crypto_Aes},
+ {"whTest_CryptoAesKeyUsagePolicies", whTest_CryptoAesKeyUsagePolicies},
+ {"whTest_Crypto_Cmac", whTest_Crypto_Cmac},
+ {"whTest_Crypto_Curve25519", whTest_Crypto_Curve25519},
+ {"whTest_Crypto_Ecc", whTest_Crypto_Ecc},
+ {"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_Sha3", whTest_Crypto_Sha3},
+ {"whTest_Crypto_Xmss", whTest_Crypto_Xmss},
+ {"whTest_CryptoEcc256", whTest_CryptoEcc256},
+ {"whTest_CryptoEd25519BufferTooSmall", whTest_CryptoEd25519BufferTooSmall},
+ {"whTest_CryptoMlDsaBufferTooSmall", whTest_CryptoMlDsaBufferTooSmall},
+ {"whTest_CryptoRsaBufferTooSmall", whTest_CryptoRsaBufferTooSmall},
+ {"whTest_CryptoSha256", whTest_CryptoSha256},
+ {"whTest_She", whTest_She},
+ {"whTest_Echo", whTest_Echo},
+ {"whTest_NvmDma", whTest_NvmDma},
+ {"whTest_NvmOps", whTest_NvmOps},
+ {"whTest_ServerInfo", whTest_ServerInfo},
+ {"whTest_WolfCryptTest", whTest_WolfCryptTest},
+ {"whTest_AuthBadArgs", whTest_AuthBadArgs},
+ {"whTest_AuthLogin", whTest_AuthLogin},
+ {"whTest_AuthLogout", whTest_AuthLogout},
+ {"whTest_AuthAddUser", whTest_AuthAddUser},
+ {"whTest_AuthDeleteUser", whTest_AuthDeleteUser},
+ {"whTest_AuthSetPermissions", whTest_AuthSetPermissions},
+ {"whTest_AuthSetCredentials", whTest_AuthSetCredentials},
+ {"whTest_AuthRequestAuthorization", whTest_AuthRequestAuthorization},
};
const size_t whTestsClientCount = ARRAY_SIZE(whTestsClient);
diff --git a/test/config/user_settings.h b/test/config/user_settings.h
index 83d03e364..f237ccaa4 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
/* ML-KEM Options */
#define WOLFSSL_HAVE_MLKEM
diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c
index ba3d86295..0965c51e3 100644
--- a/test/wh_test_check_struct_padding.c
+++ b/test/wh_test_check_struct_padding.c
@@ -124,6 +124,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;
whMessageCrypto_MlDsaKeyGenRequest pkMldsaKeygenReq;
@@ -144,6 +149,10 @@ whMessageCrypto_MlKemDecapsResponse pkMlkemDecapsRes;
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 a5850cde5..d181188cd 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -6298,6 +6298,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 check: 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)
{
@@ -15617,6 +16289,26 @@ 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_TEST_DMA_MODE_CNT)) {
+ (void)wh_Client_SetDmaMode(client, i);
+ ret = whTest_CryptoSha3(client, WH_CLIENT_DEVID(client), rng);
+ if (ret == WH_ERROR_OK) {
+ ret = whTest_CryptoSha3Async(client, WH_CLIENT_DEVID(client), rng);
+ }
+ if (ret == WH_ERROR_OK) {
+ i++;
+ }
+ }
+#ifdef WOLFHSM_CFG_DMA
+ (void)wh_Client_SetDmaMode(client, 1);
+ if (ret == WH_ERROR_OK) {
+ ret = whTest_CryptoSha3DmaAsync(client, WH_CLIENT_DEVID(client), rng);
+ }
+#endif /* WOLFHSM_CFG_DMA */
+#endif /* WOLFSSL_SHA3 */
+
#ifdef HAVE_HKDF
(void)wh_Client_SetDmaMode(client, 0);
if (ret == WH_ERROR_OK) {
diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h
index ed1843033..7698b877b 100644
--- a/wolfhsm/wh_client_crypto.h
+++ b/wolfhsm/wh_client_crypto.h
@@ -53,6 +53,9 @@
#include "wolfssl/wolfcrypt/wc_mldsa.h"
#include "wolfssl/wolfcrypt/wc_mlkem.h"
#include "wolfssl/wolfcrypt/hmac.h"
+#ifdef WOLFSSL_SHA3
+#include "wolfssl/wolfcrypt/sha3.h"
+#endif
/**
* @brief Generate random bytes
@@ -2637,6 +2640,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 74fbfd24c..f87cab564 100644
--- a/wolfhsm/wh_message_crypto.h
+++ b/wolfhsm/wh_message_crypto.h
@@ -797,8 +797,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
@@ -849,8 +853,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
@@ -875,6 +883,128 @@ 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, shared across
+ * SHA3-224/256/384/512. The partial-block buffer lives on the
+ * client and the wire only carries whole-block input. */
+typedef struct {
+ 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. Written in
+ * additive form (like the DMA assert below) because the capacity macros above
+ * subtract header sizes as unsigned values: on an undersized comm buffer they
+ * wrap to a huge value and a `capacity >= block` check would falsely pass. */
+#if defined(WOLFSSL_SHA3)
+#ifndef WOLFSSL_NOSHA3_224
+WH_UTILS_STATIC_ASSERT((uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) +
+ (uint32_t)sizeof(whMessageCrypto_Sha3Request) +
+ 144u <=
+ (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN,
+ "Comm buffer too small to fit a SHA3-224 block");
+#endif
+#ifndef WOLFSSL_NOSHA3_256
+WH_UTILS_STATIC_ASSERT((uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) +
+ (uint32_t)sizeof(whMessageCrypto_Sha3Request) +
+ 136u <=
+ (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN,
+ "Comm buffer too small to fit a SHA3-256 block");
+#endif
+#ifndef WOLFSSL_NOSHA3_384
+WH_UTILS_STATIC_ASSERT((uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) +
+ (uint32_t)sizeof(whMessageCrypto_Sha3Request) +
+ 104u <=
+ (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN,
+ "Comm buffer too small to fit a SHA3-384 block");
+#endif
+#ifndef WOLFSSL_NOSHA3_512
+WH_UTILS_STATIC_ASSERT((uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) +
+ (uint32_t)sizeof(whMessageCrypto_Sha3Request) +
+ 72u <=
+ (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN,
+ "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)
*/
@@ -1200,6 +1330,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) —