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