From 6403f477e066aebbe8bf97fefca080134c3be3ef Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Wed, 22 Jul 2026 11:46:30 +0900 Subject: [PATCH] Bound CMAC response payloads against the received frame --- src/wh_client_crypto.c | 81 ++- .../misc/wh_test_client_malformed_resp.c | 564 ++++++++++++++++++ test-refactor/wh_test_list.c | 2 + 3 files changed, 632 insertions(+), 15 deletions(-) create mode 100644 test-refactor/misc/wh_test_client_malformed_resp.c diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index d0189b33a..497fb7c25 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -5229,9 +5229,17 @@ int wh_Client_CmacGenerateResponse(whClientContext* ctx, Cmac* cmac, ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); /* wolfCrypt allows positive error codes on success */ if (ret >= 0) { - /* Restore state from response (server has finalized; buffer/digest - * carry the post-finalization state). */ - ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* The MAC bytes the response claims must be in the received frame */ + if (res_len < hdr_sz || res->outSz > (res_len - hdr_sz)) { + ret = WH_ERROR_ABORTED; + } + if (ret >= 0) { + /* Restore state from response (server has finalized; buffer/digest + * carry the post-finalization state). */ + ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + } if (ret >= 0) { ret = _CmacValidateTagLen(*outMacLen); } @@ -5349,10 +5357,17 @@ int wh_Client_CmacUpdateResponse(whClientContext* ctx, Cmac* cmac) ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); if (ret >= 0) { - /* Restore full state from server. The server may leave a partial - * (or whole) block in its buffer after wc_CmacUpdate (CMAC's last - * block has special handling), so we round-trip the whole state. */ - ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + /* No trailing payload on update, but the state must be in the frame */ + if (res_len < sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res)) { + ret = WH_ERROR_ABORTED; + } + if (ret >= 0) { + /* Restore full state from server. The server may leave a partial + * (or whole) block in its buffer after wc_CmacUpdate (CMAC's last + * block has special handling), so we round-trip the whole state. */ + ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + } } return ret; } @@ -5439,9 +5454,17 @@ int wh_Client_CmacFinalResponse(whClientContext* ctx, Cmac* cmac, ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); if (ret >= 0) { - /* Restore final state from response (server's bufferSz is 0 after - * Final). */ - ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* The MAC bytes the response claims must be in the received frame */ + if (res_len < hdr_sz || res->outSz > (res_len - hdr_sz)) { + ret = WH_ERROR_ABORTED; + } + if (ret >= 0) { + /* Restore final state from response (server's bufferSz is 0 after + * Final). */ + ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + } if (ret >= 0) { ret = _CmacValidateTagLen(*outMacLen); } @@ -5680,7 +5703,18 @@ int wh_Client_CmacGenerateDmaResponse(whClientContext* ctx, Cmac* cmac, if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); if (ret >= 0) { - ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* The MAC bytes the response claims must be in the received + * frame. A failed server request replies with the generic header + * only, so this bound stays inside the success branch. */ + if (respSz < hdr_sz || res->outSz > (respSz - hdr_sz)) { + ret = WH_ERROR_ABORTED; + } + if (ret >= 0) { + ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, + &res->resumeState); + } if (ret >= 0) { ret = _CmacValidateTagLen(*outMacLen); } @@ -5825,9 +5859,18 @@ int wh_Client_CmacDmaUpdateResponse(whClientContext* ctx, Cmac* cmac) if (ret == WH_ERROR_OK) { ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); if (ret >= 0) { - /* Restore full state from server (includes any partial/whole - * block left in the server's wc_CmacUpdate buffer). */ - ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + /* No trailing payload on update, but the state must be in the + * frame */ + if (respSz < sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res)) { + ret = WH_ERROR_ABORTED; + } + if (ret >= 0) { + /* Restore full state from server (includes any partial/whole + * block left in the server's wc_CmacUpdate buffer). */ + ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, + &res->resumeState); + } } } @@ -5914,7 +5957,15 @@ int wh_Client_CmacDmaFinalResponse(whClientContext* ctx, Cmac* cmac, ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_CMAC, (uint8_t**)&res); if (ret >= 0) { - ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* The MAC bytes the response claims must be in the received frame */ + if (respSz < hdr_sz || res->outSz > (respSz - hdr_sz)) { + ret = WH_ERROR_ABORTED; + } + if (ret >= 0) { + ret = wh_Crypto_CmacAesRestoreStateFromMsg(cmac, &res->resumeState); + } if (ret >= 0) { ret = _CmacValidateTagLen(*outMacLen); } diff --git a/test-refactor/misc/wh_test_client_malformed_resp.c b/test-refactor/misc/wh_test_client_malformed_resp.c new file mode 100644 index 000000000..d14c25776 --- /dev/null +++ b/test-refactor/misc/wh_test_client_malformed_resp.c @@ -0,0 +1,564 @@ +/* + * 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_malformed_resp.c + * + * Client-side hardening against malformed server responses. The client APIs + * exercised here send and receive within one blocking call, so the test + * installs a scripted transport that answers each request with a frame a real + * server would never emit. + */ + +#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 "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" + +#ifdef WOLFSSL_CMAC + +#include "wolfssl/wolfcrypt/cmac.h" + +#define WH_TEST_MR_POISON 0xA5 +#define WH_TEST_MR_TAG_SZ 16 + +/* Bytes a CMAC response spends on headers before the MAC payload */ +#define WH_TEST_MR_CMAC_HDR_SZ \ + ((uint16_t)(sizeof(whMessageCrypto_GenericResponseHeader) + \ + sizeof(whMessageCrypto_CmacAesResponse))) + +#ifdef WOLFHSM_CFG_DMA +#define WH_TEST_MR_CMAC_DMA_HDR_SZ \ + ((uint16_t)(sizeof(whMessageCrypto_GenericResponseHeader) + \ + sizeof(whMessageCrypto_CmacAesDmaResponse))) +#endif + +/* Scripted transport state. Each exchange is described before the client call + * that triggers it, so a response can advertise a MAC the frame does not + * actually carry. */ +typedef struct { + uint8_t request[WH_COMM_MTU]; + uint32_t claimedOutSz; /* value written to res->outSz */ + int32_t rc; /* crypto response header rc */ + uint16_t payloadLen; /* MAC bytes actually written after the response */ + uint16_t frameLen; /* crypto bytes handed back to the client */ + uint8_t dma; /* selects the DMA response layout */ + uint8_t fill; + uint8_t pending; +} whTestMrTransport; + +static int _mrTransportInit(void* context, const void* config, + whCommSetConnectedCb connectcb, void* connectcb_arg) +{ + whTestMrTransport* xport = (whTestMrTransport*)context; + + (void)config; + (void)connectcb; + (void)connectcb_arg; + + if (xport == NULL) { + return WH_ERROR_BADARGS; + } + xport->pending = 0; + return WH_ERROR_OK; +} + +static int _mrTransportSend(void* context, uint16_t size, const void* data) +{ + whTestMrTransport* xport = (whTestMrTransport*)context; + + if ((xport == NULL) || (data == NULL) || (size > sizeof(xport->request))) { + return WH_ERROR_BADARGS; + } + memcpy(xport->request, data, size); + xport->pending = 1; + return WH_ERROR_OK; +} + +/* Answers the pending request with the scripted frame. The body is always + * written whatever frameLen says: under-reporting what was written is what + * makes the frame malformed, and it keeps the two bounds separable. */ +static int _mrTransportRecv(void* context, uint16_t* out_size, void* data) +{ + whTestMrTransport* xport = (whTestMrTransport*)context; + whCommHeader* hdr; + whMessageCrypto_GenericResponseHeader* res_hdr; + whMessageCrypto_CmacAesResponse* res; + uint8_t* mac; +#ifdef WOLFHSM_CFG_DMA + whMessageCrypto_CmacAesDmaResponse* dma_res; +#endif + + if ((xport == NULL) || (out_size == NULL) || (data == NULL)) { + return WH_ERROR_BADARGS; + } + if (xport->pending == 0) { + return WH_ERROR_NOTREADY; + } + xport->pending = 0; + + /* Echo the request header so magic, kind and sequence all match */ + memcpy(data, xport->request, sizeof(whCommHeader)); + hdr = (whCommHeader*)data; + hdr->aux = 0; + + res_hdr = (whMessageCrypto_GenericResponseHeader*)((uint8_t*)data + + sizeof(*hdr)); + res_hdr->algoType = WC_ALGO_TYPE_CMAC; + res_hdr->rc = xport->rc; + res_hdr->reserved = 0; + + mac = NULL; +#ifdef WOLFHSM_CFG_DMA + if (xport->dma != 0) { + dma_res = (whMessageCrypto_CmacAesDmaResponse*)(res_hdr + 1); + memset(dma_res, 0, sizeof(*dma_res)); + dma_res->outSz = xport->claimedOutSz; + dma_res->keyId = WH_KEYID_ERASED; + mac = (uint8_t*)(dma_res + 1); + } + else +#endif + { + res = (whMessageCrypto_CmacAesResponse*)(res_hdr + 1); + memset(res, 0, sizeof(*res)); + res->outSz = xport->claimedOutSz; + res->keyId = WH_KEYID_ERASED; + mac = (uint8_t*)(res + 1); + } + + if (xport->payloadLen > 0) { + memset(mac, xport->fill, xport->payloadLen); + } + + *out_size = (uint16_t)(sizeof(*hdr) + xport->frameLen); + return WH_ERROR_OK; +} + +static int _mrTransportCleanup(void* context) +{ + (void)context; + return WH_ERROR_OK; +} + +/* A CMAC response claiming more MAC bytes than the frame carried must be + * rejected instead of handing the caller whatever trails the response body. */ +static int _whTest_ClientCmacTruncatedResponse(void) +{ + whTestMrTransport xport[1] = {0}; + const whTransportClientCb tccb[1] = {{ + .Init = _mrTransportInit, + .Send = _mrTransportSend, + .Recv = _mrTransportRecv, + .Cleanup = _mrTransportCleanup, + }}; + whCommClientConfig cc_conf[1] = {{ + .transport_cb = tccb, + .transport_context = (void*)xport, + .transport_config = NULL, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + }}; + whClientContext client[1] = {0}; + whClientConfig c_conf[1] = {{ + .comm = cc_conf, + }}; + + Cmac cmac[1]; + uint8_t key[WH_TEST_MR_TAG_SZ]; + uint8_t in[WH_TEST_MR_TAG_SZ]; + uint8_t mac[WH_TEST_MR_TAG_SZ]; + uint32_t macLen = 0; + bool sent = false; + int i = 0; + int rc = 0; + + memset(key, 0x0B, sizeof(key)); + memset(in, 0x0C, sizeof(in)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, c_conf)); + + /* Well-formed oneshot first. The frame carries the MAC it claims, so it + * must be accepted, and it leaves a known pattern in the client packet + * buffer for the malformed exchanges to expose. */ + memset(cmac, 0, sizeof(cmac)); + memset(mac, 0, sizeof(mac)); + xport->dma = 0; + xport->rc = WH_ERROR_OK; + xport->claimedOutSz = WH_TEST_MR_TAG_SZ; + xport->payloadLen = WH_TEST_MR_TAG_SZ; + xport->fill = WH_TEST_MR_POISON; + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ + WH_TEST_MR_TAG_SZ; + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(macLen == WH_TEST_MR_TAG_SZ); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == WH_TEST_MR_POISON); + } + + /* One MAC byte short of the claim: the last byte the client would copy was + * never delivered. The caller's buffer is big enough for the claim, so + * only the frame bound can reject this. */ + memset(cmac, 0, sizeof(cmac)); + memset(mac, 0, sizeof(mac)); + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ + WH_TEST_MR_TAG_SZ - 1; + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* Exactly the claim is still accepted, so the bound cannot be off by one */ + memset(cmac, 0, sizeof(cmac)); + xport->claimedOutSz = WH_TEST_MR_TAG_SZ - 1; + xport->payloadLen = WH_TEST_MR_TAG_SZ - 1; + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ + WH_TEST_MR_TAG_SZ - 1; + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(macLen == WH_TEST_MR_TAG_SZ - 1); + + /* A header-only reply carrying a server error must still surface that + * error rather than the new frame bound */ + memset(cmac, 0, sizeof(cmac)); + xport->rc = WH_ERROR_ACCESS; + xport->payloadLen = 0; + xport->frameLen = (uint16_t)sizeof(whMessageCrypto_GenericResponseHeader); + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ACCESS); + + /* A success reply too short for the response struct: every field the + * client would read, the MAC length included, is stale */ + memset(cmac, 0, sizeof(cmac)); + memset(mac, 0, sizeof(mac)); + xport->rc = WH_ERROR_OK; + xport->claimedOutSz = WH_TEST_MR_TAG_SZ; + xport->payloadLen = WH_TEST_MR_TAG_SZ; + xport->frameLen = (uint16_t)sizeof(whMessageCrypto_GenericResponseHeader); + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* Update carries no MAC, but the state it restores must be in the frame. + * One byte short of the response struct is a stale-state read. */ + memset(cmac, 0, sizeof(cmac)); + xport->rc = WH_ERROR_OK; + xport->claimedOutSz = 0; + xport->payloadLen = 0; + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ - 1; + + rc = wh_Client_CmacUpdateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), &sent); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(sent); + rc = wh_Client_CmacUpdateResponse(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + + /* The whole response struct is accepted */ + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ; + + rc = wh_Client_CmacUpdateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), &sent); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(sent); + rc = wh_Client_CmacUpdateResponse(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + + /* Final claims a MAC the frame never carried */ + memset(mac, 0, sizeof(mac)); + xport->claimedOutSz = WH_TEST_MR_TAG_SZ; + xport->payloadLen = WH_TEST_MR_TAG_SZ; + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ + WH_TEST_MR_TAG_SZ - 1; + macLen = sizeof(mac); + + rc = wh_Client_CmacFinalRequest(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacFinalResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* Final on a success reply too short for the response struct */ + xport->frameLen = (uint16_t)sizeof(whMessageCrypto_GenericResponseHeader); + macLen = sizeof(mac); + + rc = wh_Client_CmacFinalRequest(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacFinalResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* ...and is accepted once the frame carries it */ + xport->frameLen = WH_TEST_MR_CMAC_HDR_SZ + WH_TEST_MR_TAG_SZ; + macLen = sizeof(mac); + + rc = wh_Client_CmacFinalRequest(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacFinalResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(macLen == WH_TEST_MR_TAG_SZ); + + WH_TEST_RETURN_ON_FAIL(wh_Client_Cleanup(client)); + + return WH_ERROR_OK; +} + +#ifdef WOLFHSM_CFG_DMA + +/* The DMA responses return the MAC inline just like the non-DMA ones, so they + * carry the same exposure and the same bound. */ +static int _whTest_ClientCmacDmaTruncatedResponse(void) +{ + whTestMrTransport xport[1] = {0}; + const whTransportClientCb tccb[1] = {{ + .Init = _mrTransportInit, + .Send = _mrTransportSend, + .Recv = _mrTransportRecv, + .Cleanup = _mrTransportCleanup, + }}; + whCommClientConfig cc_conf[1] = {{ + .transport_cb = tccb, + .transport_context = (void*)xport, + .transport_config = NULL, + .client_id = WH_TEST_DEFAULT_CLIENT_ID, + }}; + whClientContext client[1] = {0}; + whClientConfig c_conf[1] = {{ + .comm = cc_conf, + }}; + + Cmac cmac[1]; + uint8_t key[WH_TEST_MR_TAG_SZ]; + uint8_t in[WH_TEST_MR_TAG_SZ]; + uint8_t mac[WH_TEST_MR_TAG_SZ]; + uint32_t macLen = 0; + bool sent = false; + int i = 0; + int rc = 0; + + memset(key, 0x0B, sizeof(key)); + memset(in, 0x0C, sizeof(in)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(client, c_conf)); + + /* Well-formed oneshot leaves the poison pattern behind the response */ + memset(cmac, 0, sizeof(cmac)); + memset(mac, 0, sizeof(mac)); + xport->dma = 1; + xport->rc = WH_ERROR_OK; + xport->claimedOutSz = WH_TEST_MR_TAG_SZ; + xport->payloadLen = WH_TEST_MR_TAG_SZ; + xport->fill = WH_TEST_MR_POISON; + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ + WH_TEST_MR_TAG_SZ; + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateDmaRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateDmaResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(macLen == WH_TEST_MR_TAG_SZ); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == WH_TEST_MR_POISON); + } + + /* One MAC byte short of the claim */ + memset(cmac, 0, sizeof(cmac)); + memset(mac, 0, sizeof(mac)); + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ + WH_TEST_MR_TAG_SZ - 1; + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateDmaRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateDmaResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* Exactly the claim is accepted */ + memset(cmac, 0, sizeof(cmac)); + xport->claimedOutSz = WH_TEST_MR_TAG_SZ - 1; + xport->payloadLen = WH_TEST_MR_TAG_SZ - 1; + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ + WH_TEST_MR_TAG_SZ - 1; + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateDmaRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateDmaResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(macLen == WH_TEST_MR_TAG_SZ - 1); + + /* The DMA dispatcher replies with the generic header alone when a handler + * fails, so that reply must keep returning the server's error */ + memset(cmac, 0, sizeof(cmac)); + xport->rc = WH_ERROR_ACCESS; + xport->payloadLen = 0; + xport->frameLen = (uint16_t)sizeof(whMessageCrypto_GenericResponseHeader); + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateDmaRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateDmaResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ACCESS); + + /* A success reply too short for the response struct */ + memset(cmac, 0, sizeof(cmac)); + memset(mac, 0, sizeof(mac)); + xport->rc = WH_ERROR_OK; + xport->claimedOutSz = WH_TEST_MR_TAG_SZ; + xport->payloadLen = WH_TEST_MR_TAG_SZ; + xport->frameLen = (uint16_t)sizeof(whMessageCrypto_GenericResponseHeader); + macLen = sizeof(mac); + + rc = wh_Client_CmacGenerateDmaRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacGenerateDmaResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* DMA update: state restored from a frame too short to hold it */ + memset(cmac, 0, sizeof(cmac)); + xport->rc = WH_ERROR_OK; + xport->claimedOutSz = 0; + xport->payloadLen = 0; + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ - 1; + + rc = wh_Client_CmacDmaUpdateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), &sent); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(sent); + rc = wh_Client_CmacDmaUpdateResponse(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ; + + rc = wh_Client_CmacDmaUpdateRequest(client, cmac, WC_CMAC_AES, key, + sizeof(key), in, sizeof(in), &sent); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(sent); + rc = wh_Client_CmacDmaUpdateResponse(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + + /* DMA final claims a MAC the frame never carried */ + memset(mac, 0, sizeof(mac)); + xport->claimedOutSz = WH_TEST_MR_TAG_SZ; + xport->payloadLen = WH_TEST_MR_TAG_SZ; + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ + WH_TEST_MR_TAG_SZ - 1; + macLen = sizeof(mac); + + rc = wh_Client_CmacDmaFinalRequest(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacDmaFinalResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + /* DMA final on a success reply too short for the response struct */ + xport->frameLen = (uint16_t)sizeof(whMessageCrypto_GenericResponseHeader); + macLen = sizeof(mac); + + rc = wh_Client_CmacDmaFinalRequest(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacDmaFinalResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_ABORTED); + for (i = 0; i < WH_TEST_MR_TAG_SZ; i++) { + WH_TEST_ASSERT_RETURN(mac[i] == 0); + } + + xport->frameLen = WH_TEST_MR_CMAC_DMA_HDR_SZ + WH_TEST_MR_TAG_SZ; + macLen = sizeof(mac); + + rc = wh_Client_CmacDmaFinalRequest(client, cmac); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + rc = wh_Client_CmacDmaFinalResponse(client, cmac, mac, &macLen); + WH_TEST_ASSERT_RETURN(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(macLen == WH_TEST_MR_TAG_SZ); + + WH_TEST_RETURN_ON_FAIL(wh_Client_Cleanup(client)); + + return WH_ERROR_OK; +} +#endif /* WOLFHSM_CFG_DMA */ + +int whTest_ClientMalformedResp(void* ctx) +{ + (void)ctx; + + WH_TEST_PRINT("Testing client handling of malformed responses...\n"); + WH_TEST_RETURN_ON_FAIL(_whTest_ClientCmacTruncatedResponse()); +#ifdef WOLFHSM_CFG_DMA + WH_TEST_RETURN_ON_FAIL(_whTest_ClientCmacDmaTruncatedResponse()); +#endif + + return WH_ERROR_OK; +} + +#endif /* WOLFSSL_CMAC */ + +#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..6f81042cb 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_ClientMalformedResp); 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_ClientMalformedResp", whTest_ClientMalformedResp }, { "whTest_Comm", whTest_Comm }, { "whTest_Dma", whTest_Dma }, { "whTest_KeystoreReqSize", whTest_KeystoreReqSize },