From efdbc4e19b1ef9769639db3e51028185970e8687 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 18 Sep 2026 13:11:44 -0700 Subject: [PATCH 1/7] Add crypto callback for SHAKE --- src/wh_client_crypto.c | 403 +++++++++++++++ src/wh_client_cryptocb.c | 56 ++ src/wh_message_crypto.c | 29 ++ src/wh_server_crypto.c | 151 ++++++ .../client-server/wh_test_crypto_shake.c | 487 ++++++++++++++++++ test-refactor/posix/Makefile | 16 + test-refactor/wh_test_list.c | 2 + test/config/user_settings.h | 27 +- wolfhsm/wh_client_crypto.h | 31 ++ wolfhsm/wh_message_crypto.h | 102 ++++ 10 files changed, 1300 insertions(+), 4 deletions(-) create mode 100644 test-refactor/client-server/wh_test_crypto_shake.c diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 22f52360f..bede2e3fe 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -9652,6 +9652,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 406bb5154..936f249fd 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 185577521..b20b03af1 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 fa2994089..1c35f7f21 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -78,6 +78,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); @@ -165,6 +166,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 3a035f9fb..561685c03 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -2985,6 +2985,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 83ca1f829..e3cbf45df 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) */ From 3071d1f36bce85e0bd04c95745e71c8da20c0424 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 21 Sep 2026 15:05:56 -0700 Subject: [PATCH 2/7] PR feedback: reject Keccak mode, zeroization, test updates --- src/wh_client_crypto.c | 13 +++++++ .../client-server/wh_test_crypto_shake.c | 35 ++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index bede2e3fe..c5624c43e 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -9704,6 +9704,11 @@ static int _ShakeUpdateRequest(whClientContext* ctx, wc_Shake* sha, } *requestSent = false; + ret = _Sha3RejectKeccak(sha); + if (ret != WH_ERROR_OK) { + return ret; + } + if (sha->i >= v->blockSize) { return WH_ERROR_BADARGS; } @@ -9756,6 +9761,7 @@ static int _ShakeUpdateRequest(whClientContext* ctx, wc_Shake* sha, /* Pure buffer-fill update: nothing to send. */ if (wirePos == 0) { + wc_ForceZero(savedT, sizeof(savedT)); return WH_ERROR_OK; } @@ -9776,6 +9782,7 @@ static int _ShakeUpdateRequest(whClientContext* ctx, wc_Shake* sha, sha->i = (uint8_t)savedI; memcpy(sha->t, savedT, savedI); } + wc_ForceZero(savedT, sizeof(savedT)); return ret; } @@ -9826,6 +9833,10 @@ static int _ShakeFinalRequest(whClientContext* ctx, wc_Shake* sha, if (ctx == NULL || sha == NULL || outSz == 0) { return WH_ERROR_BADARGS; } + ret = _Sha3RejectKeccak(sha); + if (ret != WH_ERROR_OK) { + return ret; + } if (sha->i >= v->blockSize) { return WH_ERROR_BADARGS; } @@ -9986,6 +9997,8 @@ static int _ShakeOneshot(whClientContext* ctx, wc_Shake* sha, if (ret != WH_ERROR_OK) { _ShakeRestoreState(sha, &saved); } + /* The snapshot holds sponge state and message bytes. */ + wc_ForceZero(&saved, sizeof(saved)); return ret; } diff --git a/test-refactor/client-server/wh_test_crypto_shake.c b/test-refactor/client-server/wh_test_crypto_shake.c index 51f97467b..3581449ce 100644 --- a/test-refactor/client-server/wh_test_crypto_shake.c +++ b/test-refactor/client-server/wh_test_crypto_shake.c @@ -445,6 +445,34 @@ static int _ShakeTestBadArgs(whClientContext* ctx, const shakeTestVariant* v) } #endif +#ifdef WOLFSSL_HASH_FLAGS + /* Keccak mode is not carried on the wire, so the request helpers must + * refuse it rather than return a digest with SHAKE padding. */ + if (!bad) { + bool sent = false; + (void)wc_Sha3_SetFlags(sha, WC_HASH_SHA3_KECCAK256); +#ifdef WOLFSSL_SHAKE128 + if (v->hashType == WC_HASH_TYPE_SHAKE128) { + bad = (wh_Client_Shake128UpdateRequest(ctx, sha, buf, sizeof(buf), + &sent) != + WH_ERROR_BADARGS) || + (wh_Client_Shake128FinalRequest(ctx, sha, 32u) != + WH_ERROR_BADARGS); + } +#endif +#ifdef WOLFSSL_SHAKE256 + if (v->hashType == WC_HASH_TYPE_SHAKE256) { + bad = (wh_Client_Shake256UpdateRequest(ctx, sha, buf, sizeof(buf), + &sent) != + WH_ERROR_BADARGS) || + (wh_Client_Shake256FinalRequest(ctx, sha, 32u) != + WH_ERROR_BADARGS); + } +#endif + (void)wc_Sha3_SetFlags(sha, 0); + } +#endif + v->freeFn(sha); if (bad) { @@ -454,11 +482,8 @@ static int _ShakeTestBadArgs(whClientContext* ctx, const shakeTestVariant* v) 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; @@ -479,9 +504,9 @@ int whTest_Crypto_Shake(whClientContext* ctx) WH_TEST_RETURN_ON_FAIL(_ShakeTestKeccakFlag(ctx, v)); #endif } -#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */ - (void)ctx; return 0; } +#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */ + #endif /* !WOLFHSM_CFG_NO_CRYPTO */ From afd4d7cc69f93bf17f5df53ed522ba0931143e8f Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Wed, 23 Sep 2026 10:53:34 -0700 Subject: [PATCH 3/7] Cleanup comments and a couple unrelated changes --- src/wh_client_crypto.c | 3 +-- src/wh_client_cryptocb.c | 18 ++++++++---------- src/wh_message_crypto.c | 4 ++-- src/wh_server_crypto.c | 10 +++------- test-refactor/posix/Makefile | 3 --- test/config/user_settings.h | 19 ++++--------------- 6 files changed, 18 insertions(+), 39 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index c5624c43e..f5ced9b4e 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -9908,8 +9908,7 @@ static int _ShakeFinalResponse(whClientContext* ctx, wc_Shake* sha, 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. */ + /* Reset state, preserving heap and devId, dropping devCtx. */ savedHeap = sha->heap; savedDevId = sha->devId; (void)v->initFn(sha, savedHeap, savedDevId); diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 936f249fd..1bdebbd45 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -724,14 +724,13 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) 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. */ + /* Caller chooses SHAKE output length, so outSz is + * relevant 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. */ + /* Keccak-mode (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; @@ -739,10 +738,9 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) } #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. */ + /* wolfCrypt accepts a finalize with outSz=0, but only the + * context is updated. Decline it and let the software path + * do the work. */ if (info->hash.digest != NULL && info->hash.outSz == 0) { ret = CRYPTOCB_UNAVAILABLE; break; diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index d9336f944..cd2e2009e 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -784,8 +784,8 @@ 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. */ +/* SHAKE Request translation. Trailing input bytes are raw and need no + * translation. */ int wh_MessageCrypto_TranslateShakeRequest( uint16_t magic, const whMessageCrypto_ShakeRequest* src, whMessageCrypto_ShakeRequest* dest) diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 3648de8cf..9917f82cf 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -5024,7 +5024,6 @@ static int _HandleSha512(whServerContext* ctx, uint16_t magic, int devId, #if defined(WOLFSSL_SHA3) /* SHA3 - one handler dispatches all four variants on hashType. */ - typedef struct { uint32_t blockSize; uint32_t digestSize; @@ -5156,9 +5155,8 @@ 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. */ +/* SHAKE server handler. Mirrors _HandleSha3 above, with caller-specified + * output length */ typedef struct { uint32_t blockSize; int (*initFn)(wc_Shake* sha, void* heap, int devId); @@ -5231,7 +5229,6 @@ static int _HandleShake(whServerContext* ctx, int hashType, uint16_t magic, 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)) { @@ -5249,8 +5246,7 @@ static int _HandleShake(whServerContext* ctx, int hashType, uint16_t magic, return ret; } - /* Restore intermediate state from the client; the server is stateless - * otherwise and the partial block lives only on the client. */ + /* Return intermediate state to the client; the server is stateless */ memcpy(shake->s, req.resumeState.s, sizeof(shake->s)); if (req.inSz > 0) { diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index b20b03af1..64610a973 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -56,9 +56,6 @@ 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/config/user_settings.h b/test/config/user_settings.h index 273641eee..f1e60a486 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -137,31 +137,20 @@ * 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 */ +/* ML-DSA and ML-KEM both hash with SHAKE128 and SHAKE256. */ +#if !defined(WOLFHSM_CFG_TEST_NO_SHAKE128) && \ + !defined(WOLFHSM_CFG_TEST_NO_SHAKE256) #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 From 0e238634b60d106a5434eabfa53fc28b4579d331 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 2 Oct 2026 13:02:18 -0700 Subject: [PATCH 4/7] Add WOLF_CRYPTO_CB_SHAKE_XOF --- src/wh_client_cryptocb.c | 8 +++ .../client-server/wh_test_crypto_shake.c | 54 ++++++++++++++++++- test-refactor/posix/Makefile | 5 ++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 1bdebbd45..90486aef9 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -737,6 +737,14 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) break; } #endif +#ifdef WOLF_CRYPTO_CB_SHAKE_XOF + /* Absorb and squeeze are not offloaded yet. Declining is + * safe because the host wc_Shake state is still current. */ + if (info->hash.shakeOp != WC_SHAKE_OP_NONE) { + ret = CRYPTOCB_UNAVAILABLE; + break; + } +#endif /* wolfCrypt accepts a finalize with outSz=0, but only the * context is updated. Decline it and let the software path diff --git a/test-refactor/client-server/wh_test_crypto_shake.c b/test-refactor/client-server/wh_test_crypto_shake.c index 3581449ce..fa0ec9296 100644 --- a/test-refactor/client-server/wh_test_crypto_shake.c +++ b/test-refactor/client-server/wh_test_crypto_shake.c @@ -68,17 +68,21 @@ typedef struct { 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); + int (*absorbFn)(wc_Shake* sha, const byte* in, word32 inSz); + int (*squeezeFn)(wc_Shake* sha, byte* out, word32 blockCnt); 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}, + wc_Shake128_Update, wc_Shake128_Final, wc_Shake128_Absorb, + wc_Shake128_SqueezeBlocks, wc_Shake128_Free}, #endif #ifdef WOLFSSL_SHAKE256 {WC_HASH_TYPE_SHAKE256, 136u, "SHAKE256", wc_InitShake256, - wc_Shake256_Update, wc_Shake256_Final, wc_Shake256_Free}, + wc_Shake256_Update, wc_Shake256_Final, wc_Shake256_Absorb, + wc_Shake256_SqueezeBlocks, wc_Shake256_Free}, #endif }; @@ -306,6 +310,51 @@ static int _ShakeTestAsync(whClientContext* ctx, const shakeTestVariant* v) return WH_ERROR_OK; } +/* Absorb once, then squeeze several times on a device-bound object. Each + * squeeze must continue the sponge. */ +#define SHAKE_TEST_SQUEEZE_CALLS 3u +#define SHAKE_TEST_SQUEEZE_BLOCKS 2u +static int _ShakeTestAbsorbSqueeze(whClientContext* ctx, + const shakeTestVariant* v) +{ + wc_Shake sha[1]; + uint32_t outSz = + SHAKE_TEST_SQUEEZE_CALLS * SHAKE_TEST_SQUEEZE_BLOCKS * v->blockSize; + uint32_t j; + int ret = 0; + int i; + + for (i = 0; (ret == 0) && (i < 2); i++) { + int devId = (i == 0) ? WH_CLIENT_DEVID(ctx) : INVALID_DEVID; + uint8_t* out = (i == 0) ? shakeTestOutDev : shakeTestOutSw; + + memset(out, (i == 0) ? 0 : 0xA5, outSz); + ret = v->initFn(sha, NULL, devId); + if (ret != 0) { + break; + } + ret = v->absorbFn(sha, shakeTestIn, 2u * v->blockSize + 7u); + for (j = 0; (ret == 0) && (j < SHAKE_TEST_SQUEEZE_CALLS); j++) { + ret = v->squeezeFn( + sha, out + j * SHAKE_TEST_SQUEEZE_BLOCKS * v->blockSize, + SHAKE_TEST_SQUEEZE_BLOCKS); + } + v->freeFn(sha); + } + + if (ret != 0) { + WH_ERROR_PRINT("%s absorb/squeeze failed: %d\n", v->name, ret); + return ret; + } + if (memcmp(shakeTestOutDev, shakeTestOutSw, outSz) != 0) { + WH_ERROR_PRINT("%s absorb/squeeze device and software differ\n", + v->name); + return WH_ERROR_ABORTED; + } + WH_TEST_PRINT("%s absorb/squeeze SUCCESS\n", v->name); + 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, @@ -500,6 +549,7 @@ int whTest_Crypto_Shake(whClientContext* ctx) WH_TEST_RETURN_ON_FAIL(_ShakeTestAsync(ctx, v)); WH_TEST_RETURN_ON_FAIL(_ShakeTestLongOutput(ctx, v)); WH_TEST_RETURN_ON_FAIL(_ShakeTestZeroLengthFinal(ctx, v)); + WH_TEST_RETURN_ON_FAIL(_ShakeTestAbsorbSqueeze(ctx, v)); #ifdef WOLFSSL_HASH_FLAGS WH_TEST_RETURN_ON_FAIL(_ShakeTestKeccakFlag(ctx, v)); #endif diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index 64610a973..d1f24d486 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -56,6 +56,11 @@ endif ifeq ($(SHAKE256_ONLY),1) DEF += -DWOLFHSM_CFG_TEST_NO_SHAKE128 endif +# Dispatch SHAKE absorb and squeeze to the crypto callback. Needs a wolfSSL +# with hash.shakeOp in wc_CryptoInfo. +ifeq ($(SHAKE_XOF),1) + DEF += -DWOLF_CRYPTO_CB_SHAKE_XOF +endif # C standard CSTD ?= -std=c90 From 07e7dfd5b6a187fc56016349fa39b51efc2b81c8 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 2 Oct 2026 16:09:37 -0700 Subject: [PATCH 5/7] Integrate absorb and squeeze from wolfSSL --- .github/actions/checkout-wolfssl/action.yml | 2 +- src/wh_client_crypto.c | 280 ++++++++++++------ src/wh_client_cryptocb.c | 54 +++- src/wh_message_crypto.c | 1 + src/wh_server_crypto.c | 81 +++-- .../client-server/wh_test_crypto_shake.c | 153 +++++++--- .../misc/wh_test_check_struct_padding.c | 2 + test-refactor/posix/Makefile | 7 +- test/Makefile | 2 + test/config/user_settings.h | 2 + test/wh_test_check_struct_padding.c | 2 + wolfhsm/wh_client_crypto.h | 8 + wolfhsm/wh_message_crypto.h | 16 +- 13 files changed, 439 insertions(+), 171 deletions(-) diff --git a/.github/actions/checkout-wolfssl/action.yml b/.github/actions/checkout-wolfssl/action.yml index 53a341d96..cd5816321 100644 --- a/.github/actions/checkout-wolfssl/action.yml +++ b/.github/actions/checkout-wolfssl/action.yml @@ -10,7 +10,7 @@ inputs: description: > wolfSSL git ref to check out required: false - default: 'v5.9.4-stable' + default: 'd526f9d020483afbbc1cee4d1b834c53f1d4af70' runs: using: 'composite' diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index f5ced9b4e..aef27f5e8 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -9768,6 +9768,7 @@ static int _ShakeUpdateRequest(whClientContext* ctx, wc_Shake* sha, req->isLastBlock = 0; req->inSz = wirePos; req->outSz = 0; + req->op = WH_MESSAGE_CRYPTO_SHAKE_OP_HASH; memcpy(req->resumeState.s, sha->s, sizeof(req->resumeState.s)); ret = wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_HASH, @@ -9786,19 +9787,16 @@ static int _ShakeUpdateRequest(whClientContext* ctx, wc_Shake* sha, return ret; } -static int _ShakeUpdateResponse(whClientContext* ctx, wc_Shake* sha, - const whShakeVariant* v) +/* Receive a SHAKE response and check it has exactly outSz bytes of output */ +static int _ShakeRecvResponse(whClientContext* ctx, const whShakeVariant* v, + uint32_t outSz, + whMessageCrypto_ShakeResponse** res) { - 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; - } + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WH_MESSAGE_ACTION_NONE; + uint16_t dataSz = 0; + uint8_t* dataPtr; + int ret; dataPtr = wh_CommClient_GetDataPtr(ctx->comm); if (dataPtr == NULL) { @@ -9811,26 +9809,48 @@ static int _ShakeUpdateResponse(whClientContext* ctx, wc_Shake* sha, return ret; } - ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)&res); + ret = _getCryptoResponse(dataPtr, v->hashType, (uint8_t**)res); + if ((ret >= 0) && ((dataSz < sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(**res) + outSz) || + ((*res)->outSz != outSz))) { + ret = WH_ERROR_ABORTED; + } + return ret; +} + +static int _ShakeUpdateResponse(whClientContext* ctx, wc_Shake* sha, + const whShakeVariant* v) +{ + whMessageCrypto_ShakeResponse* res = NULL; + int ret; + + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + + ret = _ShakeRecvResponse(ctx, v, 0, &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) +/* Send the sponge, plus the tail buffered in sha->t unless squeezing */ +static int _ShakeStateRequest(whClientContext* ctx, wc_Shake* sha, + const whShakeVariant* v, uint32_t op, + uint32_t outSz) { int ret; whMessageCrypto_ShakeRequest* req; - uint8_t* inlineData; uint8_t* dataPtr; + int isSqueeze = (op == WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE); + uint32_t inSz; - if (ctx == NULL || sha == NULL || outSz == 0) { + if (ctx == NULL || sha == NULL) { + return WH_ERROR_BADARGS; + } + /* Only an absorb asks for no output */ + if ((op != WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB) && (outSz == 0)) { return WH_ERROR_BADARGS; } ret = _Sha3RejectKeccak(sha); @@ -9853,65 +9873,73 @@ static int _ShakeFinalRequest(whClientContext* ctx, wc_Shake* sha, req = (whMessageCrypto_ShakeRequest*)_createCryptoRequest( dataPtr, v->hashType, ctx->cryptoAffinity); - inlineData = (uint8_t*)(req + 1); - req->isLastBlock = 1; - req->inSz = sha->i; + inSz = isSqueeze ? 0u : sha->i; + req->isLastBlock = isSqueeze ? 0u : 1u; + req->inSz = inSz; req->outSz = outSz; + req->op = op; memcpy(req->resumeState.s, sha->s, sizeof(req->resumeState.s)); - if (sha->i > 0) { - memcpy(inlineData, sha->t, sha->i); + if (inSz > 0) { + memcpy((uint8_t*)(req + 1), sha->t, inSz); } ret = wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_HASH, sizeof(whMessageCrypto_GenericRequestHeader) + - sizeof(*req) + sha->i, + sizeof(*req) + inSz, dataPtr); return ret; } -static int _ShakeFinalResponse(whClientContext* ctx, wc_Shake* sha, - const whShakeVariant* v, uint8_t* out, - uint32_t outSz) +static int _ShakeStateResponse(whClientContext* ctx, wc_Shake* sha, + const whShakeVariant* v, uint32_t op, + 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) { + if (ctx == NULL || sha == NULL) { return WH_ERROR_BADARGS; } - - dataPtr = wh_CommClient_GetDataPtr(ctx->comm); - if (dataPtr == NULL) { + if ((op != WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB) && + ((out == NULL) || (outSz == 0))) { 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); + ret = _ShakeRecvResponse(ctx, v, outSz, &res); if (ret >= 0) { - if (dataSz < sizeof(whMessageCrypto_GenericResponseHeader) + - sizeof(*res) + outSz) { - return WH_ERROR_ABORTED; + if (outSz > 0) { + memcpy(out, (uint8_t*)(res + 1), outSz); } - if (res->outSz != outSz) { - return WH_ERROR_ABORTED; + if (op == WH_MESSAGE_CRYPTO_SHAKE_OP_HASH) { + /* Reset state, preserving heap and devId, dropping devCtx. */ + savedHeap = sha->heap; + savedDevId = sha->devId; + (void)v->initFn(sha, savedHeap, savedDevId); } - memcpy(out, (uint8_t*)(res + 1), outSz); - /* Reset state, preserving heap and devId, dropping devCtx. */ - savedHeap = sha->heap; - savedDevId = sha->devId; - (void)v->initFn(sha, savedHeap, savedDevId); + else { + memcpy(sha->s, res->resumeState.s, sizeof(sha->s)); + /* The server padded the buffered tail into the state */ + if (op == WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB) { + sha->i = 0; + } + } + } + return ret; +} + +static int _ShakeStateOp(whClientContext* ctx, wc_Shake* sha, + const whShakeVariant* v, uint32_t op, uint8_t* out, + uint32_t outSz) +{ + int ret = _ShakeStateRequest(ctx, sha, v, op, outSz); + + if (ret == WH_ERROR_OK) { + do { + ret = _ShakeStateResponse(ctx, sha, v, op, out, outSz); + } while (ret == WH_ERROR_NOTREADY); } return ret; } @@ -9938,11 +9966,38 @@ static void _ShakeRestoreState(wc_Shake* sha, const _ShakeSavedState* saved) memcpy(sha->t, saved->t, sizeof(saved->t)); } +/* Send all of in as whole-block updates, leaving any tail in sha->t */ +static int _ShakeUpdateAll(whClientContext* ctx, wc_Shake* sha, + const whShakeVariant* v, const uint8_t* in, + uint32_t inLen) +{ + int ret = WH_ERROR_OK; + 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 && sent) { + do { + ret = _ShakeUpdateResponse(ctx, sha, v); + } while (ret == WH_ERROR_NOTREADY); + } + consumed += chunk; + } + return ret; +} + +/* Update with in, then finish with an absorb or, when out is set, a final */ static int _ShakeOneshot(whClientContext* ctx, wc_Shake* sha, - const whShakeVariant* v, const uint8_t* in, - uint32_t inLen, uint8_t* out, uint32_t outSz) + const whShakeVariant* v, uint32_t op, + const uint8_t* in, uint32_t inLen, uint8_t* out, + uint32_t outSz) { - int ret = WH_ERROR_OK; + int ret; _ShakeSavedState saved; /* _ShakeUpdatePerCallCapacity reads sha->i, so validate sha here rather @@ -9959,44 +10014,49 @@ static int _ShakeOneshot(whClientContext* ctx, wc_Shake* sha, * 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 = _ShakeUpdateAll(ctx, sha, v, in, inLen); + if ((ret == WH_ERROR_OK) && + ((op == WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB) || (out != NULL))) { + ret = _ShakeStateOp(ctx, sha, v, op, out, outSz); + } - 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; - } + /* Leave sha as the caller passed it so a fallback starts clean. */ + if (ret != WH_ERROR_OK) { + _ShakeRestoreState(sha, &saved); } + /* The snapshot holds sponge state and message bytes. */ + wc_ForceZero(&saved, sizeof(saved)); + return ret; +} - 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); - } +static int _ShakeSqueeze(whClientContext* ctx, wc_Shake* sha, + const whShakeVariant* v, uint8_t* out, + uint32_t blockCnt) +{ + const uint32_t maxBlocks = + WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ / v->blockSize; + int ret = WH_ERROR_OK; + _ShakeSavedState saved; + + if (ctx == NULL || sha == NULL || (out == NULL && blockCnt != 0)) { + return WH_ERROR_BADARGS; + } + + _ShakeSaveState(sha, &saved); + + /* Each response returns the state, so a long squeeze spans requests */ + while (ret == WH_ERROR_OK && blockCnt > 0) { + uint32_t n = (blockCnt < maxBlocks) ? blockCnt : maxBlocks; + + ret = _ShakeStateOp(ctx, sha, v, WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE, + out, n * v->blockSize); + out += n * v->blockSize; + blockCnt -= n; } - /* Leave sha as the caller passed it so a fallback starts clean. */ if (ret != WH_ERROR_OK) { _ShakeRestoreState(sha, &saved); } - /* The snapshot holds sponge state and message bytes. */ wc_ForceZero(&saved, sizeof(saved)); return ret; } @@ -10006,7 +10066,8 @@ static int _ShakeOneshot(whClientContext* ctx, wc_Shake* sha, 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); + return _ShakeOneshot(ctx, sha, &whShake128, WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, + in, inLen, out, outSz); } int wh_Client_Shake128UpdateRequest(whClientContext* ctx, wc_Shake* sha, @@ -10024,13 +10085,28 @@ int wh_Client_Shake128UpdateResponse(whClientContext* ctx, wc_Shake* sha) int wh_Client_Shake128FinalRequest(whClientContext* ctx, wc_Shake* sha, uint32_t outSz) { - return _ShakeFinalRequest(ctx, sha, &whShake128, outSz); + return _ShakeStateRequest(ctx, sha, &whShake128, + WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, outSz); } int wh_Client_Shake128FinalResponse(whClientContext* ctx, wc_Shake* sha, uint8_t* out, uint32_t outSz) { - return _ShakeFinalResponse(ctx, sha, &whShake128, out, outSz); + return _ShakeStateResponse(ctx, sha, &whShake128, + WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, out, outSz); +} + +int wh_Client_Shake128Absorb(whClientContext* ctx, wc_Shake* sha, + const uint8_t* in, uint32_t inLen) +{ + return _ShakeOneshot(ctx, sha, &whShake128, + WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB, in, inLen, NULL, 0); +} + +int wh_Client_Shake128SqueezeBlocks(whClientContext* ctx, wc_Shake* sha, + uint8_t* out, uint32_t blockCnt) +{ + return _ShakeSqueeze(ctx, sha, &whShake128, out, blockCnt); } #endif /* WOLFSSL_SHAKE128 */ @@ -10038,7 +10114,8 @@ int wh_Client_Shake128FinalResponse(whClientContext* ctx, wc_Shake* sha, 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); + return _ShakeOneshot(ctx, sha, &whShake256, WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, + in, inLen, out, outSz); } int wh_Client_Shake256UpdateRequest(whClientContext* ctx, wc_Shake* sha, @@ -10056,13 +10133,28 @@ int wh_Client_Shake256UpdateResponse(whClientContext* ctx, wc_Shake* sha) int wh_Client_Shake256FinalRequest(whClientContext* ctx, wc_Shake* sha, uint32_t outSz) { - return _ShakeFinalRequest(ctx, sha, &whShake256, outSz); + return _ShakeStateRequest(ctx, sha, &whShake256, + WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, outSz); } int wh_Client_Shake256FinalResponse(whClientContext* ctx, wc_Shake* sha, uint8_t* out, uint32_t outSz) { - return _ShakeFinalResponse(ctx, sha, &whShake256, out, outSz); + return _ShakeStateResponse(ctx, sha, &whShake256, + WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, out, outSz); +} + +int wh_Client_Shake256Absorb(whClientContext* ctx, wc_Shake* sha, + const uint8_t* in, uint32_t inLen) +{ + return _ShakeOneshot(ctx, sha, &whShake256, + WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB, in, inLen, NULL, 0); +} + +int wh_Client_Shake256SqueezeBlocks(whClientContext* ctx, wc_Shake* sha, + uint8_t* out, uint32_t blockCnt) +{ + return _ShakeSqueeze(ctx, sha, &whShake256, out, blockCnt); } #endif /* WOLFSSL_SHAKE256 */ #endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */ diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 90486aef9..1dda097b4 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -61,6 +61,11 @@ #include "wolfhsm/wh_message_crypto.h" +#if defined(WOLF_CRYPTO_CB_SHAKE_XOF) && \ + (defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)) +static int _handleShakeXof(whClientContext* ctx, wc_CryptoInfo* info); +#endif + #if defined(WOLFSSL_HAVE_MLKEM) static int _handlePqcKemKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma); @@ -738,10 +743,8 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) } #endif #ifdef WOLF_CRYPTO_CB_SHAKE_XOF - /* Absorb and squeeze are not offloaded yet. Declining is - * safe because the host wc_Shake state is still current. */ if (info->hash.shakeOp != WC_SHAKE_OP_NONE) { - ret = CRYPTOCB_UNAVAILABLE; + ret = _handleShakeXof(ctx, info); break; } #endif @@ -772,7 +775,7 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) break; #endif } - /* Requested output size is too big, surface error. */ + /* Output too big for one response; let software produce it */ if (ret == WH_ERROR_NOSPACE) { ret = CRYPTOCB_UNAVAILABLE; } @@ -851,6 +854,49 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) return ret; } +#if defined(WOLF_CRYPTO_CB_SHAKE_XOF) && \ + (defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256)) +/* Declining an unknown op is safe as the wc_Shake state is still current */ +static int _handleShakeXof(whClientContext* ctx, wc_CryptoInfo* info) +{ + wc_Shake* sha = info->hash.sha3; + int op = info->hash.shakeOp; + int ret = CRYPTOCB_UNAVAILABLE; + + switch (info->hash.type) { +#ifdef WOLFSSL_SHAKE128 + case WC_HASH_TYPE_SHAKE128: + if (op == WC_SHAKE_OP_ABSORB) { + ret = wh_Client_Shake128Absorb(ctx, sha, info->hash.in, + info->hash.inSz); + } + else if (op == WC_SHAKE_OP_SQUEEZE) { + ret = wh_Client_Shake128SqueezeBlocks( + ctx, sha, info->hash.digest, + info->hash.outSz / (WC_SHA3_128_COUNT * 8u)); + } + break; +#endif +#ifdef WOLFSSL_SHAKE256 + case WC_HASH_TYPE_SHAKE256: + if (op == WC_SHAKE_OP_ABSORB) { + ret = wh_Client_Shake256Absorb(ctx, sha, info->hash.in, + info->hash.inSz); + } + else if (op == WC_SHAKE_OP_SQUEEZE) { + ret = wh_Client_Shake256SqueezeBlocks( + ctx, sha, info->hash.digest, + info->hash.outSz / (WC_SHA3_256_COUNT * 8u)); + } + break; +#endif + default: + break; + } + return ret; +} +#endif /* WOLF_CRYPTO_CB_SHAKE_XOF */ + #if defined(WOLFSSL_HAVE_MLKEM) static int _handlePqcKemKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma) diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index cd2e2009e..376618b0d 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -796,6 +796,7 @@ int wh_MessageCrypto_TranslateShakeRequest( WH_T32(magic, dest, src, isLastBlock); WH_T32(magic, dest, src, inSz); WH_T32(magic, dest, src, outSz); + WH_T32(magic, dest, src, op); return wh_MessageCrypto_TranslateSha3State(magic, &src->resumeState, &dest->resumeState); } diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 9917f82cf..f8399f3ba 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -5162,6 +5162,8 @@ typedef struct { 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); + int (*absorbFn)(wc_Shake* sha, const byte* data, word32 len); + int (*squeezeFn)(wc_Shake* sha, byte* out, word32 blockCnt); void (*freeFn)(wc_Shake* sha); } _ShakeVariantOps; @@ -5174,6 +5176,8 @@ static int _ShakeLookupOps(int hashType, _ShakeVariantOps* ops) ops->initFn = wc_InitShake128; ops->updateFn = wc_Shake128_Update; ops->finalFn = wc_Shake128_Final; + ops->absorbFn = wc_Shake128_Absorb; + ops->squeezeFn = wc_Shake128_SqueezeBlocks; ops->freeFn = wc_Shake128_Free; return 0; #endif @@ -5183,6 +5187,8 @@ static int _ShakeLookupOps(int hashType, _ShakeVariantOps* ops) ops->initFn = wc_InitShake256; ops->updateFn = wc_Shake256_Update; ops->finalFn = wc_Shake256_Final; + ops->absorbFn = wc_Shake256_Absorb; + ops->squeezeFn = wc_Shake256_SqueezeBlocks; ops->freeFn = wc_Shake256_Free; return 0; #endif @@ -5191,6 +5197,36 @@ static int _ShakeLookupOps(int hashType, _ShakeVariantOps* ops) } } +/* Check the input and output sizes against what the op allows */ +static int _ShakeCheckRequest(const whMessageCrypto_ShakeRequest* req, + uint32_t blockSize) +{ + const uint32_t maxOut = WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ; + int ok = 0; + + switch (req->op) { + case WH_MESSAGE_CRYPTO_SHAKE_OP_HASH: + if (req->isLastBlock) { + ok = (req->inSz < blockSize) && (req->outSz != 0) && + (req->outSz <= maxOut); + } + else { + ok = ((req->inSz % blockSize) == 0) && (req->outSz == 0); + } + break; + case WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB: + ok = (req->inSz < blockSize) && (req->outSz == 0); + break; + case WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE: + ok = (req->inSz == 0) && (req->outSz != 0) && + ((req->outSz % blockSize) == 0) && (req->outSz <= maxOut); + break; + default: + break; + } + return ok ? WH_ERROR_OK : 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) @@ -5202,6 +5238,7 @@ static int _HandleShake(whServerContext* ctx, int hashType, uint16_t magic, const uint8_t* inData; uint8_t* outData; _ShakeVariantOps ops; + int isFinal; (void)ctx; @@ -5223,18 +5260,11 @@ static int _HandleShake(whServerContext* ctx, int hashType, uint16_t magic, (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; - } - if (req.isLastBlock) { - if ((req.outSz == 0) || - (req.outSz > WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ)) { - return WH_ERROR_BADARGS; - } + ret = _ShakeCheckRequest(&req, ops.blockSize); + if (ret != 0) { + return ret; } + isFinal = (req.op == WH_MESSAGE_CRYPTO_SHAKE_OP_HASH) && req.isLastBlock; inData = (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_ShakeRequest); @@ -5249,27 +5279,30 @@ static int _HandleShake(whServerContext* ctx, int hashType, uint16_t magic, /* Return intermediate state to the client; the server is stateless */ memcpy(shake->s, req.resumeState.s, sizeof(shake->s)); - if (req.inSz > 0) { + if (req.op == WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB) { + ret = ops.absorbFn(shake, inData, req.inSz); + } + else if (req.op == WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE) { + ret = ops.squeezeFn(shake, outData, req.outSz / ops.blockSize); + } + else if (req.inSz > 0) { ret = ops.updateFn(shake, inData, req.inSz); } if (ret == 0) { - if (req.isLastBlock) { + if (isFinal) { ret = ops.finalFn(shake, outData, req.outSz); - if (ret == 0) { - res.outSz = req.outSz; - } + } + /* Only the sponge goes back, so no partial block may be left. */ + else if (shake->i != 0) { + ret = WH_ERROR_ABORTED; } 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)); - } + memcpy(res.resumeState.s, shake->s, sizeof(res.resumeState.s)); } } + if (ret == 0) { + res.outSz = req.outSz; + } ops.freeFn(shake); diff --git a/test-refactor/client-server/wh_test_crypto_shake.c b/test-refactor/client-server/wh_test_crypto_shake.c index fa0ec9296..a082d8a3d 100644 --- a/test-refactor/client-server/wh_test_crypto_shake.c +++ b/test-refactor/client-server/wh_test_crypto_shake.c @@ -71,18 +71,24 @@ typedef struct { int (*absorbFn)(wc_Shake* sha, const byte* in, word32 inSz); int (*squeezeFn)(wc_Shake* sha, byte* out, word32 blockCnt); void (*freeFn)(wc_Shake* sha); + int (*clientAbsorbFn)(whClientContext* ctx, wc_Shake* sha, + const uint8_t* in, uint32_t inLen); + int (*clientSqueezeFn)(whClientContext* ctx, wc_Shake* sha, uint8_t* out, + uint32_t blockCnt); } shakeTestVariant; static const shakeTestVariant shakeTestVariants[] = { #ifdef WOLFSSL_SHAKE128 {WC_HASH_TYPE_SHAKE128, 168u, "SHAKE128", wc_InitShake128, wc_Shake128_Update, wc_Shake128_Final, wc_Shake128_Absorb, - wc_Shake128_SqueezeBlocks, wc_Shake128_Free}, + wc_Shake128_SqueezeBlocks, wc_Shake128_Free, wh_Client_Shake128Absorb, + wh_Client_Shake128SqueezeBlocks}, #endif #ifdef WOLFSSL_SHAKE256 {WC_HASH_TYPE_SHAKE256, 136u, "SHAKE256", wc_InitShake256, wc_Shake256_Update, wc_Shake256_Final, wc_Shake256_Absorb, - wc_Shake256_SqueezeBlocks, wc_Shake256_Free}, + wc_Shake256_SqueezeBlocks, wc_Shake256_Free, wh_Client_Shake256Absorb, + wh_Client_Shake256SqueezeBlocks}, #endif }; @@ -310,49 +316,114 @@ static int _ShakeTestAsync(whClientContext* ctx, const shakeTestVariant* v) return WH_ERROR_OK; } -/* Absorb once, then squeeze several times on a device-bound object. Each - * squeeze must continue the sponge. */ -#define SHAKE_TEST_SQUEEZE_CALLS 3u -#define SHAKE_TEST_SQUEEZE_BLOCKS 2u -static int _ShakeTestAbsorbSqueeze(whClientContext* ctx, - const shakeTestVariant* v) +/* Ways to drive an absorb/squeeze case */ +#define SHAKE_TEST_XOF_SOFTWARE 0 +#define SHAKE_TEST_XOF_CRYPTOCB 1 +#define SHAKE_TEST_XOF_CLIENT 2 + +/* Absorb, then squeeze 1, 2, and more blocks than one response holds */ +static int _ShakeTestXofRun(whClientContext* ctx, const shakeTestVariant* v, + int path, uint32_t inLen, uint8_t* out, + uint32_t* outSz) { - wc_Shake sha[1]; - uint32_t outSz = - SHAKE_TEST_SQUEEZE_CALLS * SHAKE_TEST_SQUEEZE_BLOCKS * v->blockSize; - uint32_t j; - int ret = 0; - int i; + const uint32_t blockCnts[] = { + 1u, 2u, + WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ / v->blockSize + 1u}; + const uint32_t cnt = sizeof(blockCnts) / sizeof(blockCnts[0]); + int devId = WH_CLIENT_DEVID(ctx); + int toServer; + uint16_t seq; + wc_Shake sha[1]; + uint32_t i; + int ret; + +#ifdef WOLF_CRYPTO_CB_SHAKE_XOF + toServer = (path != SHAKE_TEST_XOF_SOFTWARE); +#else + toServer = (path == SHAKE_TEST_XOF_CLIENT); +#endif + if (path == SHAKE_TEST_XOF_SOFTWARE) { + devId = INVALID_DEVID; + } - for (i = 0; (ret == 0) && (i < 2); i++) { - int devId = (i == 0) ? WH_CLIENT_DEVID(ctx) : INVALID_DEVID; - uint8_t* out = (i == 0) ? shakeTestOutDev : shakeTestOutSw; + *outSz = 0; + for (i = 0; i < cnt; i++) { + *outSz += blockCnts[i] * v->blockSize; + } + memset(out, (path == SHAKE_TEST_XOF_SOFTWARE) ? 0xA5 : 0, *outSz); - memset(out, (i == 0) ? 0 : 0xA5, outSz); - ret = v->initFn(sha, NULL, devId); - if (ret != 0) { - break; + ret = v->initFn(sha, NULL, devId); + if (ret != 0) { + return ret; + } + + seq = ctx->comm->seq; + if (path == SHAKE_TEST_XOF_CLIENT) { + ret = v->clientAbsorbFn(ctx, sha, shakeTestIn, inLen); + } + else { + ret = v->absorbFn(sha, shakeTestIn, inLen); + } + /* Software continues from this state, which has no partial block */ + if ((ret == 0) && (sha->i != 0)) { + ret = WH_ERROR_ABORTED; + } + for (i = 0; (ret == 0) && (i < cnt); i++) { + if (path == SHAKE_TEST_XOF_CLIENT) { + ret = v->clientSqueezeFn(ctx, sha, out, blockCnts[i]); } - ret = v->absorbFn(sha, shakeTestIn, 2u * v->blockSize + 7u); - for (j = 0; (ret == 0) && (j < SHAKE_TEST_SQUEEZE_CALLS); j++) { - ret = v->squeezeFn( - sha, out + j * SHAKE_TEST_SQUEEZE_BLOCKS * v->blockSize, - SHAKE_TEST_SQUEEZE_BLOCKS); + else { + ret = v->squeezeFn(sha, out, blockCnts[i]); } - v->freeFn(sha); + out += blockCnts[i] * v->blockSize; } + v->freeFn(sha); - if (ret != 0) { - WH_ERROR_PRINT("%s absorb/squeeze failed: %d\n", v->name, ret); - return ret; + /* A silent decline would still match software, so check where it ran */ + if ((ret == 0) && (toServer != (seq != ctx->comm->seq))) { + WH_ERROR_PRINT("%s absorb/squeeze path %d server use %d expected %d\n", + v->name, path, (seq != ctx->comm->seq), toServer); + ret = WH_ERROR_ABORTED; } - if (memcmp(shakeTestOutDev, shakeTestOutSw, outSz) != 0) { - WH_ERROR_PRINT("%s absorb/squeeze device and software differ\n", - v->name); - return WH_ERROR_ABORTED; + return ret; +} + +static int _ShakeTestAbsorbSqueeze(whClientContext* ctx, + const shakeTestVariant* v) +{ + /* Empty, a partial tail, and more input than one request can carry */ + const uint32_t inLens[] = {0u, 2u * v->blockSize + 7u, SHAKE_TEST_MAX_IN}; + const int paths[] = {SHAKE_TEST_XOF_CRYPTOCB, SHAKE_TEST_XOF_CLIENT}; + const uint32_t inLenCnt = sizeof(inLens) / sizeof(inLens[0]); + const uint32_t pathCnt = sizeof(paths) / sizeof(paths[0]); + uint32_t swSz = 0; + uint32_t devSz = 0; + uint32_t i; + uint32_t j; + int ret = 0; + + for (i = 0; (ret == 0) && (i < inLenCnt); i++) { + ret = _ShakeTestXofRun(ctx, v, SHAKE_TEST_XOF_SOFTWARE, inLens[i], + shakeTestOutSw, &swSz); + for (j = 0; (ret == 0) && (j < pathCnt); j++) { + ret = _ShakeTestXofRun(ctx, v, paths[j], inLens[i], shakeTestOutDev, + &devSz); + if ((ret == 0) && + ((devSz != swSz) || + (memcmp(shakeTestOutDev, shakeTestOutSw, swSz) != 0))) { + ret = WH_ERROR_ABORTED; + } + if (ret != 0) { + WH_ERROR_PRINT("%s absorb/squeeze path %d in %u failed: %d\n", + v->name, paths[j], (unsigned)inLens[i], ret); + } + } } - WH_TEST_PRINT("%s absorb/squeeze SUCCESS\n", v->name); - return WH_ERROR_OK; + + if (ret == 0) { + WH_TEST_PRINT("%s absorb/squeeze SUCCESS\n", v->name); + } + return ret; } /* wolfCrypt accepts a finalize asking for zero bytes: it writes nothing and @@ -493,6 +564,14 @@ static int _ShakeTestBadArgs(whClientContext* ctx, const shakeTestVariant* v) WH_ERROR_BADARGS); } #endif + bad = + bad || + (v->clientAbsorbFn(NULL, sha, buf, sizeof(buf)) != WH_ERROR_BADARGS) || + (v->clientAbsorbFn(ctx, NULL, buf, sizeof(buf)) != WH_ERROR_BADARGS) || + (v->clientAbsorbFn(ctx, sha, NULL, sizeof(buf)) != WH_ERROR_BADARGS) || + (v->clientSqueezeFn(NULL, sha, buf, 1u) != WH_ERROR_BADARGS) || + (v->clientSqueezeFn(ctx, NULL, buf, 1u) != WH_ERROR_BADARGS) || + (v->clientSqueezeFn(ctx, sha, NULL, 1u) != WH_ERROR_BADARGS); #ifdef WOLFSSL_HASH_FLAGS /* Keccak mode is not carried on the wire, so the request helpers must @@ -518,6 +597,8 @@ static int _ShakeTestBadArgs(whClientContext* ctx, const shakeTestVariant* v) WH_ERROR_BADARGS); } #endif + bad = bad || (v->clientAbsorbFn(ctx, sha, buf, sizeof(buf)) != + WH_ERROR_BADARGS); (void)wc_Sha3_SetFlags(sha, 0); } #endif diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index 699d503ca..e717d05a3 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -133,6 +133,8 @@ whMessageCrypto_Sha3State hashSha3State; whMessageCrypto_Sha3Request hashSha3Req; whMessageCrypto_Sha3Response hashSha3Res; #endif +whMessageCrypto_ShakeRequest hashShakeReq; +whMessageCrypto_ShakeResponse hashShakeRes; whMessageCrypto_HkdfRequest hkdfReq; whMessageCrypto_HkdfResponse hkdfRes; whMessageCrypto_MlDsaKeyGenRequest pkMldsaKeygenReq; diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index d1f24d486..f497cc6b1 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -56,11 +56,6 @@ endif ifeq ($(SHAKE256_ONLY),1) DEF += -DWOLFHSM_CFG_TEST_NO_SHAKE128 endif -# Dispatch SHAKE absorb and squeeze to the crypto callback. Needs a wolfSSL -# with hash.shakeOp in wc_CryptoInfo. -ifeq ($(SHAKE_XOF),1) - DEF += -DWOLF_CRYPTO_CB_SHAKE_XOF -endif # C standard CSTD ?= -std=c90 @@ -192,6 +187,8 @@ endif # Build the wolfCrypt test suite as a wolfHSM client ifeq ($(TESTWOLFCRYPT),1) DEF += -DWOLFHSM_CFG_TEST_WOLFCRYPTTEST + # Some wolfCrypt tests read ./certs, so point them at the wolfSSL tree + DEF += -DCERT_PREFIX=\"$(abspath $(WOLFSSL_DIR))/\" endif # Add code coverage option diff --git a/test/Makefile b/test/Makefile index 68a99f266..817b18884 100644 --- a/test/Makefile +++ b/test/Makefile @@ -230,6 +230,8 @@ endif # Option to build wolfcrypt tests ifeq ($(TESTWOLFCRYPT),1) DEF += -DWOLFHSM_CFG_TEST_WOLFCRYPTTEST + # Some wolfCrypt tests read ./certs, so point them at the wolfSSL tree + DEF += -DCERT_PREFIX=\"$(abspath $(WOLFSSL_DIR))/\" endif ifeq ($(CLIENT_ONLY),1) diff --git a/test/config/user_settings.h b/test/config/user_settings.h index f1e60a486..003f2b136 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -143,6 +143,8 @@ #ifndef WOLFHSM_CFG_TEST_NO_SHAKE256 #define WOLFSSL_SHAKE256 #endif +/* Also dispatch SHAKE absorb and squeeze to the crypto callback */ +#define WOLF_CRYPTO_CB_SHAKE_XOF /* ML-DSA and ML-KEM both hash with SHAKE128 and SHAKE256. */ #if !defined(WOLFHSM_CFG_TEST_NO_SHAKE128) && \ diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index f716566b6..e27a018f2 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -131,6 +131,8 @@ whMessageCrypto_Sha3State hashSha3State; whMessageCrypto_Sha3Request hashSha3Req; whMessageCrypto_Sha3Response hashSha3Res; #endif +whMessageCrypto_ShakeRequest hashShakeReq; +whMessageCrypto_ShakeResponse hashShakeRes; whMessageCrypto_HkdfRequest hkdfReq; whMessageCrypto_HkdfResponse hkdfRes; whMessageCrypto_MlDsaKeyGenRequest pkMldsaKeygenReq; diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 561685c03..75820a535 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -3001,6 +3001,10 @@ 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); +int wh_Client_Shake128Absorb(whClientContext* ctx, wc_Shake* sha, + const uint8_t* in, uint32_t inLen); +int wh_Client_Shake128SqueezeBlocks(whClientContext* ctx, wc_Shake* sha, + uint8_t* out, uint32_t blockCnt); #endif /* WOLFSSL_SHAKE128 */ #ifdef WOLFSSL_SHAKE256 int wh_Client_Shake256(whClientContext* ctx, wc_Shake* sha, const uint8_t* in, @@ -3013,6 +3017,10 @@ 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); +int wh_Client_Shake256Absorb(whClientContext* ctx, wc_Shake* sha, + const uint8_t* in, uint32_t inLen); +int wh_Client_Shake256SqueezeBlocks(whClientContext* ctx, wc_Shake* sha, + uint8_t* out, uint32_t blockCnt); #endif /* WOLFSSL_SHAKE256 */ #endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */ diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index e3cbf45df..cff28f31f 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -1056,13 +1056,16 @@ int wh_MessageCrypto_TranslateSha3Response( * (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. */ +#define WH_MESSAGE_CRYPTO_SHAKE_OP_HASH 0 /* update, or final on the tail */ +#define WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB 1 /* pad the tail into the state */ +#define WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE 2 /* whole blocks out, no input */ + 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. */ + /* Bytes of output wanted on a final or squeeze */ uint32_t outSz; - uint8_t WH_PAD[4]; + uint32_t op; /* WH_MESSAGE_CRYPTO_SHAKE_OP_* */ whMessageCrypto_Sha3State resumeState; } whMessageCrypto_ShakeRequest; @@ -1071,11 +1074,10 @@ typedef struct { * Wire layout in the comm buffer: * whMessageCrypto_GenericResponseHeader * whMessageCrypto_ShakeResponse - * uint8_t out[outSz] (finalize only; outSz is 0 on an update) + * uint8_t out[outSz] (final or squeeze; outSz is 0 otherwise) * - * 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. */ + * Except after a final, the state carries the sponge to resume from. Sized to + * match the request so the outgoing data starts where the incoming data did. */ typedef struct { whMessageCrypto_Sha3State resumeState; uint32_t outSz; From e8f26b1d4f633ca08e2cecc721d7cbff3a84a4de Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 2 Oct 2026 20:32:25 -0700 Subject: [PATCH 6/7] PR feedback: add wc_Shake*_Reset(), add/update test cases --- .github/workflows/build-and-test-refactor.yml | 13 ++ src/wh_client_crypto.c | 15 +- .../client-server/wh_test_crypto_shake.c | 139 +++++++++++++++++- 3 files changed, 154 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index 97f03c93b..f88fd8e7b 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -163,6 +163,19 @@ jobs: if: matrix.group == 'base' run: cd test-refactor/posix && make clean && make -j DEBUG_VERBOSE=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test with only one SHAKE variant, or none + - name: Build and test refactor SHAKE128 only + if: matrix.group == 'base' + run: cd test-refactor/posix && make clean && make -j SHAKE128_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + + - name: Build and test refactor SHAKE256 only + if: matrix.group == 'base' + run: cd test-refactor/posix && make clean && make -j SHAKE256_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + + - name: Build and test refactor no SHAKE + if: matrix.group == 'base' + run: cd test-refactor/posix && make clean && make -j NO_SHAKE=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test in multithreaded mode with everything enabled - name: Build and test refactor with THREADSAFE and everything if: matrix.group == 'threadsafe' diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index aef27f5e8..ce5a02938 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -9660,19 +9660,19 @@ 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); + /* Restores the context after Final, keeping heap and devId */ + int (*resetFn)(wc_Shake* sha); } whShakeVariant; #ifdef WOLFSSL_SHAKE128 static const whShakeVariant whShake128 = { WC_HASH_TYPE_SHAKE128, 168u, - WH_MESSAGE_CRYPTO_SHAKE128_MAX_INLINE_UPDATE_SZ, wc_InitShake128}; + WH_MESSAGE_CRYPTO_SHAKE128_MAX_INLINE_UPDATE_SZ, wc_Shake128_Reset}; #endif #ifdef WOLFSSL_SHAKE256 static const whShakeVariant whShake256 = { WC_HASH_TYPE_SHAKE256, 136u, - WH_MESSAGE_CRYPTO_SHAKE256_MAX_INLINE_UPDATE_SZ, wc_InitShake256}; + WH_MESSAGE_CRYPTO_SHAKE256_MAX_INLINE_UPDATE_SZ, wc_Shake256_Reset}; #endif /* Maximum data size for a single UpdateRequest: inline wire capacity @@ -9897,8 +9897,6 @@ static int _ShakeStateResponse(whClientContext* ctx, wc_Shake* sha, { int ret; whMessageCrypto_ShakeResponse* res = NULL; - void* savedHeap; - int savedDevId; if (ctx == NULL || sha == NULL) { return WH_ERROR_BADARGS; @@ -9914,10 +9912,7 @@ static int _ShakeStateResponse(whClientContext* ctx, wc_Shake* sha, memcpy(out, (uint8_t*)(res + 1), outSz); } if (op == WH_MESSAGE_CRYPTO_SHAKE_OP_HASH) { - /* Reset state, preserving heap and devId, dropping devCtx. */ - savedHeap = sha->heap; - savedDevId = sha->devId; - (void)v->initFn(sha, savedHeap, savedDevId); + ret = v->resetFn(sha); } else { memcpy(sha->s, res->resumeState.s, sizeof(sha->s)); diff --git a/test-refactor/client-server/wh_test_crypto_shake.c b/test-refactor/client-server/wh_test_crypto_shake.c index a082d8a3d..0c9cf099d 100644 --- a/test-refactor/client-server/wh_test_crypto_shake.c +++ b/test-refactor/client-server/wh_test_crypto_shake.c @@ -433,8 +433,17 @@ static int _ShakeTestZeroLengthFinal(whClientContext* ctx, { int devId = WH_CLIENT_DEVID(ctx); int ret; + int i; wc_Shake sha[1]; uint8_t out[1] = {0xA5}; + uint8_t again[32]; + uint8_t expected[32]; + + ret = _ShakeTestHash(INVALID_DEVID, v, shakeTestIn, 4u, 0u, expected, + sizeof(expected)); + if (ret != 0) { + return ret; + } ret = v->initFn(sha, NULL, devId); if (ret != 0) { @@ -449,13 +458,16 @@ static int _ShakeTestZeroLengthFinal(whClientContext* ctx, 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]; + /* Reuse after the software reset, then after the offload reset */ + for (i = 0; (ret == 0) && (i < 2); i++) { ret = v->updateFn(sha, shakeTestIn, 4u); if (ret == 0) { ret = v->finalFn(sha, again, sizeof(again)); } + if ((ret == 0) && (memcmp(again, expected, sizeof(again)) != 0)) { + WH_ERROR_PRINT("%s reuse %d differs from software\n", v->name, i); + ret = WH_ERROR_ABORTED; + } } v->freeFn(sha); @@ -612,6 +624,126 @@ static int _ShakeTestBadArgs(whClientContext* ctx, const shakeTestVariant* v) WH_TEST_PRINT("%s bad-args SUCCESS\n", v->name); return WH_ERROR_OK; } + +/* A raw request; dropSz bytes are cut from the end to make it short */ +typedef struct { + uint32_t op; + uint32_t isLastBlock; + uint32_t inSz; + uint32_t outSz; + uint32_t dropSz; +} shakeTestRequest; + +/* Send each request as-is and require the server to return expected */ +static int _ShakeTestRawRequests(whClientContext* ctx, + const shakeTestVariant* v, + const shakeTestRequest* reqs, uint32_t cnt, + int expected) +{ + uint8_t* dataPtr; + whMessageCrypto_GenericRequestHeader* hdr; + whMessageCrypto_ShakeRequest* req; + uint16_t group; + uint16_t action; + uint16_t dataSz; + uint32_t i; + int rc; + int ret = WH_ERROR_OK; + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + hdr = (whMessageCrypto_GenericRequestHeader*)dataPtr; + req = (whMessageCrypto_ShakeRequest*)(hdr + 1); + + for (i = 0; (ret == WH_ERROR_OK) && (i < cnt); i++) { + const shakeTestRequest* r = &reqs[i]; + + memset(hdr, 0, sizeof(*hdr)); + memset(req, 0, sizeof(*req)); + hdr->algoType = (whMessageCrypto_AlgoType)v->hashType; + hdr->affinity = ctx->cryptoAffinity; + req->op = r->op; + req->isLastBlock = r->isLastBlock; + req->inSz = r->inSz; + req->outSz = r->outSz; + memcpy((uint8_t*)(req + 1), shakeTestIn, r->inSz); + + ret = wh_Client_SendRequest( + ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_HASH, + (uint16_t)(sizeof(*hdr) + sizeof(*req) + r->inSz - r->dropSz), + dataPtr); + if (ret == WH_ERROR_OK) { + do { + ret = + wh_Client_RecvResponse(ctx, &group, &action, &dataSz, + WOLFHSM_CFG_COMM_DATA_LEN, dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret == WH_ERROR_OK) { + rc = ((whMessageCrypto_GenericResponseHeader*)dataPtr)->rc; + if (rc != expected) { + WH_ERROR_PRINT("%s raw request %u returned %d, expected %d\n", + v->name, (unsigned)i, rc, expected); + ret = WH_ERROR_ABORTED; + } + } + } + return ret; +} + +/* The server must check requests itself rather than trust the client */ +static int _ShakeTestServerRequests(whClientContext* ctx, + const shakeTestVariant* v) +{ + const uint32_t rate = v->blockSize; + const uint32_t maxOut = WH_MESSAGE_CRYPTO_SHAKE_MAX_INLINE_OUTPUT_SZ; + const uint32_t maxSqz = (maxOut / rate) * rate; + /* Each limit is accepted, and crossing it is rejected below */ + const shakeTestRequest accepted[] = { + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 0, rate, 0, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 1, rate - 1u, maxOut, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB, 1, rate - 1u, 0, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE, 0, 0, maxSqz, 0}, + }; + const shakeTestRequest rejected[] = { + /* Shorter than the request struct, then shorter than inSz */ + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 1, 0, 32u, 1u}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 1, 10u, 32u, 1u}, + /* Non-final input not whole blocks, or asking for output */ + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 0, 1u, 0, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 0, rate, 32u, 0}, + /* Final with a full-block tail, no output, or too much output */ + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 1, rate, 32u, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 1, 0, 0, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_HASH, 1, 0, maxOut + 1u, 0}, + /* Absorb with a full-block tail, or asking for output */ + {WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB, 1, rate, 0, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_ABSORB, 1, 0, rate, 0}, + /* Squeeze with input, no output, a partial block, or too much */ + {WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE, 0, 1u, rate, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE, 0, 0, 0, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE, 0, 0, rate + 1u, 0}, + {WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE, 0, 0, maxSqz + rate, 0}, + /* Unknown op */ + {WH_MESSAGE_CRYPTO_SHAKE_OP_SQUEEZE + 1u, 1, 0, 32u, 0}, + }; + int ret; + + ret = _ShakeTestRawRequests( + ctx, v, accepted, sizeof(accepted) / sizeof(accepted[0]), WH_ERROR_OK); + if (ret == WH_ERROR_OK) { + ret = _ShakeTestRawRequests(ctx, v, rejected, + sizeof(rejected) / sizeof(rejected[0]), + WH_ERROR_BADARGS); + } + if (ret == WH_ERROR_OK) { + WH_TEST_PRINT("%s server request checks SUCCESS\n", v->name); + } + return ret; +} + int whTest_Crypto_Shake(whClientContext* ctx) { const uint32_t variantCnt = @@ -626,6 +758,7 @@ int whTest_Crypto_Shake(whClientContext* ctx) const shakeTestVariant* v = &shakeTestVariants[i]; WH_TEST_RETURN_ON_FAIL(_ShakeTestBadArgs(ctx, v)); + WH_TEST_RETURN_ON_FAIL(_ShakeTestServerRequests(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)); From 9b2cb26f487a7fd64f86f3a4a00bc3bb7e73deda Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 2 Oct 2026 20:44:05 -0700 Subject: [PATCH 7/7] Fix scan-build issue --- src/wh_client_crypto.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index ce5a02938..b428b6406 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -9829,7 +9829,7 @@ static int _ShakeUpdateResponse(whClientContext* ctx, wc_Shake* sha, } ret = _ShakeRecvResponse(ctx, v, 0, &res); - if (ret >= 0) { + if (ret == WH_ERROR_OK) { memcpy(sha->s, res->resumeState.s, sizeof(sha->s)); } return ret; @@ -9907,7 +9907,7 @@ static int _ShakeStateResponse(whClientContext* ctx, wc_Shake* sha, } ret = _ShakeRecvResponse(ctx, v, outSz, &res); - if (ret >= 0) { + if (ret == WH_ERROR_OK) { if (outSz > 0) { memcpy(out, (uint8_t*)(res + 1), outSz); }