diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c
index 893b1c10b..e4b0cfd19 100644
--- a/src/wh_client_crypto.c
+++ b/src/wh_client_crypto.c
@@ -9598,6 +9598,409 @@ int wh_Client_Sha3_512FinalResponse(whClientContext* ctx, wc_Sha3* sha,
}
#endif /* !WOLFSSL_NOSHA3_512 */
+#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)
+
+#define WH_SHAKE_MAX_BLOCK_SIZE 168u
+
+typedef struct {
+ int hashType; /* WC_HASH_TYPE_SHAKE* (also algoType on the wire) */
+ uint32_t blockSize;
+ uint32_t maxInlineSz;
+ /* Only initFn is used client-side (context reset after Final). */
+ int (*initFn)(wc_Shake* sha, void* heap, int devId);
+} whShakeVariant;
+
+#ifdef WOLFSSL_SHAKE128
+static const whShakeVariant whShake128 = {
+ WC_HASH_TYPE_SHAKE128, 168u,
+ WH_MESSAGE_CRYPTO_SHAKE128_MAX_INLINE_UPDATE_SZ, wc_InitShake128};
+#endif
+#ifdef WOLFSSL_SHAKE256
+static const whShakeVariant whShake256 = {
+ WC_HASH_TYPE_SHAKE256, 136u,
+ WH_MESSAGE_CRYPTO_SHAKE256_MAX_INLINE_UPDATE_SZ, wc_InitShake256};
+#endif
+
+/* Maximum data size for a single UpdateRequest: inline wire capacity
+ * plus room left in the local partial-block buffer. */
+static uint32_t _ShakeUpdatePerCallCapacity(const wc_Shake* sha,
+ const whShakeVariant* v)
+{
+ return v->maxInlineSz + (uint32_t)(v->blockSize - 1u - sha->i);
+}
+
+static int _ShakeUpdateRequest(whClientContext* ctx, wc_Shake* sha,
+ const whShakeVariant* v, const uint8_t* in,
+ uint32_t inLen, bool* requestSent)
+{
+ int ret = 0;
+ whMessageCrypto_ShakeRequest* req = NULL;
+ uint8_t* inlineData;
+ uint8_t* dataPtr = NULL;
+ uint32_t capacity;
+ uint32_t wirePos = 0;
+ uint32_t i = 0;
+ /* Buffer for rollback if SendRequest fails */
+ uint32_t savedI;
+ uint8_t savedT[WH_SHAKE_MAX_BLOCK_SIZE];
+
+ if (ctx == NULL || sha == NULL || requestSent == NULL ||
+ (in == NULL && inLen != 0)) {
+ return WH_ERROR_BADARGS;
+ }
+ *requestSent = false;
+
+ if (sha->i >= v->blockSize) {
+ return WH_ERROR_BADARGS;
+ }
+
+ capacity = _ShakeUpdatePerCallCapacity(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_ShakeRequest*)_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;
+ req->outSz = 0;
+ 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 so the caller can retry without losing buffered input. */
+ sha->i = (uint8_t)savedI;
+ memcpy(sha->t, savedT, savedI);
+ }
+ return ret;
+}
+
+static int _ShakeUpdateResponse(whClientContext* ctx, wc_Shake* sha,
+ const whShakeVariant* v)
+{
+ uint16_t group = WH_MESSAGE_GROUP_CRYPTO;
+ uint16_t action = WH_MESSAGE_ACTION_NONE;
+ uint16_t dataSz = 0;
+ int ret = 0;
+ whMessageCrypto_ShakeResponse* 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,
+ WOLFHSM_CFG_COMM_DATA_LEN, 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 _ShakeFinalRequest(whClientContext* ctx, wc_Shake* sha,
+ const whShakeVariant* v, uint32_t outSz)
+{
+ int ret;
+ whMessageCrypto_ShakeRequest* req;
+ uint8_t* inlineData;
+ uint8_t* dataPtr;
+
+ if (ctx == NULL || sha == NULL || outSz == 0) {
+ return WH_ERROR_BADARGS;
+ }
+ if (sha->i >= v->blockSize) {
+ return WH_ERROR_BADARGS;
+ }
+ /* Requested output is too big for the response. Return NOSPACE and let
+ * the software fallback handle it, if available. */
+ if (outSz > WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ) {
+ return WH_ERROR_NOSPACE;
+ }
+
+ dataPtr = wh_CommClient_GetDataPtr(ctx->comm);
+ if (dataPtr == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ req = (whMessageCrypto_ShakeRequest*)_createCryptoRequest(
+ dataPtr, v->hashType, ctx->cryptoAffinity);
+ inlineData = (uint8_t*)(req + 1);
+
+ req->isLastBlock = 1;
+ req->inSz = sha->i;
+ req->outSz = outSz;
+ 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 _ShakeFinalResponse(whClientContext* ctx, wc_Shake* sha,
+ const whShakeVariant* v, uint8_t* out,
+ uint32_t outSz)
+{
+ uint16_t group = WH_MESSAGE_GROUP_CRYPTO;
+ uint16_t action = WH_MESSAGE_ACTION_NONE;
+ uint16_t dataSz = 0;
+ int ret;
+ whMessageCrypto_ShakeResponse* res = NULL;
+ uint8_t* dataPtr;
+ void* savedHeap;
+ int savedDevId;
+
+ if (ctx == NULL || sha == NULL || out == NULL || outSz == 0) {
+ 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,
+ WOLFHSM_CFG_COMM_DATA_LEN, dataPtr);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)&res);
+ if (ret >= 0) {
+ if (dataSz < sizeof(whMessageCrypto_GenericResponseHeader) +
+ sizeof(*res) + outSz) {
+ return WH_ERROR_ABORTED;
+ }
+ if (res->outSz != outSz) {
+ return WH_ERROR_ABORTED;
+ }
+ memcpy(out, (uint8_t*)(res + 1), outSz);
+ /* Reset state, preserving heap and devId. Also drops devCtx, as the
+ * other hash types here do. */
+ savedHeap = sha->heap;
+ savedDevId = sha->devId;
+ (void)v->initFn(sha, savedHeap, savedDevId);
+ }
+ return ret;
+}
+
+/* Snapshot of the streaming state the offload path mutates, so a fallback to
+ * software starts from exactly what the caller passed in. */
+typedef struct {
+ uint64_t s[25];
+ uint8_t t[WH_SHAKE_MAX_BLOCK_SIZE];
+ uint32_t i;
+} _ShakeSavedState;
+
+static void _ShakeSaveState(const wc_Shake* sha, _ShakeSavedState* saved)
+{
+ saved->i = sha->i;
+ memcpy(saved->s, sha->s, sizeof(saved->s));
+ memcpy(saved->t, sha->t, sizeof(saved->t));
+}
+
+static void _ShakeRestoreState(wc_Shake* sha, const _ShakeSavedState* saved)
+{
+ sha->i = (uint8_t)saved->i;
+ memcpy(sha->s, saved->s, sizeof(saved->s));
+ memcpy(sha->t, saved->t, sizeof(saved->t));
+}
+
+static int _ShakeOneshot(whClientContext* ctx, wc_Shake* sha,
+ const whShakeVariant* v, const uint8_t* in,
+ uint32_t inLen, uint8_t* out, uint32_t outSz)
+{
+ int ret = WH_ERROR_OK;
+ _ShakeSavedState saved;
+
+ /* _ShakeUpdatePerCallCapacity 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;
+ }
+ if (in == NULL && inLen != 0) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* A server without SHAKE answers NOT_COMPILED_IN, and an output too large
+ * to return is declined here; either way wolfCrypt re-runs the operation
+ * in software. Snapshot so that fallback cannot absorb any input twice. */
+ _ShakeSaveState(sha, &saved);
+
+ if (in != NULL && inLen > 0) {
+ uint32_t consumed = 0;
+ while (ret == WH_ERROR_OK && consumed < inLen) {
+ uint32_t capacity = _ShakeUpdatePerCallCapacity(sha, v);
+ uint32_t remaining = inLen - consumed;
+ uint32_t chunk = (remaining < capacity) ? remaining : capacity;
+ bool sent = false;
+
+ ret = _ShakeUpdateRequest(ctx, sha, v, in + consumed, chunk, &sent);
+ if (ret != WH_ERROR_OK) {
+ break;
+ }
+ if (sent) {
+ do {
+ ret = _ShakeUpdateResponse(ctx, sha, v);
+ } while (ret == WH_ERROR_NOTREADY);
+ if (ret != WH_ERROR_OK) {
+ break;
+ }
+ }
+ consumed += chunk;
+ }
+ }
+
+ if (ret == WH_ERROR_OK && out != NULL) {
+ ret = _ShakeFinalRequest(ctx, sha, v, outSz);
+ if (ret == WH_ERROR_OK) {
+ do {
+ ret = _ShakeFinalResponse(ctx, sha, v, out, outSz);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+
+ /* Leave sha as the caller passed it so a fallback starts clean. */
+ if (ret != WH_ERROR_OK) {
+ _ShakeRestoreState(sha, &saved);
+ }
+ return ret;
+}
+
+/* Per-variant public APIs - thin wrappers over the shared helpers. */
+#ifdef WOLFSSL_SHAKE128
+int wh_Client_Shake128(whClientContext* ctx, wc_Shake* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out, uint32_t outSz)
+{
+ return _ShakeOneshot(ctx, sha, &whShake128, in, inLen, out, outSz);
+}
+
+int wh_Client_Shake128UpdateRequest(whClientContext* ctx, wc_Shake* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _ShakeUpdateRequest(ctx, sha, &whShake128, in, inLen, requestSent);
+}
+
+int wh_Client_Shake128UpdateResponse(whClientContext* ctx, wc_Shake* sha)
+{
+ return _ShakeUpdateResponse(ctx, sha, &whShake128);
+}
+
+int wh_Client_Shake128FinalRequest(whClientContext* ctx, wc_Shake* sha,
+ uint32_t outSz)
+{
+ return _ShakeFinalRequest(ctx, sha, &whShake128, outSz);
+}
+
+int wh_Client_Shake128FinalResponse(whClientContext* ctx, wc_Shake* sha,
+ uint8_t* out, uint32_t outSz)
+{
+ return _ShakeFinalResponse(ctx, sha, &whShake128, out, outSz);
+}
+#endif /* WOLFSSL_SHAKE128 */
+
+#ifdef WOLFSSL_SHAKE256
+int wh_Client_Shake256(whClientContext* ctx, wc_Shake* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out, uint32_t outSz)
+{
+ return _ShakeOneshot(ctx, sha, &whShake256, in, inLen, out, outSz);
+}
+
+int wh_Client_Shake256UpdateRequest(whClientContext* ctx, wc_Shake* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent)
+{
+ return _ShakeUpdateRequest(ctx, sha, &whShake256, in, inLen, requestSent);
+}
+
+int wh_Client_Shake256UpdateResponse(whClientContext* ctx, wc_Shake* sha)
+{
+ return _ShakeUpdateResponse(ctx, sha, &whShake256);
+}
+
+int wh_Client_Shake256FinalRequest(whClientContext* ctx, wc_Shake* sha,
+ uint32_t outSz)
+{
+ return _ShakeFinalRequest(ctx, sha, &whShake256, outSz);
+}
+
+int wh_Client_Shake256FinalResponse(whClientContext* ctx, wc_Shake* sha,
+ uint8_t* out, uint32_t outSz)
+{
+ return _ShakeFinalResponse(ctx, sha, &whShake256, out, outSz);
+}
+#endif /* WOLFSSL_SHAKE256 */
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
+
#ifdef WOLFHSM_CFG_DMA
/* SHA3 DMA helpers - inline first block (assembled from partial buffer) plus
* whole-block DMA input. Final goes inline-only. */
diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c
index 8c0bb0118..8115790ee 100644
--- a/src/wh_client_cryptocb.c
+++ b/src/wh_client_cryptocb.c
@@ -716,6 +716,62 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx)
}
} break;
#endif /* WOLFSSL_SHA3 */
+#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)
+#ifdef WOLFSSL_SHAKE128
+ case WC_HASH_TYPE_SHAKE128:
+#endif
+#ifdef WOLFSSL_SHAKE256
+ case WC_HASH_TYPE_SHAKE256:
+#endif
+ {
+ /* SHAKE output length the caller chooses, so outSz is
+ * meaningful only on a finalize. Digest set to NULL means
+ * update, non-NULL means finalize. */
+ wc_Shake* sha = info->hash.sha3;
+#ifdef WOLFSSL_HASH_FLAGS
+ /* Keccak mode swaps SHAKE256's 0x1f padding for 0x01, and the
+ * flag is not carried on the wire, so the server would produce
+ * different output. Fall through to the software path. */
+ if (sha != NULL &&
+ (sha->flags & WC_HASH_SHA3_KECCAK256) != 0u) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ break;
+ }
+#endif
+
+ /* wolfCrypt accepts a finalize with outSz set to 0: it
+ * produces nothing and only resets the context, so there is
+ * nothing worth a round trip. Decline so the software path
+ * keeps that behaviour rather than turning it into an error. */
+ if (info->hash.digest != NULL && info->hash.outSz == 0) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ break;
+ }
+
+ switch (info->hash.type) {
+#ifdef WOLFSSL_SHAKE128
+ case WC_HASH_TYPE_SHAKE128:
+ ret = wh_Client_Shake128(ctx, sha, info->hash.in,
+ info->hash.inSz,
+ info->hash.digest,
+ info->hash.outSz);
+ break;
+#endif
+#ifdef WOLFSSL_SHAKE256
+ case WC_HASH_TYPE_SHAKE256:
+ ret = wh_Client_Shake256(ctx, sha, info->hash.in,
+ info->hash.inSz,
+ info->hash.digest,
+ info->hash.outSz);
+ break;
+#endif
+ }
+ /* Requested output size is too big, surface error. */
+ if (ret == WH_ERROR_NOSPACE) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ }
+ } break;
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
default:
ret = CRYPTOCB_UNAVAILABLE;
break;
diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c
index 45579b7be..d9336f944 100644
--- a/src/wh_message_crypto.c
+++ b/src/wh_message_crypto.c
@@ -784,6 +784,35 @@ int wh_MessageCrypto_TranslateSha3Response(
return 0;
}
+/* SHAKE Request translation. The input and output data follows these structs
+ * and are byte arrays, so neither translation touches them. */
+int wh_MessageCrypto_TranslateShakeRequest(
+ uint16_t magic, const whMessageCrypto_ShakeRequest* src,
+ whMessageCrypto_ShakeRequest* dest)
+{
+ if ((src == NULL) || (dest == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+ WH_T32(magic, dest, src, isLastBlock);
+ WH_T32(magic, dest, src, inSz);
+ WH_T32(magic, dest, src, outSz);
+ return wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState,
+ &dest->resumeState);
+}
+
+/* SHAKE Response translation */
+int wh_MessageCrypto_TranslateShakeResponse(
+ uint16_t magic, const whMessageCrypto_ShakeResponse* src,
+ whMessageCrypto_ShakeResponse* dest)
+{
+ if ((src == NULL) || (dest == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+ WH_T32(magic, dest, src, outSz);
+ return wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState,
+ &dest->resumeState);
+}
+
/* CMAC-AES State translation */
int wh_MessageCrypto_TranslateCmacAesState(
diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c
index 23e3b703c..3648de8cf 100644
--- a/src/wh_server_crypto.c
+++ b/src/wh_server_crypto.c
@@ -5155,6 +5155,140 @@ static int _HandleSha3(whServerContext* ctx, int hashType, uint16_t magic,
}
#endif /* WOLFSSL_SHA3 */
+#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)
+/* SHAKE server handler. Mirrors _HandleSha3 above, with the output length
+ * coming from the request and the result trailing the response rather than
+ * sitting in a fixed digest field. */
+typedef struct {
+ uint32_t blockSize;
+ int (*initFn)(wc_Shake* sha, void* heap, int devId);
+ int (*updateFn)(wc_Shake* sha, const byte* data, word32 len);
+ int (*finalFn)(wc_Shake* sha, byte* out, word32 outLen);
+ void (*freeFn)(wc_Shake* sha);
+} _ShakeVariantOps;
+
+static int _ShakeLookupOps(int hashType, _ShakeVariantOps* ops)
+{
+ switch (hashType) {
+#ifdef WOLFSSL_SHAKE128
+ case WC_HASH_TYPE_SHAKE128:
+ ops->blockSize = WC_SHA3_128_COUNT * 8u;
+ ops->initFn = wc_InitShake128;
+ ops->updateFn = wc_Shake128_Update;
+ ops->finalFn = wc_Shake128_Final;
+ ops->freeFn = wc_Shake128_Free;
+ return 0;
+#endif
+#ifdef WOLFSSL_SHAKE256
+ case WC_HASH_TYPE_SHAKE256:
+ ops->blockSize = WC_SHA3_256_COUNT * 8u;
+ ops->initFn = wc_InitShake256;
+ ops->updateFn = wc_Shake256_Update;
+ ops->finalFn = wc_Shake256_Final;
+ ops->freeFn = wc_Shake256_Free;
+ return 0;
+#endif
+ default:
+ return WH_ERROR_BADARGS;
+ }
+}
+
+static int _HandleShake(whServerContext* ctx, int hashType, uint16_t magic,
+ int devId, const void* cryptoDataIn, uint16_t inSize,
+ void* cryptoDataOut, uint16_t* outSize)
+{
+ int ret = 0;
+ wc_Shake shake[1];
+ whMessageCrypto_ShakeRequest req;
+ whMessageCrypto_ShakeResponse res = {0};
+ const uint8_t* inData;
+ uint8_t* outData;
+ _ShakeVariantOps ops;
+
+ (void)ctx;
+
+ ret = _ShakeLookupOps(hashType, &ops);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (inSize < sizeof(whMessageCrypto_ShakeRequest)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ ret = wh_MessageCrypto_TranslateShakeRequest(magic, cryptoDataIn, &req);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ((uint32_t)req.inSz >
+ (uint32_t)(inSize - sizeof(whMessageCrypto_ShakeRequest))) {
+ 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;
+ }
+ /* A SHAKE produces whatever was asked for, bounded by what fits back */
+ if (req.isLastBlock) {
+ if ((req.outSz == 0) ||
+ (req.outSz > WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ)) {
+ return WH_ERROR_BADARGS;
+ }
+ }
+
+ inData = (const uint8_t*)cryptoDataIn +
+ sizeof(whMessageCrypto_ShakeRequest);
+ outData =
+ (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_ShakeResponse);
+
+ ret = ops.initFn(shake, NULL, devId);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Restore intermediate state from the client; the server is stateless
+ * otherwise and the partial block lives only on the client. */
+ memcpy(shake->s, req.resumeState.s, sizeof(shake->s));
+
+ if (req.inSz > 0) {
+ ret = ops.updateFn(shake, inData, req.inSz);
+ }
+ if (ret == 0) {
+ if (req.isLastBlock) {
+ ret = ops.finalFn(shake, outData, req.outSz);
+ if (ret == 0) {
+ res.outSz = req.outSz;
+ }
+ }
+ else {
+ /* Post-condition: whole-block input must leave i == 0. */
+ if (shake->i != 0) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else {
+ res.outSz = 0;
+ memcpy(res.resumeState.s, shake->s, sizeof(res.resumeState.s));
+ }
+ }
+ }
+
+ ops.freeFn(shake);
+
+ if (ret == 0) {
+ ret = wh_MessageCrypto_TranslateShakeResponse(magic, &res,
+ cryptoDataOut);
+ if (ret == 0) {
+ *outSize = (uint16_t)(sizeof(res) + res.outSz);
+ }
+ }
+
+ return ret;
+}
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
+
#ifdef WOLFSSL_HAVE_MLDSA
#ifndef WOLFSSL_MLDSA_NO_MAKE_KEY
@@ -6289,6 +6423,23 @@ int wh_Server_HandleCryptoRequest(whServerContext* ctx, uint16_t magic,
}
break;
#endif /* WOLFSSL_SHA3 */
+#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)
+#ifdef WOLFSSL_SHAKE128
+ case WC_HASH_TYPE_SHAKE128:
+#endif
+#ifdef WOLFSSL_SHAKE256
+ case WC_HASH_TYPE_SHAKE256:
+#endif
+ WH_DEBUG_SERVER("SHAKE req recv. type:%u\n",
+ rqstHeader.algoType);
+ ret = _HandleShake(ctx, rqstHeader.algoType, magic, devId,
+ cryptoDataIn, cryptoInSize,
+ cryptoDataOut, &cryptoOutSize);
+ if (ret != 0) {
+ WH_DEBUG_SERVER("SHAKE ret = %d\n", ret);
+ }
+ break;
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
default:
ret = NOT_COMPILED_IN;
break;
diff --git a/test-refactor/client-server/wh_test_crypto_shake.c b/test-refactor/client-server/wh_test_crypto_shake.c
new file mode 100644
index 000000000..51f97467b
--- /dev/null
+++ b/test-refactor/client-server/wh_test_crypto_shake.c
@@ -0,0 +1,487 @@
+/*
+ * 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_shake.c
+ *
+ * SHAKE128/256 routed through the server via the per-client devId.
+ *
+ * Every case runs the same input twice, once with INVALID_DEVID so wolfCrypt
+ * computes it locally and once on the server, and requires the two to agree.
+ * The software path is the oracle: what is under test is the offload, not
+ * Keccak. The size tables are written out here rather than shared with the
+ * client, so a wrong block size cannot agree with itself.
+ */
+
+#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 "wolfssl/wolfcrypt/error-crypt.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_SHAKE128) || defined(WOLFSSL_SHAKE256)
+
+/* Long enough to span several comm-buffer messages at any supported size */
+#define SHAKE_TEST_MAX_IN 20000u
+/* Larger than any response can carry, so a SHAKE this long must fall back to
+ * software rather than be truncated */
+#define SHAKE_TEST_LONG_OUT (WOLFHSM_CFG_COMM_DATA_LEN + 1024u)
+
+static uint8_t shakeTestIn[SHAKE_TEST_MAX_IN];
+static uint8_t shakeTestOutDev[SHAKE_TEST_LONG_OUT];
+static uint8_t shakeTestOutSw[SHAKE_TEST_LONG_OUT];
+
+typedef struct {
+ int hashType;
+ uint32_t blockSize;
+ const char* name;
+ int (*initFn)(wc_Shake* sha, void* heap, int devId);
+ int (*updateFn)(wc_Shake* sha, const byte* in, word32 inSz);
+ int (*finalFn)(wc_Shake* sha, byte* out, word32 outSz);
+ void (*freeFn)(wc_Shake* sha);
+} shakeTestVariant;
+
+static const shakeTestVariant shakeTestVariants[] = {
+#ifdef WOLFSSL_SHAKE128
+ {WC_HASH_TYPE_SHAKE128, 168u, "SHAKE128", wc_InitShake128,
+ wc_Shake128_Update, wc_Shake128_Final, wc_Shake128_Free},
+#endif
+#ifdef WOLFSSL_SHAKE256
+ {WC_HASH_TYPE_SHAKE256, 136u, "SHAKE256", wc_InitShake256,
+ wc_Shake256_Update, wc_Shake256_Final, wc_Shake256_Free},
+#endif
+};
+
+/* Hash inLen bytes, feeding the update in chunks of chunkSz (0 = all at once)
+ * so the multi-update path and the partial-block buffering are exercised. */
+static int _ShakeTestHash(int devId, const shakeTestVariant* v,
+ const uint8_t* in, uint32_t inLen, uint32_t chunkSz,
+ uint8_t* out, uint32_t outSz)
+{
+ wc_Shake sha[1];
+ int ret;
+ uint32_t done = 0;
+
+ ret = v->initFn(sha, NULL, devId);
+ if (ret != 0) {
+ return ret;
+ }
+
+ while ((ret == 0) && (done < inLen)) {
+ uint32_t remaining = inLen - done;
+ uint32_t chunk = (chunkSz == 0) ? remaining : chunkSz;
+
+ if (chunk > remaining) {
+ chunk = remaining;
+ }
+ ret = v->updateFn(sha, in + done, chunk);
+ done += chunk;
+ }
+
+ if (ret == 0) {
+ ret = v->finalFn(sha, out, outSz);
+ }
+
+ v->freeFn(sha);
+ return ret;
+}
+
+/* Run one case on both paths and require them to agree. */
+static int _ShakeTestCompare(int devId, const shakeTestVariant* v,
+ uint32_t inLen, uint32_t chunkSz, uint32_t outSz)
+{
+ int ret;
+
+ memset(shakeTestOutDev, 0, outSz);
+ memset(shakeTestOutSw, 0xA5, outSz);
+
+ ret = _ShakeTestHash(INVALID_DEVID, v, shakeTestIn, inLen, chunkSz,
+ shakeTestOutSw, outSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s software hash failed: %d\n", v->name, ret);
+ return ret;
+ }
+
+ ret = _ShakeTestHash(devId, v, shakeTestIn, inLen, chunkSz, shakeTestOutDev,
+ outSz);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s device hash failed (in %u chunk %u out %u): %d\n",
+ v->name, (unsigned)inLen, (unsigned)chunkSz,
+ (unsigned)outSz, ret);
+ return ret;
+ }
+
+ if (memcmp(shakeTestOutDev, shakeTestOutSw, outSz) != 0) {
+ WH_ERROR_PRINT("%s device and software differ (in %u chunk %u "
+ "out %u)\n",
+ v->name, (unsigned)inLen, (unsigned)chunkSz,
+ (unsigned)outSz);
+ return WH_ERROR_ABORTED;
+ }
+ return WH_ERROR_OK;
+}
+
+static int _ShakeTestVariant(whClientContext* ctx, const shakeTestVariant* v)
+{
+ int devId = WH_CLIENT_DEVID(ctx);
+ uint32_t rate = v->blockSize;
+ uint32_t i;
+ uint32_t j;
+ int ret = WH_ERROR_OK;
+ /* Sizes around the block boundary, plus one long enough to need several
+ * messages */
+ const uint32_t inLens[] = {0u, 1u, rate - 1u,
+ rate, rate + 1u, 2u * rate,
+ 2u * rate + 7u, SHAKE_TEST_MAX_IN};
+ /* All at once, then patterns that leave partial blocks buffered */
+ const uint32_t chunks[] = {0u, 1u, 7u, rate, rate + 1u};
+ /* Output lengths a SHAKE caller might pick, including ones that are not
+ * multiples of the block */
+ const uint32_t outSzs[] = {1u, 32u, 64u, rate, rate + 5u, 3u * rate};
+ const uint32_t inLenCnt = sizeof(inLens) / sizeof(inLens[0]);
+ const uint32_t chunkCnt = sizeof(chunks) / sizeof(chunks[0]);
+ const uint32_t outSzCnt = sizeof(outSzs) / sizeof(outSzs[0]);
+
+ for (i = 0; (ret == WH_ERROR_OK) && (i < inLenCnt); i++) {
+ for (j = 0; (ret == WH_ERROR_OK) && (j < chunkCnt); j++) {
+ /* Chunking a 20000-byte input one byte at a time is a lot of
+ * round trips for no extra coverage; the smaller inputs above
+ * already exercise the same path */
+ if ((inLens[i] > 4u * rate) && (chunks[j] != 0u) &&
+ (chunks[j] < rate)) {
+ continue;
+ }
+ ret = _ShakeTestCompare(devId, v, inLens[i], chunks[j], 32u);
+ }
+ }
+
+ /* Output length is the part SHA3 has no equivalent of, so sweep it */
+ for (i = 0; (ret == WH_ERROR_OK) && (i < outSzCnt); i++) {
+ ret = _ShakeTestCompare(devId, v, 2u * rate + 7u, 0u, outSzs[i]);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ WH_TEST_PRINT("%s DEVID=0x%X SUCCESS\n", v->name, devId);
+ }
+ return ret;
+}
+
+/* A SHAKE asked for more output than a response can carry must still produce
+ * the right answer, by declining the offload and letting software finish from
+ * the state the client holds. */
+static int _ShakeTestLongOutput(whClientContext* ctx, const shakeTestVariant* v)
+{
+ int devId = WH_CLIENT_DEVID(ctx);
+ int ret;
+
+ ret = _ShakeTestCompare(devId, v, 4096u, 0u, SHAKE_TEST_LONG_OUT);
+ if (ret == WH_ERROR_OK) {
+ WH_TEST_PRINT("%s long output DEVID=0x%X SUCCESS\n", v->name, devId);
+ }
+ return ret;
+}
+
+/* Exercise the request/response primitives directly, the way the async SHA3
+ * tests do, rather than only through the wolfCrypt API. */
+static int _ShakeTestAsync(whClientContext* ctx, const shakeTestVariant* v)
+{
+ int devId = WH_CLIENT_DEVID(ctx);
+ int ret;
+ wc_Shake sha[1];
+ uint8_t out[64];
+ uint32_t inLen = 3u * v->blockSize + 11u;
+ uint32_t consumed = 0;
+
+ ret = _ShakeTestHash(INVALID_DEVID, v, shakeTestIn, inLen, 0u,
+ shakeTestOutSw, sizeof(out));
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = v->initFn(sha, NULL, devId);
+ if (ret != 0) {
+ return ret;
+ }
+
+ while ((ret == WH_ERROR_OK) && (consumed < inLen)) {
+ uint32_t remaining = inLen - consumed;
+ uint32_t chunk = (remaining < v->blockSize) ? remaining
+ : v->blockSize;
+ bool sent = false;
+
+#ifdef WOLFSSL_SHAKE128
+ if (v->hashType == WC_HASH_TYPE_SHAKE128) {
+ ret = wh_Client_Shake128UpdateRequest(ctx, sha,
+ shakeTestIn + consumed,
+ chunk, &sent);
+ if ((ret == WH_ERROR_OK) && sent) {
+ do {
+ ret = wh_Client_Shake128UpdateResponse(ctx, sha);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+#endif
+#ifdef WOLFSSL_SHAKE256
+ if (v->hashType == WC_HASH_TYPE_SHAKE256) {
+ ret = wh_Client_Shake256UpdateRequest(ctx, sha,
+ shakeTestIn + consumed,
+ chunk, &sent);
+ if ((ret == WH_ERROR_OK) && sent) {
+ do {
+ ret = wh_Client_Shake256UpdateResponse(ctx, sha);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+#endif
+ consumed += chunk;
+ }
+
+ if (ret == WH_ERROR_OK) {
+#ifdef WOLFSSL_SHAKE128
+ if (v->hashType == WC_HASH_TYPE_SHAKE128) {
+ ret = wh_Client_Shake128FinalRequest(ctx, sha, sizeof(out));
+ if (ret == WH_ERROR_OK) {
+ do {
+ ret = wh_Client_Shake128FinalResponse(ctx, sha, out,
+ sizeof(out));
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+#endif
+#ifdef WOLFSSL_SHAKE256
+ if (v->hashType == WC_HASH_TYPE_SHAKE256) {
+ ret = wh_Client_Shake256FinalRequest(ctx, sha, sizeof(out));
+ if (ret == WH_ERROR_OK) {
+ do {
+ ret = wh_Client_Shake256FinalResponse(ctx, sha, out,
+ sizeof(out));
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ }
+#endif
+ }
+
+ v->freeFn(sha);
+
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s async failed: %d\n", v->name, ret);
+ return ret;
+ }
+ if (memcmp(out, shakeTestOutSw, sizeof(out)) != 0) {
+ WH_ERROR_PRINT("%s async result differs from software\n", v->name);
+ return WH_ERROR_ABORTED;
+ }
+
+ WH_TEST_PRINT("%s ASYNC DEVID=0x%X SUCCESS\n", v->name, devId);
+ return WH_ERROR_OK;
+}
+
+/* wolfCrypt accepts a finalize asking for zero bytes: it writes nothing and
+ * resets the context. Enabling the offload must not turn that into an error. */
+static int _ShakeTestZeroLengthFinal(whClientContext* ctx,
+ const shakeTestVariant* v)
+{
+ int devId = WH_CLIENT_DEVID(ctx);
+ int ret;
+ wc_Shake sha[1];
+ uint8_t out[1] = {0xA5};
+
+ ret = v->initFn(sha, NULL, devId);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = v->updateFn(sha, shakeTestIn, v->blockSize + 3u);
+ if (ret == 0) {
+ ret = v->finalFn(sha, out, 0u);
+ }
+ if (ret == 0 && out[0] != 0xA5) {
+ WH_ERROR_PRINT("%s zero-length final wrote output\n", v->name);
+ ret = WH_ERROR_ABORTED;
+ }
+ /* The context must be reusable afterwards, as a reset implies */
+ if (ret == 0) {
+ uint8_t again[32];
+ ret = v->updateFn(sha, shakeTestIn, 4u);
+ if (ret == 0) {
+ ret = v->finalFn(sha, again, sizeof(again));
+ }
+ }
+
+ v->freeFn(sha);
+
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s zero-length final failed: %d\n", v->name, ret);
+ return ret;
+ }
+ WH_TEST_PRINT("%s zero-length final SUCCESS\n", v->name);
+ return WH_ERROR_OK;
+}
+
+#ifdef WOLFSSL_HASH_FLAGS
+/* Keccak mode swaps SHAKE256's padding and the flag is not carried on the
+ * wire, so the offload must decline and leave the result matching software. */
+static int _ShakeTestKeccakFlag(whClientContext* ctx,
+ const shakeTestVariant* v)
+{
+ wc_Shake sha[1];
+ uint8_t dev[32];
+ uint8_t sw[32];
+ int ret;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ int devId = (i == 0) ? WH_CLIENT_DEVID(ctx) : INVALID_DEVID;
+ uint8_t* out = (i == 0) ? dev : sw;
+
+ ret = v->initFn(sha, NULL, devId);
+ if (ret == 0) {
+ ret = wc_Sha3_SetFlags(sha, WC_HASH_SHA3_KECCAK256);
+ }
+ if (ret == 0) {
+ ret = v->updateFn(sha, shakeTestIn, v->blockSize + 3u);
+ }
+ if (ret == 0) {
+ ret = v->finalFn(sha, out, sizeof(dev));
+ }
+ v->freeFn(sha);
+ if (ret != 0) {
+ WH_ERROR_PRINT("%s keccak-flag hash failed (devId %d): %d\n",
+ v->name, devId, ret);
+ return ret;
+ }
+ }
+
+ if (memcmp(dev, sw, sizeof(dev)) != 0) {
+ WH_ERROR_PRINT("%s keccak-flag device and software differ\n", v->name);
+ return WH_ERROR_ABORTED;
+ }
+ WH_TEST_PRINT("%s keccak flag SUCCESS\n", v->name);
+ return WH_ERROR_OK;
+}
+#endif /* WOLFSSL_HASH_FLAGS */
+
+/* The client entry points must reject bad arguments before going on the wire */
+static int _ShakeTestBadArgs(whClientContext* ctx, const shakeTestVariant* v)
+{
+ wc_Shake sha[1];
+ uint8_t buf[8];
+ int bad = 0;
+
+ if (v->initFn(sha, NULL, WH_CLIENT_DEVID(ctx)) != 0) {
+ return WH_ERROR_ABORTED;
+ }
+
+#ifdef WOLFSSL_SHAKE128
+ if (v->hashType == WC_HASH_TYPE_SHAKE128) {
+ bad = (wh_Client_Shake128(NULL, sha, buf, sizeof(buf), NULL, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake128(ctx, NULL, buf, sizeof(buf), NULL, 0) !=
+ WH_ERROR_BADARGS) ||
+ /* A length with no buffer behind it would digest the state */
+ (wh_Client_Shake128(ctx, sha, NULL, sizeof(buf), NULL, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake128UpdateRequest(ctx, sha, buf, sizeof(buf),
+ NULL) != WH_ERROR_BADARGS) ||
+ (wh_Client_Shake128UpdateResponse(ctx, NULL) !=
+ WH_ERROR_BADARGS) ||
+ /* A SHAKE has no natural length, so finalizing needs one */
+ (wh_Client_Shake128FinalRequest(ctx, sha, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake128FinalResponse(ctx, sha, NULL, 32u) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake128FinalResponse(ctx, sha, buf, 0) !=
+ WH_ERROR_BADARGS);
+ }
+#endif
+#ifdef WOLFSSL_SHAKE256
+ if (v->hashType == WC_HASH_TYPE_SHAKE256) {
+ bad = (wh_Client_Shake256(NULL, sha, buf, sizeof(buf), NULL, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256(ctx, NULL, buf, sizeof(buf), NULL, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256(ctx, sha, NULL, sizeof(buf), NULL, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256UpdateRequest(ctx, sha, buf, sizeof(buf),
+ NULL) != WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256UpdateResponse(ctx, NULL) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256FinalRequest(ctx, sha, 0) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256FinalResponse(ctx, sha, NULL, 32u) !=
+ WH_ERROR_BADARGS) ||
+ (wh_Client_Shake256FinalResponse(ctx, sha, buf, 0) !=
+ WH_ERROR_BADARGS);
+ }
+#endif
+
+ v->freeFn(sha);
+
+ if (bad) {
+ WH_ERROR_PRINT("%s accepted bad arguments\n", v->name);
+ return WH_ERROR_ABORTED;
+ }
+ WH_TEST_PRINT("%s bad-args SUCCESS\n", v->name);
+ return WH_ERROR_OK;
+}
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
+
+int whTest_Crypto_Shake(whClientContext* ctx)
+{
+#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)
+ const uint32_t variantCnt =
+ sizeof(shakeTestVariants) / sizeof(shakeTestVariants[0]);
+ uint32_t i;
+
+ for (i = 0; i < sizeof(shakeTestIn); i++) {
+ shakeTestIn[i] = (uint8_t)(i * 31u + 7u);
+ }
+
+ for (i = 0; i < variantCnt; i++) {
+ const shakeTestVariant* v = &shakeTestVariants[i];
+
+ WH_TEST_RETURN_ON_FAIL(_ShakeTestBadArgs(ctx, v));
+ WH_TEST_RETURN_ON_FAIL(_ShakeTestVariant(ctx, v));
+ WH_TEST_RETURN_ON_FAIL(_ShakeTestAsync(ctx, v));
+ WH_TEST_RETURN_ON_FAIL(_ShakeTestLongOutput(ctx, v));
+ WH_TEST_RETURN_ON_FAIL(_ShakeTestZeroLengthFinal(ctx, v));
+#ifdef WOLFSSL_HASH_FLAGS
+ WH_TEST_RETURN_ON_FAIL(_ShakeTestKeccakFlag(ctx, v));
+#endif
+ }
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
+ (void)ctx;
+ return 0;
+}
+
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile
index 4852c9c57..e6e5ef88b 100644
--- a/test-refactor/posix/Makefile
+++ b/test-refactor/posix/Makefile
@@ -44,6 +44,22 @@ DEF += -DWOLFHSM_CFG_TEST_POSIX
DEF += -DWOLFHSM_CFG_ENABLE_CLIENT
DEF += -DWOLFHSM_CFG_ENABLE_SERVER
+# SHAKE build variants. Each variant is gated on its own so a helper that
+# assumes the other one exists fails to compile rather than silently working
+# in the build where both are enabled.
+ifeq ($(NO_SHAKE),1)
+ DEF += -DWOLFHSM_CFG_TEST_NO_SHAKE128 -DWOLFHSM_CFG_TEST_NO_SHAKE256
+endif
+ifeq ($(SHAKE128_ONLY),1)
+ DEF += -DWOLFHSM_CFG_TEST_NO_SHAKE256
+endif
+ifeq ($(SHAKE256_ONLY),1)
+ DEF += -DWOLFHSM_CFG_TEST_NO_SHAKE128
+endif
+ifeq ($(NO_SHA3_512),1)
+ DEF += -DWOLFHSM_CFG_TEST_NO_SHA3_512
+endif
+
# C standard
CSTD ?= -std=c90
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index 27465d87c..f6b5410a5 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -76,6 +76,7 @@ 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_Shake);
WH_TEST_DECL(whTest_Crypto_Xmss);
WH_TEST_DECL(whTest_CryptoEcc256);
WH_TEST_DECL(whTest_CryptoEd25519BufferTooSmall);
@@ -159,6 +160,7 @@ const whTestCase whTestsClient[] = {
{"whTest_Crypto_Rsa", whTest_Crypto_Rsa},
{"whTest_Crypto_Sha", whTest_Crypto_Sha},
{"whTest_Crypto_Sha3", whTest_Crypto_Sha3},
+ {"whTest_Crypto_Shake", whTest_Crypto_Shake},
{"whTest_Crypto_Xmss", whTest_Crypto_Xmss},
{"whTest_CryptoEcc256", whTest_CryptoEcc256},
{"whTest_CryptoEd25519BufferTooSmall", whTest_CryptoEd25519BufferTooSmall},
diff --git a/test/config/user_settings.h b/test/config/user_settings.h
index f237ccaa4..273641eee 100644
--- a/test/config/user_settings.h
+++ b/test/config/user_settings.h
@@ -132,17 +132,36 @@
#define WOLFSSL_SHA512
#define WOLFSSL_SHA512_HASHTYPE
-/* ML-DSA Options */
-#define WOLFSSL_HAVE_MLDSA
#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
+/* SHAKE Options. The two variants are gated separately so each can be built
+ * on its own, which is what catches a helper that assumes the other exists.
+ * ML-DSA and ML-KEM need both, so they follow. */
+#ifndef WOLFHSM_CFG_TEST_NO_SHAKE128
+#define WOLFSSL_SHAKE128
+#endif
+#ifndef WOLFHSM_CFG_TEST_NO_SHAKE256
+#define WOLFSSL_SHAKE256
+#endif
+/* Drop the largest SHA3 variant, so nothing else may depend on its guard. */
+#ifdef WOLFHSM_CFG_TEST_NO_SHA3_512
+#define WOLFSSL_NOSHA3_512
+#endif
+
+/* ML-DSA and ML-KEM need both SHAKE variants, and ML-KEM hashes with SHA3-512,
+ * so they follow whichever of those is dropped. */
+#if !defined(WOLFHSM_CFG_TEST_NO_SHAKE128) && \
+ !defined(WOLFHSM_CFG_TEST_NO_SHAKE256) && \
+ !defined(WOLFHSM_CFG_TEST_NO_SHA3_512)
+/* ML-DSA Options */
+#define WOLFSSL_HAVE_MLDSA
+
/* ML-KEM Options */
#define WOLFSSL_HAVE_MLKEM
+#endif
/* LMS / HSS Options (RFC 8554, NIST SP 800-208) */
#define WOLFSSL_HAVE_LMS
diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h
index 9a0e921c6..b234be8da 100644
--- a/wolfhsm/wh_client_crypto.h
+++ b/wolfhsm/wh_client_crypto.h
@@ -2979,6 +2979,37 @@ int wh_Client_Sha3_512DmaFinalResponse(whClientContext* ctx, wc_Sha3* sha,
#endif /* WOLFHSM_CFG_DMA */
#endif /* !WOLFSSL_NOSHA3_512 */
+#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)
+/* SHAKE offload. Mirrors the SHA3 entry points above, with the caller's
+ * chosen output length carried through: a SHAKE has no natural digest size.
+ * A length larger than a response can carry returns WH_ERROR_NOSPACE so the
+ * caller can finish in software from the state it still holds. */
+#ifdef WOLFSSL_SHAKE128
+int wh_Client_Shake128(whClientContext* ctx, wc_Shake* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out, uint32_t outSz);
+int wh_Client_Shake128UpdateRequest(whClientContext* ctx, wc_Shake* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent);
+int wh_Client_Shake128UpdateResponse(whClientContext* ctx, wc_Shake* sha);
+int wh_Client_Shake128FinalRequest(whClientContext* ctx, wc_Shake* sha,
+ uint32_t outSz);
+int wh_Client_Shake128FinalResponse(whClientContext* ctx, wc_Shake* sha,
+ uint8_t* out, uint32_t outSz);
+#endif /* WOLFSSL_SHAKE128 */
+#ifdef WOLFSSL_SHAKE256
+int wh_Client_Shake256(whClientContext* ctx, wc_Shake* sha, const uint8_t* in,
+ uint32_t inLen, uint8_t* out, uint32_t outSz);
+int wh_Client_Shake256UpdateRequest(whClientContext* ctx, wc_Shake* sha,
+ const uint8_t* in, uint32_t inLen,
+ bool* requestSent);
+int wh_Client_Shake256UpdateResponse(whClientContext* ctx, wc_Shake* sha);
+int wh_Client_Shake256FinalRequest(whClientContext* ctx, wc_Shake* sha,
+ uint32_t outSz);
+int wh_Client_Shake256FinalResponse(whClientContext* ctx, wc_Shake* sha,
+ uint8_t* out, uint32_t outSz);
+#endif /* WOLFSSL_SHAKE256 */
+#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */
+
#endif /* WOLFSSL_SHA3 */
#ifdef WOLFSSL_HAVE_MLDSA
diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h
index 809513bc9..c84f84339 100644
--- a/wolfhsm/wh_message_crypto.h
+++ b/wolfhsm/wh_message_crypto.h
@@ -1036,6 +1036,108 @@ int wh_MessageCrypto_TranslateSha3Response(
whMessageCrypto_Sha3Response* dest);
+/*
+ * SHAKE
+ *
+ * SHAKE reuses the Keccak state above but needs its own messages: the caller
+ * chooses how much output it wants, so the length has to travel with the
+ * request and the output cannot sit in a fixed field the way a digest does.
+ * The SHA3 messages are a released wire format and are left alone.
+ */
+
+/* SHAKE Request (variable-length input data follows the struct).
+ *
+ * Wire layout in the comm buffer:
+ * whMessageCrypto_GenericRequestHeader
+ * whMessageCrypto_ShakeRequest
+ * uint8_t in[inSz]
+ *
+ * Non-final updates: inSz must be a multiple of the variant's block size
+ * (168 for SHAKE128, 136 for SHAKE256). The client buffers any partial-block
+ * tail locally in sha->t[] and only sends it on Final with isLastBlock=1.
+ */
+typedef struct {
+ uint32_t isLastBlock;
+ uint32_t inSz;
+ /* Bytes of output wanted; ignored when isLastBlock is 0. A SHAKE has no
+ * natural digest length, so there is nothing to infer this from. */
+ uint32_t outSz;
+ uint8_t WH_PAD[4];
+ whMessageCrypto_Sha3State resumeState;
+} whMessageCrypto_ShakeRequest;
+
+/* SHAKE Response.
+ *
+ * Wire layout in the comm buffer:
+ * whMessageCrypto_GenericResponseHeader
+ * whMessageCrypto_ShakeResponse
+ * uint8_t out[outSz] (finalize only; outSz is 0 on an update)
+ *
+ * On a non-final update the state carries the sponge to resume from and no
+ * output follows. Sized to match the request so the outgoing data starts
+ * where the incoming data did. */
+typedef struct {
+ whMessageCrypto_Sha3State resumeState;
+ uint32_t outSz;
+ uint8_t WH_PAD[12];
+} whMessageCrypto_ShakeResponse;
+
+WH_UTILS_STATIC_ASSERT(sizeof(whMessageCrypto_ShakeResponse) ==
+ sizeof(whMessageCrypto_ShakeRequest),
+ "ShakeRequest and ShakeResponse must be the same size");
+
+/* Per-variant max-inline update sizes, rounded down to a whole-block
+ * multiple, as the SHA3 macros above are. */
+#define WH_MESSAGE_CRYPTO_SHAKE128_MAX_INLINE_UPDATE_SZ \
+ (((WOLFHSM_CFG_COMM_DATA_LEN - \
+ (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) - \
+ (uint32_t)sizeof(whMessageCrypto_ShakeRequest)) / \
+ 168u) * \
+ 168u)
+
+#define WH_MESSAGE_CRYPTO_SHAKE256_MAX_INLINE_UPDATE_SZ \
+ (((WOLFHSM_CFG_COMM_DATA_LEN - \
+ (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) - \
+ (uint32_t)sizeof(whMessageCrypto_ShakeRequest)) / \
+ 136u) * \
+ 136u)
+
+/* Most output a single response can carry. A SHAKE asked for more than this
+ * is declined so wolfCrypt produces it in software. */
+#define WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ \
+ (WOLFHSM_CFG_COMM_DATA_LEN - \
+ (uint32_t)sizeof(whMessageCrypto_GenericResponseHeader) - \
+ (uint32_t)sizeof(whMessageCrypto_ShakeResponse))
+
+/* Each enabled SHAKE variant must fit at least one block inline. Additive
+ * form for the same reason as the SHA3 asserts above: the capacity macros
+ * subtract as unsigned values and wrap on an undersized comm buffer. SHAKE128
+ * has the larger block (168) despite being the weaker variant, because a
+ * smaller capacity leaves a larger rate. */
+#ifdef WOLFSSL_SHAKE128
+WH_UTILS_STATIC_ASSERT((uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) +
+ (uint32_t)sizeof(whMessageCrypto_ShakeRequest) +
+ 168u <=
+ (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN,
+ "Comm buffer too small to fit a SHAKE128 block");
+#endif
+#ifdef WOLFSSL_SHAKE256
+WH_UTILS_STATIC_ASSERT((uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) +
+ (uint32_t)sizeof(whMessageCrypto_ShakeRequest) +
+ 136u <=
+ (uint32_t)WOLFHSM_CFG_COMM_DATA_LEN,
+ "Comm buffer too small to fit a SHAKE256 block");
+#endif
+
+int wh_MessageCrypto_TranslateShakeRequest(
+ uint16_t magic, const whMessageCrypto_ShakeRequest* src,
+ whMessageCrypto_ShakeRequest* dest);
+
+int wh_MessageCrypto_TranslateShakeResponse(
+ uint16_t magic, const whMessageCrypto_ShakeResponse* src,
+ whMessageCrypto_ShakeResponse* dest);
+
+
/*
* CMAC (AES)
*/