From 6bc747d5e38f800f2fd5de3198b62893289fbd12 Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Tue, 21 Jul 2026 15:42:15 +0900 Subject: [PATCH] Bound KDF response output size against received frame --- src/wh_client_crypto.c | 16 ++ .../misc/wh_test_client_respbounds.c | 269 ++++++++++++++++++ test-refactor/wh_test_list.c | 2 + 3 files changed, 287 insertions(+) create mode 100644 test-refactor/misc/wh_test_client_respbounds.c diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index d0189b33a..fd39805c9 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -4839,6 +4839,14 @@ static int _HkdfMakeKey(whClientContext* ctx, int hashType, whKeyId keyIdIn, } if (ret == WH_ERROR_OK) { + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* Defensive bound: res->outSz must fit within the actual received + * frame */ + if (res_len < hdr_sz || res->outSz > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + /* Key is cached on server or is ephemeral */ key_id = (whKeyId)(res->keyIdOut); @@ -5001,6 +5009,14 @@ static int _CmacKdfMakeKey(whClientContext* ctx, whKeyId saltKeyId, } if (ret == WH_ERROR_OK) { + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* Defensive bound: res->outSz must fit within the actual received + * frame */ + if (res_len < hdr_sz || res->outSz > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + key_id = (whKeyId)(res->keyIdOut); if (inout_key_id != NULL) { diff --git a/test-refactor/misc/wh_test_client_respbounds.c b/test-refactor/misc/wh_test_client_respbounds.c new file mode 100644 index 000000000..3451421cb --- /dev/null +++ b/test-refactor/misc/wh_test_client_respbounds.c @@ -0,0 +1,269 @@ +/* + * 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/misc/wh_test_client_respbounds.c + * + * Verify the client bounds response data against the received frame. */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/hash.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_comm.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(HAVE_HKDF) || defined(HAVE_CMAC_KDF) + +/* Output buffer the caller offers, and the size the stub server claims to + * have derived. Large enough that the declared size passes the client's + * caller-buffer check and only a frame-length check can reject it. */ +#define WH_TEST_RESPBOUNDS_OUT_SIZE 256 +#define WH_TEST_RESPBOUNDS_BODY_MAX 128 +#define WH_TEST_RESPBOUNDS_POISON 0xA5 +#define WH_TEST_RESPBOUNDS_CASE_COUNT 2 + +/* Body lengths to reply with: a full response header carrying no key material, + * then a frame too short to even hold the response header */ +#define WH_TEST_RESPBOUNDS_LEN_NODATA \ + (uint16_t)(sizeof(whMessageCrypto_GenericResponseHeader) + \ + sizeof(whMessageCrypto_HkdfResponse)) +#define WH_TEST_RESPBOUNDS_LEN_SHORT \ + (uint16_t)(sizeof(whMessageCrypto_GenericResponseHeader) + \ + sizeof(uint32_t)) + +/* Stub transport state: the request header to echo and the canned reply */ +typedef struct { + whCommHeader reqHdr; + uint8_t body[WH_TEST_RESPBOUNDS_BODY_MAX]; + uint16_t bodyLen; + int pending; +} whTestRespBoundsCtx; + +static int _stubInit(void* context, const void* config, + whCommSetConnectedCb connectcb, void* connectcb_arg) +{ + (void)config; + (void)connectcb; + (void)connectcb_arg; + + if (context == NULL) { + return WH_ERROR_BADARGS; + } + ((whTestRespBoundsCtx*)context)->pending = 0; + return WH_ERROR_OK; +} + +/* Capture the request header so the reply can echo its kind and sequence */ +static int _stubSend(void* context, uint16_t size, const void* data) +{ + whTestRespBoundsCtx* ctx = (whTestRespBoundsCtx*)context; + + if ((ctx == NULL) || (data == NULL) || (size < sizeof(whCommHeader))) { + return WH_ERROR_BADARGS; + } + + memcpy(&ctx->reqHdr, data, sizeof(ctx->reqHdr)); + ctx->pending = 1; + return WH_ERROR_OK; +} + +/* Reply with the canned body, reporting only the bytes the test staged */ +static int _stubRecv(void* context, uint16_t* out_size, void* data) +{ + whTestRespBoundsCtx* ctx = (whTestRespBoundsCtx*)context; + whCommHeader* hdr = (whCommHeader*)data; + + if ((ctx == NULL) || (out_size == NULL) || (data == NULL)) { + return WH_ERROR_BADARGS; + } + if (ctx->pending == 0) { + return WH_ERROR_NOTREADY; + } + + memcpy(hdr, &ctx->reqHdr, sizeof(*hdr)); + hdr->aux = WH_COMM_AUX_RESP_OK; + memcpy((uint8_t*)data + sizeof(*hdr), ctx->body, ctx->bodyLen); + + *out_size = (uint16_t)(sizeof(*hdr) + ctx->bodyLen); + ctx->pending = 0; + return WH_ERROR_OK; +} + +static int _stubCleanup(void* context) +{ + (void)context; + return WH_ERROR_OK; +} + +static const whTransportClientCb _stubCb = { + _stubInit, + _stubSend, + _stubRecv, + _stubCleanup, +}; + +/* Stage a KDF response declaring outSz bytes of key material while reporting + * only bodyLen bytes. Both KDF response structs share the same layout. */ +static void _stageKdfResponse(whTestRespBoundsCtx* ctx, uint32_t outSz, + uint16_t bodyLen) +{ + whMessageCrypto_GenericResponseHeader* genHdr = + (whMessageCrypto_GenericResponseHeader*)ctx->body; + whMessageCrypto_HkdfResponse* res = + (whMessageCrypto_HkdfResponse*)(genHdr + 1); + + memset(ctx->body, 0, sizeof(ctx->body)); + genHdr->algoType = WC_ALGO_TYPE_KDF; + genHdr->rc = WH_ERROR_OK; + res->keyIdOut = WH_KEYID_ERASED; + res->outSz = outSz; + ctx->bodyLen = bodyLen; +} + +/* Confirm the client left the caller's buffer alone */ +static int _outputUntouched(const uint8_t* out, uint32_t outSz) +{ + uint32_t i; + + for (i = 0; i < outSz; i++) { + if (out[i] != WH_TEST_RESPBOUNDS_POISON) { + return 0; + } + } + return 1; +} + +#ifdef HAVE_HKDF +static int _whTest_ClientRespBoundsHkdf(void) +{ + const uint16_t bodyLens[WH_TEST_RESPBOUNDS_CASE_COUNT] = { + WH_TEST_RESPBOUNDS_LEN_NODATA, WH_TEST_RESPBOUNDS_LEN_SHORT}; + whTestRespBoundsCtx stub[1] = {{{0, 0, 0, 0}, {0}, 0, 0}}; + whCommClientConfig cc_conf[1] = {{0}}; + whClientConfig c_conf[1] = {{0}}; + whClientContext client[1] = {{0}}; + uint8_t ikm[32]; + uint8_t out[WH_TEST_RESPBOUNDS_OUT_SIZE]; + int ret; + int i; + + cc_conf->transport_cb = &_stubCb; + cc_conf->transport_context = (void*)stub; + cc_conf->client_id = WH_TEST_DEFAULT_CLIENT_ID; + c_conf->comm = cc_conf; + + memset(ikm, 0x0b, sizeof(ikm)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, c_conf)); + + for (i = 0; i < WH_TEST_RESPBOUNDS_CASE_COUNT; i++) { + WH_TEST_PRINT(" HKDF response body length %u\n", + (unsigned int)bodyLens[i]); + memset(out, WH_TEST_RESPBOUNDS_POISON, sizeof(out)); + _stageKdfResponse(stub, WH_TEST_RESPBOUNDS_OUT_SIZE, bodyLens[i]); + + ret = wh_Client_HkdfMakeExportKey(client, WC_SHA256, WH_KEYID_ERASED, + ikm, (uint32_t)sizeof(ikm), NULL, 0, + NULL, 0, out, (uint32_t)sizeof(out)); + + WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED); + WH_TEST_ASSERT_RETURN(_outputUntouched(out, (uint32_t)sizeof(out))); + } + + (void)wh_Client_Cleanup(client); + + return WH_ERROR_OK; +} +#endif /* HAVE_HKDF */ + +#ifdef HAVE_CMAC_KDF +static int _whTest_ClientRespBoundsCmacKdf(void) +{ + const uint16_t bodyLens[WH_TEST_RESPBOUNDS_CASE_COUNT] = { + WH_TEST_RESPBOUNDS_LEN_NODATA, WH_TEST_RESPBOUNDS_LEN_SHORT}; + whTestRespBoundsCtx stub[1] = {{{0, 0, 0, 0}, {0}, 0, 0}}; + whCommClientConfig cc_conf[1] = {{0}}; + whClientConfig c_conf[1] = {{0}}; + whClientContext client[1] = {{0}}; + uint8_t z[32]; + uint8_t out[WH_TEST_RESPBOUNDS_OUT_SIZE]; + int ret; + int i; + + cc_conf->transport_cb = &_stubCb; + cc_conf->transport_context = (void*)stub; + cc_conf->client_id = WH_TEST_DEFAULT_CLIENT_ID; + c_conf->comm = cc_conf; + + memset(z, 0x5c, sizeof(z)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, c_conf)); + + for (i = 0; i < WH_TEST_RESPBOUNDS_CASE_COUNT; i++) { + WH_TEST_PRINT(" CMAC-KDF response body length %u\n", + (unsigned int)bodyLens[i]); + memset(out, WH_TEST_RESPBOUNDS_POISON, sizeof(out)); + _stageKdfResponse(stub, WH_TEST_RESPBOUNDS_OUT_SIZE, bodyLens[i]); + + ret = wh_Client_CmacKdfMakeExportKey( + client, WH_KEYID_ERASED, NULL, 0, WH_KEYID_ERASED, z, + (uint32_t)sizeof(z), NULL, 0, out, (uint32_t)sizeof(out)); + + WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED); + WH_TEST_ASSERT_RETURN(_outputUntouched(out, (uint32_t)sizeof(out))); + } + + (void)wh_Client_Cleanup(client); + + return WH_ERROR_OK; +} +#endif /* HAVE_CMAC_KDF */ + +int whTest_ClientRespBounds(void* ctx) +{ + (void)ctx; + + WH_TEST_PRINT("Testing client response bounds checking...\n"); + +#ifdef HAVE_HKDF + WH_TEST_RETURN_ON_FAIL(_whTest_ClientRespBoundsHkdf()); +#endif +#ifdef HAVE_CMAC_KDF + WH_TEST_RETURN_ON_FAIL(_whTest_ClientRespBoundsCmacKdf()); +#endif + + return WH_ERROR_OK; +} + +#endif /* HAVE_HKDF || HAVE_CMAC_KDF */ + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 70f7d0396..dfbc5c62b 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -41,6 +41,7 @@ /* Test declarations and weak skip implementations. */ WH_TEST_DECL(whTest_ClientDevId); +WH_TEST_DECL(whTest_ClientRespBounds); WH_TEST_DECL(whTest_Comm); WH_TEST_DECL(whTest_Dma); WH_TEST_DECL(whTest_HwKeystore); @@ -99,6 +100,7 @@ WH_TEST_DECL(whTest_AuthRequestAuthorization); const whTestCase whTestsMisc[] = { { "whTest_ClientDevId", whTest_ClientDevId }, + { "whTest_ClientRespBounds", whTest_ClientRespBounds }, { "whTest_Comm", whTest_Comm }, { "whTest_Dma", whTest_Dma }, { "whTest_KeystoreReqSize", whTest_KeystoreReqSize },