diff --git a/docs/src/9-Configuration.md b/docs/src/9-Configuration.md
index 01760f2ac..b111805f5 100644
--- a/docs/src/9-Configuration.md
+++ b/docs/src/9-Configuration.md
@@ -134,6 +134,7 @@ These macros gate and tune DMA-mode crypto and large-buffer operations.
| `WOLFHSM_CFG_DMAADDR_COUNT` | `10` | Number of entries in the DMA address allowlist used by the server to validate client-supplied DMA buffers. |
| `WOLFHSM_CFG_DMA_PTR_SIZE` | Compiler-detected (`__SIZEOF_POINTER__`) | Override the assumed DMA pointer size, in bytes (must be `4` or `8`). Auto-detection works for GCC/Clang and IAR; define this explicitly for any toolchain that does not provide `__SIZEOF_POINTER__`. |
| `WOLFHSM_CFG_DMA_ALT_PTR_SIZE` | Undefined | If defined, allows the DMA pointer size to differ from the native CPU pointer size (e.g. a 32-bit-pointer server reachable from a 64-bit-pointer client). When undefined, wh_settings.h refuses to build with a mismatched `WOLFHSM_CFG_DMA_PTR_SIZE`. |
+| `WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE` | `128` | Largest additional authenticated data, in bytes, that a DMA AEAD request carries inside the message instead of over DMA, letting the AAD live in memory the server cannot address. An AAD above this size is passed as a DMA buffer; `0` sends every AAD over DMA. A client-side policy: the server reads whatever the request carries and never consults this value, so client and server may be built with different ones. Inline AAD does require a server new enough to understand the encoding; an older server rejects such a request with `WH_ERROR_BADARGS`. This is an upper bound only - the AAD shares the message with the request header, IV, tag and key, so a value close to `WOLFHSM_CFG_COMM_DATA_LEN` can never be reached. |
| `WOLFHSM_CFG_DMA_CUSTOM_CLIENT_COPY` | Undefined | If defined, expose hooks that let the integrator override the client-to-server and server-to-client memory copy used during DMA requests. Useful when DMA buffers live in shared memory that requires custom invalidation or cache maintenance beyond the standard `XCACHE*` macros. |
## Authentication
diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c
index 893b1c10b..ecdea4a41 100644
--- a/src/wh_client_crypto.c
+++ b/src/wh_client_crypto.c
@@ -484,7 +484,7 @@ int wh_Client_AesCtrRequest(whClientContext* ctx, Aes* aes, int enc,
len + key_len + AES_IV_SIZE + AES_BLOCK_SIZE;
if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) {
- return WH_ERROR_BADARGS;
+ return WH_ERROR_REQUEST_SIZE;
}
req->enc = enc;
@@ -826,7 +826,7 @@ int wh_Client_AesEcbRequest(whClientContext* ctx, Aes* aes, int enc,
len + key_len;
if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) {
- return WH_ERROR_BADARGS;
+ return WH_ERROR_REQUEST_SIZE;
}
req->enc = enc;
@@ -1159,7 +1159,7 @@ int wh_Client_AesCbcRequest(whClientContext* ctx, Aes* aes, int enc,
len + key_len + iv_len;
if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) {
- return WH_ERROR_BADARGS;
+ return WH_ERROR_REQUEST_SIZE;
}
req->enc = enc;
@@ -1507,7 +1507,7 @@ int wh_Client_AesGcmRequest(whClientContext* ctx, Aes* aes, int enc,
len + key_len + iv_len + authin_len + ((enc == 0) ? tag_len : 0);
if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) {
- return WH_ERROR_BADARGS;
+ return WH_ERROR_REQUEST_SIZE;
}
req->enc = enc;
@@ -1635,15 +1635,17 @@ int wh_Client_AesGcmDmaRequest(whClientContext* ctx, Aes* aes, int enc,
const uint8_t* authin, uint32_t authin_len,
const uint8_t* dec_tag, uint32_t tag_len)
{
- int ret = WH_ERROR_OK;
- whMessageCrypto_AesGcmDmaRequest* req = NULL;
- uint8_t* dataPtr = NULL;
- uintptr_t inAddr = 0;
- uintptr_t outAddr = 0;
- uintptr_t aadAddr = 0;
- bool inAcq = false;
- bool outAcq = false;
- bool aadAcq = false;
+ int ret = WH_ERROR_OK;
+ whMessageCrypto_AesGcmDmaRequest* req = NULL;
+ uint8_t* dataPtr = NULL;
+ uintptr_t inAddr = 0;
+ uintptr_t outAddr = 0;
+ uintptr_t aadAddr = 0;
+ bool inAcq = false;
+ bool outAcq = false;
+ bool aadAcq = false;
+ bool aadInline = false;
+ uint8_t* req_aad;
uint8_t* req_iv;
uint8_t* req_tag;
uint8_t* req_key;
@@ -1687,6 +1689,16 @@ int wh_Client_AesGcmDmaRequest(whClientContext* ctx, Aes* aes, int enc,
req_len += req->keySz;
}
+ /* DMA would demand the AAD live in memory the server can address, which a
+ * TLS record header on the caller's stack often does not. */
+ req_aad = req_key + req->keySz;
+ if ((authin != NULL) && (authin_len > 0) &&
+ (authin_len <= WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE) &&
+ ((req_len + authin_len) <= WOLFHSM_CFG_COMM_DATA_LEN)) {
+ aadInline = true;
+ req_len += authin_len;
+ }
+
if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) {
return WH_ERROR_BADARGS;
}
@@ -1700,6 +1712,9 @@ int wh_Client_AesGcmDmaRequest(whClientContext* ctx, Aes* aes, int enc,
if (req->keySz > 0) {
memcpy(req_key, (const uint8_t*)(aes->devKey), req->keySz);
}
+ if (aadInline) {
+ memcpy(req_aad, authin, authin_len);
+ }
if (in != NULL && len > 0) {
req->input.sz = len;
@@ -1725,12 +1740,18 @@ int wh_Client_AesGcmDmaRequest(whClientContext* ctx, Aes* aes, int enc,
if (ret == WH_ERROR_OK && authin != NULL && authin_len > 0) {
req->aad.sz = authin_len;
- ret = wh_Client_DmaProcessClientAddress(
- ctx, (uintptr_t)authin, (void**)&aadAddr, req->aad.sz,
- WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0});
- if (ret == WH_ERROR_OK) {
- aadAcq = true;
- req->aad.addr = aadAddr;
+ if (aadInline) {
+ /* addr 0 with a non-zero size means "follows the key inline". */
+ req->aad.addr = 0;
+ }
+ else {
+ ret = wh_Client_DmaProcessClientAddress(
+ ctx, (uintptr_t)authin, (void**)&aadAddr, req->aad.sz,
+ WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0});
+ if (ret == WH_ERROR_OK) {
+ aadAcq = true;
+ req->aad.addr = aadAddr;
+ }
}
}
diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c
index 8c0bb0118..4534fe8b7 100644
--- a/src/wh_client_cryptocb.c
+++ b/src/wh_client_cryptocb.c
@@ -160,6 +160,10 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx)
uint8_t* out = info->cipher.aescbc.out;
ret = wh_Client_AesCbc(ctx, aes, enc, in, len, out);
+ if ((ret == WH_ERROR_REQUEST_SIZE) &&
+ WH_KEYID_ISERASED(WH_DEVCTX_TO_KEYID(aes->devCtx))) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ }
} break;
#endif /* HAVE_AES_CBC */
@@ -174,6 +178,10 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx)
uint8_t* out = info->cipher.aesctr.out;
ret = wh_Client_AesCtr(ctx, aes, enc, in, len, out);
+ if ((ret == WH_ERROR_REQUEST_SIZE) &&
+ WH_KEYID_ISERASED(WH_DEVCTX_TO_KEYID(aes->devCtx))) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ }
} break;
#endif /* WOLFSSL_AES_COUNTER */
@@ -188,6 +196,10 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx)
uint8_t* out = info->cipher.aesecb.out;
ret = wh_Client_AesEcb(ctx, aes, enc, in, len, out);
+ if ((ret == WH_ERROR_REQUEST_SIZE) &&
+ WH_KEYID_ISERASED(WH_DEVCTX_TO_KEYID(aes->devCtx))) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ }
} break;
#endif /* HAVE_AES_ECB */
@@ -229,6 +241,10 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx)
ret = wh_Client_AesGcm(ctx, aes, enc, in, len,iv, iv_len,
authin, authin_len, dec_tag, enc_tag, tag_len, out);
+ if ((ret == WH_ERROR_REQUEST_SIZE) &&
+ WH_KEYID_ISERASED(WH_DEVCTX_TO_KEYID(aes->devCtx))) {
+ ret = CRYPTOCB_UNAVAILABLE;
+ }
} break;
#endif /* HAVE_AESGCM */
#endif /* !NO_AES */
diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c
index 23e3b703c..721ea4a03 100644
--- a/src/wh_server_crypto.c
+++ b/src/wh_server_crypto.c
@@ -4258,10 +4258,11 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
whMessageCrypto_AesGcmDmaResponse res;
Aes aes[1] = {0};
- void* inAddr = NULL;
- void* outAddr = NULL;
- void* aadAddr = NULL;
- word32 outSz = 0;
+ void* inAddr = NULL;
+ void* outAddr = NULL;
+ void* aadAddr = NULL;
+ const uint8_t* aadPtr = NULL;
+ word32 outSz = 0;
whKeyId keyId;
uint8_t cachedKey[AES_256_KEY_SIZE];
@@ -4282,12 +4283,25 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
uint32_t enc = req.enc;
uint32_t keyLen = req.keySz;
+ uint32_t reqKeySz = req.keySz;
uint32_t len = req.input.sz;
uint32_t ivLen = req.ivSz;
uint32_t tagLen = req.authTagSz;
- uint64_t needed_size = (uint64_t)sizeof(whMessageCrypto_AesGcmDmaRequest) +
- (uint64_t)keyLen + (uint64_t)ivLen +
- (uint64_t)(enc != 0 ? 0 : tagLen);
+ uint32_t aadLen = 0;
+ int aadInline = ((req.aad.sz > 0) && (req.aad.addr == 0));
+ uint64_t needed_size = 0;
+
+ /* addr 0 with a non-zero size means the AAD follows the key inline; bound
+ * that length here, before it can wrap the sum below. */
+ if (req.aad.sz > (aadInline ? (uint64_t)inSize : (uint64_t)0xFFFFFFFFu)) {
+ return WH_ERROR_BADARGS;
+ }
+ aadLen = (uint32_t)req.aad.sz;
+
+ needed_size = (uint64_t)sizeof(whMessageCrypto_AesGcmDmaRequest) +
+ (uint64_t)keyLen + (uint64_t)ivLen +
+ (uint64_t)(enc != 0 ? 0 : tagLen) +
+ (uint64_t)(aadInline ? aadLen : 0);
if (needed_size != inSize) {
return WH_ERROR_BADARGS;
}
@@ -4352,13 +4366,22 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
}
/* Handle AAD */
- if (ret == WH_ERROR_OK && req.aad.sz > 0) {
- /* Process client address for AAD */
- ret = wh_Server_DmaProcessClientAddress(
- ctx, req.aad.addr, &aadAddr, req.aad.sz,
- WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
- if (ret != WH_ERROR_OK) {
- res.dmaAddrStatus.badAddr = req.aad;
+ if (ret == WH_ERROR_OK && aadLen > 0) {
+ if (aadInline) {
+ /* The wire keySz, not keyLen: a keystore key contributes no bytes
+ * here, but keyLen has already been replaced by its length. */
+ aadPtr = (const uint8_t*)(iv + ivLen + (enc != 0 ? 0 : tagLen) +
+ reqKeySz);
+ }
+ else {
+ /* Process client address for AAD */
+ ret = wh_Server_DmaProcessClientAddress(
+ ctx, req.aad.addr, &aadAddr, aadLen,
+ WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0});
+ if (ret != WH_ERROR_OK) {
+ res.dmaAddrStatus.badAddr = req.aad;
+ }
+ aadPtr = (const uint8_t*)aadAddr;
}
}
@@ -4386,13 +4409,13 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
ret = wc_AesGcmEncrypt(
aes, (byte*)outAddr, (byte*)inAddr, (word32)len,
(byte*)iv, (word32)ivLen, (byte*)out_tag, (word32)tagLen,
- (byte*)aadAddr, (word32)req.aad.sz);
+ (byte*)aadPtr, aadLen);
}
else {
ret = wc_AesGcmDecrypt(
aes, (byte*)outAddr, (byte*)inAddr, (word32)len,
(byte*)iv, (word32)ivLen, (byte*)tag, (word32)tagLen,
- (byte*)aadAddr, (word32)req.aad.sz);
+ (byte*)aadPtr, aadLen);
}
if (ret == WH_ERROR_OK) {
outSz = len;
@@ -4422,7 +4445,7 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId,
}
if (aadAddr != NULL) {
if (wh_Server_DmaProcessClientAddress(
- ctx, req.aad.addr, &aadAddr, req.aad.sz,
+ ctx, req.aad.addr, &aadAddr, aadLen,
WH_DMA_OPER_CLIENT_READ_POST,
(whServerDmaFlags){0}) != WH_ERROR_OK) {
WH_DEBUG_SERVER_VERBOSE(
diff --git a/test/wh_test.c b/test/wh_test.c
index 2d3bc9925..68bab52f0 100644
--- a/test/wh_test.c
+++ b/test/wh_test.c
@@ -46,6 +46,7 @@
#include "wh_test_timeout.h"
#include "wh_test_dma.h"
#include "wh_test_keystore_reqsize.h"
+#include "wh_test_crypto_reqsize.h"
#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
#include "wh_test_auth.h"
#endif /* WOLFHSM_CFG_ENABLE_AUTHENTICATION */
@@ -99,6 +100,9 @@ int whTest_Unit(void)
#if defined(WOLFHSM_CFG_ENABLE_SERVER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
/* Keystore req_size validation */
WH_TEST_ASSERT(0 == whTest_KeystoreReqSize());
+
+ /* Crypto request framing validation */
+ WH_TEST_ASSERT(0 == whTest_CryptoReqSize());
#endif /* WOLFHSM_CFG_ENABLE_SERVER && !WOLFHSM_CFG_NO_CRYPTO */
/* Comm tests */
diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c
index c24040ddb..5a9adaee0 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -10003,6 +10003,357 @@ static int whTestCrypto_Aes(whClientContext* ctx, int devId, WC_RNG* rng)
return ret;
}
+#if !defined(NO_AES) && \
+ (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \
+ defined(HAVE_AES_ECB) || defined(HAVE_AESGCM))
+/* One full comm buffer of plaintext cannot fit alongside the request header,
+ * key and IV, so the non-DMA cryptocb has to choose a fallback or an error. */
+#define WH_TEST_AES_OVERSZ \
+ ((WOLFHSM_CFG_COMM_DATA_LEN / AES_BLOCK_SIZE) * AES_BLOCK_SIZE)
+static uint8_t whTest_AesOverszIn[WH_TEST_AES_OVERSZ];
+static uint8_t whTest_AesOverszOut[WH_TEST_AES_OVERSZ];
+static uint8_t whTest_AesOverszRef[WH_TEST_AES_OVERSZ];
+
+static int whTest_CryptoAesCommBuffer(whClientContext* ctx, int devId,
+ WC_RNG* rng)
+{
+ int ret;
+ Aes aes[1];
+ Aes swAes[1];
+ uint8_t key[AES_128_KEY_SIZE];
+ uint8_t iv[AES_BLOCK_SIZE];
+ int overRet;
+ uint8_t label[WH_NVM_LABEL_LEN] = "AES comm buffer";
+ whKeyId keyId = WH_KEYID_ERASED;
+#ifdef HAVE_AES_CBC
+ int aesInit = 0;
+#endif
+#ifdef HAVE_AESGCM
+ uint8_t swTag[AES_BLOCK_SIZE];
+ uint8_t hsmTag[AES_BLOCK_SIZE];
+#endif
+
+ memset(whTest_AesOverszIn, 0xA5, sizeof(whTest_AesOverszIn));
+ memset(whTest_AesOverszOut, 0, sizeof(whTest_AesOverszOut));
+ memset(whTest_AesOverszRef, 0, sizeof(whTest_AesOverszRef));
+
+ ret = wc_RNG_GenerateBlock(rng, key, sizeof(key));
+ if (ret == 0) {
+ ret = wc_RNG_GenerateBlock(rng, iv, sizeof(iv));
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wc_RNG_GenerateBlock %d\n", ret);
+ return ret;
+ }
+
+#ifdef HAVE_AES_CBC
+ ret = wc_AesInit(swAes, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_AesSetKey(swAes, key, sizeof(key), iv, AES_ENCRYPTION);
+ if (ret == 0) {
+ ret =
+ wc_AesCbcEncrypt(swAes, whTest_AesOverszRef, whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ }
+ (void)wc_AesFree(swAes);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to build AES-CBC reference %d\n", ret);
+ return ret;
+ }
+
+ /* Client-side key: too big to offload is not the same as invalid */
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wc_AesCbcEncrypt(aes, whTest_AesOverszOut, whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Oversized AES-CBC did not fall back %d\n", ret);
+ }
+ else if (memcmp(whTest_AesOverszOut, whTest_AesOverszRef,
+ sizeof(whTest_AesOverszRef)) != 0) {
+ WH_ERROR_PRINT("Oversized AES-CBC fallback mismatch\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_ANY, label, sizeof(label),
+ key, sizeof(key), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache %d\n", ret);
+ return ret;
+ }
+
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ aesInit = 1;
+ /* Install the key locally too, so a fallback would silently succeed */
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ /* The fallback leg above left the same ciphertext in the output */
+ memset(whTest_AesOverszOut, 0, sizeof(whTest_AesOverszOut));
+ ret = wc_AesCbcEncrypt(aes, whTest_AesOverszOut, whTest_AesOverszIn,
+ AES_BLOCK_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Server-key AES-CBC failed %d\n", ret);
+ }
+ else if (memcmp(whTest_AesOverszOut, whTest_AesOverszRef,
+ AES_BLOCK_SIZE) != 0) {
+ WH_ERROR_PRINT("Server-key AES-CBC mismatch\n");
+ ret = -1;
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesSetIV(aes, iv);
+ }
+ if (ret == 0) {
+ overRet =
+ wc_AesCbcEncrypt(aes, whTest_AesOverszOut, whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ if (overRet == 0) {
+ WH_ERROR_PRINT("Oversized AES-CBC bypassed the server key\n");
+ ret = -1;
+ }
+ }
+ if (aesInit) {
+ (void)wc_AesFree(aes);
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+#endif /* HAVE_AES_CBC */
+
+ /* The cryptocb re-derives each mode's trailing-data size to decide whether
+ * the request fits, so every mode needs its own oversized round-trip. */
+#ifdef WOLFSSL_AES_COUNTER
+ if (ret == 0) {
+ ret = wc_AesInit(swAes, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_AesSetKeyDirect(swAes, key, sizeof(key), iv,
+ AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wc_AesCtrEncrypt(swAes, whTest_AesOverszRef,
+ whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ }
+ (void)wc_AesFree(swAes);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to build AES-CTR reference %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesSetKeyDirect(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wc_AesCtrEncrypt(aes, whTest_AesOverszOut,
+ whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Oversized AES-CTR did not fall back %d\n",
+ ret);
+ }
+ else if (memcmp(whTest_AesOverszOut, whTest_AesOverszRef,
+ sizeof(whTest_AesOverszRef)) != 0) {
+ WH_ERROR_PRINT("Oversized AES-CTR fallback mismatch\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ }
+
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_ANY, label,
+ sizeof(label), key, sizeof(key), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ overRet = wc_AesCtrEncrypt(aes, whTest_AesOverszOut,
+ whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ if (overRet == 0) {
+ WH_ERROR_PRINT(
+ "Oversized AES-CTR bypassed the server key\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+#endif /* WOLFSSL_AES_COUNTER */
+
+#ifdef HAVE_AES_ECB
+ if (ret == 0) {
+ ret = wc_AesInit(swAes, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_AesSetKey(swAes, key, sizeof(key), NULL, AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wc_AesEcbEncrypt(swAes, whTest_AesOverszRef,
+ whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ }
+ (void)wc_AesFree(swAes);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to build AES-ECB reference %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), NULL, AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wc_AesEcbEncrypt(aes, whTest_AesOverszOut,
+ whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Oversized AES-ECB did not fall back %d\n",
+ ret);
+ }
+ else if (memcmp(whTest_AesOverszOut, whTest_AesOverszRef,
+ sizeof(whTest_AesOverszRef)) != 0) {
+ WH_ERROR_PRINT("Oversized AES-ECB fallback mismatch\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ }
+
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_ANY, label,
+ sizeof(label), key, sizeof(key), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesSetKey(aes, key, sizeof(key), NULL,
+ AES_ENCRYPTION);
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ overRet = wc_AesEcbEncrypt(aes, whTest_AesOverszOut,
+ whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn));
+ if (overRet == 0) {
+ WH_ERROR_PRINT(
+ "Oversized AES-ECB bypassed the server key\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+#endif /* HAVE_AES_ECB */
+
+#ifdef HAVE_AESGCM
+ if (ret == 0) {
+ ret = wc_AesInit(swAes, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_AesGcmSetKey(swAes, key, sizeof(key));
+ if (ret == 0) {
+ ret = wc_AesGcmEncrypt(
+ swAes, whTest_AesOverszRef, whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn), iv, sizeof(iv), swTag,
+ sizeof(swTag), iv, sizeof(iv));
+ }
+ (void)wc_AesFree(swAes);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to build AES-GCM reference %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesGcmSetKey(aes, key, sizeof(key));
+ if (ret == 0) {
+ ret = wc_AesGcmEncrypt(
+ aes, whTest_AesOverszOut, whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn), iv, sizeof(iv), hsmTag,
+ sizeof(hsmTag), iv, sizeof(iv));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Oversized AES-GCM did not fall back %d\n",
+ ret);
+ }
+ else if ((memcmp(whTest_AesOverszOut, whTest_AesOverszRef,
+ sizeof(whTest_AesOverszRef)) != 0) ||
+ (memcmp(hsmTag, swTag, sizeof(swTag)) != 0)) {
+ WH_ERROR_PRINT("Oversized AES-GCM fallback mismatch\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ }
+
+ if (ret == 0) {
+ keyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_USAGE_ANY, label,
+ sizeof(label), key, sizeof(key), &keyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to wh_Client_KeyCache %d\n", ret);
+ }
+ }
+ if (ret == 0) {
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesGcmSetKey(aes, key, sizeof(key));
+ if (ret == 0) {
+ ret = wh_Client_AesSetKeyId(aes, keyId);
+ }
+ if (ret == 0) {
+ overRet = wc_AesGcmEncrypt(
+ aes, whTest_AesOverszOut, whTest_AesOverszIn,
+ sizeof(whTest_AesOverszIn), iv, sizeof(iv), hsmTag,
+ sizeof(hsmTag), iv, sizeof(iv));
+ if (overRet == 0) {
+ WH_ERROR_PRINT(
+ "Oversized AES-GCM bypassed the server key\n");
+ ret = -1;
+ }
+ }
+ (void)wc_AesFree(aes);
+ }
+ (void)wh_Client_KeyEvict(ctx, keyId);
+ }
+#endif /* HAVE_AESGCM */
+
+ if (ret == 0) {
+ WH_TEST_PRINT("AES COMM BUFFER DEVID=0x%X SUCCESS\n", devId);
+ }
+ return ret;
+}
+#endif /* !NO_AES && HAVE_AES_CBC */
+
/* Direct exercise of the native async AES primitives
* (wh_Client_AesXxxRequest / wh_Client_AesXxxResponse).
* Covers each mode's round-trip, state continuity, and argument rejection. */
@@ -11286,51 +11637,98 @@ static int whTest_CryptoAesDmaAsync(whClientContext* ctx, int devId,
#endif /* HAVE_AES_ECB */
#ifdef HAVE_AESGCM
- /* GCM DMA: round-trip with AAD via DMA */
+ /* GCM DMA: round-trip at the inline-AAD cap and one byte past it, so both
+ * forms of the AAD are exercised unless the cap is configured to 0. */
+#if WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE == 0
+ WH_TEST_PRINT("AES GCM DMA ASYNC: inline AAD disabled by config, only the "
+ "DMA form of the AAD is covered\n");
+#endif
if (ret == 0) {
- uint8_t authin[32];
- uint8_t enc_tag[AES_BLOCK_SIZE];
- uint8_t dec_tag[AES_BLOCK_SIZE];
+ Aes swAes[1];
+ uint8_t authin[WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE + 1];
+ uint8_t enc_tag[AES_BLOCK_SIZE];
+ uint8_t dec_tag[AES_BLOCK_SIZE];
+ uint8_t refCipher[WH_TEST_AES_ASYNC_DMA_BUFSZ];
+ uint8_t refTag[AES_BLOCK_SIZE];
+ uint32_t aadSz[2] = {WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE,
+ WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE + 1};
+ size_t i;
memset(authin, 0x5A, sizeof(authin));
- memset(enc_tag, 0, sizeof(enc_tag));
- ret = wc_AesInit(aes, NULL, devId);
- if (ret == 0) {
- ret = wc_AesGcmSetKey(aes, key, sizeof(key));
- }
- if (ret == 0) {
- ret = wh_Client_AesGcmDmaRequest(
- ctx, aes, 1, plainIn, sizeof(plainIn), cipher, iv,
- AES_BLOCK_SIZE, authin, sizeof(authin), NULL, sizeof(enc_tag));
- }
- if (ret == 0) {
- do {
- ret = wh_Client_AesGcmDmaResponse(ctx, aes, enc_tag,
- sizeof(enc_tag));
- } while (ret == WH_ERROR_NOTREADY);
- }
+ for (i = 0; i < 2 && ret == 0; i++) {
+ memset(enc_tag, 0, sizeof(enc_tag));
+ memset(refCipher, 0, sizeof(refCipher));
+ memset(refTag, 0, sizeof(refTag));
- if (ret == 0) {
- memcpy(dec_tag, enc_tag, sizeof(dec_tag));
- ret = wh_Client_AesGcmDmaRequest(
- ctx, aes, 0, cipher, sizeof(cipher), plainOut, iv,
- AES_BLOCK_SIZE, authin, sizeof(authin), dec_tag,
- sizeof(dec_tag));
- }
- if (ret == 0) {
- do {
- ret = wh_Client_AesGcmDmaResponse(ctx, aes, NULL, 0);
- } while (ret == WH_ERROR_NOTREADY);
- }
- if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
- WH_ERROR_PRINT("AES-GCM DMA async round-trip mismatch\n");
- ret = -1;
+ /* Encrypt and decrypt carry the same AAD, so only a reference tag
+ * catches an AAD the server read at the wrong offset or length. */
+ ret = wc_AesInit(swAes, NULL, INVALID_DEVID);
+ if (ret == 0) {
+ ret = wc_AesGcmSetKey(swAes, key, sizeof(key));
+ if (ret == 0) {
+ ret = wc_AesGcmEncrypt(swAes, refCipher, plainIn,
+ sizeof(plainIn), iv, AES_BLOCK_SIZE,
+ refTag, sizeof(refTag), authin,
+ aadSz[i]);
+ }
+ (void)wc_AesFree(swAes);
+ }
+ if (ret != 0) {
+ WH_ERROR_PRINT("AES-GCM DMA async reference failed %d\n", ret);
+ break;
+ }
+
+ ret = wc_AesInit(aes, NULL, devId);
+ if (ret == 0) {
+ ret = wc_AesGcmSetKey(aes, key, sizeof(key));
+ }
+ if (ret == 0) {
+ ret = wh_Client_AesGcmDmaRequest(
+ ctx, aes, 1, plainIn, sizeof(plainIn), cipher, iv,
+ AES_BLOCK_SIZE, authin, aadSz[i], NULL, sizeof(enc_tag));
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_AesGcmDmaResponse(ctx, aes, enc_tag,
+ sizeof(enc_tag));
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0 && memcmp(cipher, refCipher, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-GCM DMA async cipher mismatch, aadSz=%u\n",
+ (unsigned int)aadSz[i]);
+ ret = -1;
+ }
+ if (ret == 0 && memcmp(enc_tag, refTag, sizeof(refTag)) != 0) {
+ WH_ERROR_PRINT("AES-GCM DMA async tag mismatch, aadSz=%u\n",
+ (unsigned int)aadSz[i]);
+ ret = -1;
+ }
+
+ if (ret == 0) {
+ memcpy(dec_tag, enc_tag, sizeof(dec_tag));
+ ret = wh_Client_AesGcmDmaRequest(
+ ctx, aes, 0, cipher, sizeof(cipher), plainOut, iv,
+ AES_BLOCK_SIZE, authin, aadSz[i], dec_tag,
+ sizeof(dec_tag));
+ }
+ if (ret == 0) {
+ do {
+ ret = wh_Client_AesGcmDmaResponse(ctx, aes, NULL, 0);
+ } while (ret == WH_ERROR_NOTREADY);
+ }
+ if (ret == 0 && memcmp(plainIn, plainOut, sizeof(plainIn)) != 0) {
+ WH_ERROR_PRINT("AES-GCM DMA async round-trip mismatch, "
+ "aadSz=%u\n",
+ (unsigned int)aadSz[i]);
+ ret = -1;
+ }
+ (void)wc_AesFree(aes);
+ memset(cipher, 0, sizeof(cipher));
+ memset(plainOut, 0, sizeof(plainOut));
}
- (void)wc_AesFree(aes);
- memset(cipher, 0, sizeof(cipher));
- memset(plainOut, 0, sizeof(plainOut));
}
+
if (ret == 0) {
WH_TEST_PRINT("AES GCM DMA ASYNC DEVID=0x%X SUCCESS\n", devId);
}
@@ -17903,6 +18301,14 @@ int whTest_CryptoClientConfig(whClientConfig* config)
i++;
}
}
+#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \
+ defined(HAVE_AES_ECB) || defined(HAVE_AESGCM)
+ if (ret == WH_ERROR_OK) {
+ /* The comm buffer bounds the non-DMA path only */
+ (void)wh_Client_SetDmaMode(client, 0);
+ ret = whTest_CryptoAesCommBuffer(client, WH_CLIENT_DEVID(client), rng);
+ }
+#endif
#ifdef WOLFHSM_CFG_DMA
/* Dedicated async DMA tests drive the wh_Client_*Dma APIs directly; prefer
* DMA so any wolfCrypt-routed operations also take the DMA path. */
diff --git a/test/wh_test_crypto_reqsize.c b/test/wh_test_crypto_reqsize.c
new file mode 100644
index 000000000..18a894d0f
--- /dev/null
+++ b/test/wh_test_crypto_reqsize.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2025 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/wh_test_crypto_reqsize.c
+ *
+ * Unit tests to verify _HandleAesGcmDma validates the declared inline-AAD
+ * length against the received message, preventing reads past the packet.
+ *
+ * The client always builds a self-consistent frame, so these cases can only
+ * be produced by driving wh_Server_HandleCryptoDmaRequest directly with a
+ * hand-built packet.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_message.h"
+#include "wolfhsm/wh_server.h"
+#include "wolfhsm/wh_transport_mem.h"
+#include "wolfhsm/wh_nvm.h"
+#include "wolfhsm/wh_nvm_flash.h"
+#include "wolfhsm/wh_flash_ramsim.h"
+
+#if !defined(WOLFHSM_CFG_NO_CRYPTO)
+#include "wolfssl/wolfcrypt/settings.h"
+#include "wolfssl/wolfcrypt/types.h"
+#include "wolfssl/wolfcrypt/aes.h"
+#include "wolfhsm/wh_message_crypto.h"
+#include "wolfhsm/wh_server_crypto.h"
+#endif
+
+#include "wh_test_common.h"
+
+#if defined(WOLFHSM_CFG_ENABLE_SERVER) && !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_DMA) && defined(HAVE_AESGCM)
+
+#define BUFFER_SIZE 4096
+#define FLASH_RAM_SIZE (1024 * 1024)
+#define FLASH_SECTOR_SIZE (128 * 1024)
+#define FLASH_PAGE_SIZE 8
+
+typedef struct {
+ whServerContext server[1];
+ whNvmContext nvm[1];
+ whServerCryptoContext crypto[1];
+
+ uint8_t reqBuf[BUFFER_SIZE];
+ uint8_t respBuf[BUFFER_SIZE];
+ whTransportMemConfig tmcf[1];
+ whTransportServerCb tscb[1];
+ whTransportMemServerContext tmsc[1];
+ whCommServerConfig cs_conf[1];
+
+ whFlashRamsimCtx fc[1];
+ whFlashRamsimCfg fc_conf[1];
+ whFlashCb fcb[1];
+ whNvmFlashConfig nf_conf[1];
+ whNvmFlashContext nfc[1];
+ whNvmCb nfcb[1];
+ whNvmConfig n_conf[1];
+ whServerConfig s_conf[1];
+} TestCtx;
+
+/* Flash memory is static to avoid 1MB on the stack */
+static uint8_t _flashMemory[FLASH_RAM_SIZE];
+
+static int _SetupServer(TestCtx* ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ memset(_flashMemory, 0, sizeof(_flashMemory));
+
+ ctx->tmcf[0] = (whTransportMemConfig){
+ .req = (whTransportMemCsr*)ctx->reqBuf,
+ .req_size = sizeof(ctx->reqBuf),
+ .resp = (whTransportMemCsr*)ctx->respBuf,
+ .resp_size = sizeof(ctx->respBuf),
+ };
+ ctx->tscb[0] = (whTransportServerCb)WH_TRANSPORT_MEM_SERVER_CB;
+ ctx->cs_conf[0] = (whCommServerConfig){
+ .transport_cb = ctx->tscb,
+ .transport_context = (void*)ctx->tmsc,
+ .transport_config = (void*)ctx->tmcf,
+ .server_id = 125,
+ };
+
+ ctx->fc_conf[0] = (whFlashRamsimCfg){
+ .size = FLASH_RAM_SIZE,
+ .sectorSize = FLASH_SECTOR_SIZE,
+ .pageSize = FLASH_PAGE_SIZE,
+ .erasedByte = ~(uint8_t)0,
+ .memory = _flashMemory,
+ };
+ ctx->fcb[0] = (whFlashCb)WH_FLASH_RAMSIM_CB;
+ ctx->nf_conf[0] = (whNvmFlashConfig){
+ .cb = ctx->fcb,
+ .context = ctx->fc,
+ .config = ctx->fc_conf,
+ };
+ ctx->nfcb[0] = (whNvmCb)WH_NVM_FLASH_CB;
+ ctx->n_conf[0] = (whNvmConfig){
+ .cb = ctx->nfcb,
+ .context = ctx->nfc,
+ .config = ctx->nf_conf,
+ };
+
+ ctx->s_conf[0] = (whServerConfig){
+ .comm_config = ctx->cs_conf,
+ .nvm = ctx->nvm,
+ .crypto = ctx->crypto,
+ };
+
+ WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(ctx->nvm, ctx->n_conf));
+ WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init());
+ WH_TEST_RETURN_ON_FAIL(
+ wc_InitRng_ex(ctx->crypto->rng, NULL, INVALID_DEVID));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_Init(ctx->server, ctx->s_conf));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Server_SetConnected(ctx->server, WH_COMM_CONNECTED));
+ return 0;
+}
+
+static void _CleanupServer(TestCtx* ctx)
+{
+ (void)wh_Server_Cleanup(ctx->server);
+ (void)wh_Nvm_Cleanup(ctx->nvm);
+ (void)wc_FreeRng(ctx->crypto->rng);
+ (void)wolfCrypt_Cleanup();
+}
+
+#define WH_TEST_GCM_KEY_SZ AES_128_KEY_SIZE
+#define WH_TEST_GCM_IV_SZ 12
+#define WH_TEST_GCM_TAG_SZ AES_BLOCK_SIZE
+#define WH_TEST_GCM_AAD_SZ 16
+
+/* Lay out a well-formed encrypt request carrying aadBytes of inline AAD after
+ * the key, and return the size of the frame actually written. */
+static uint16_t _BuildInlineAadRequest(uint8_t* req_packet, uint32_t aadBytes)
+{
+ whMessageCrypto_GenericRequestHeader* hdr =
+ (whMessageCrypto_GenericRequestHeader*)req_packet;
+ whMessageCrypto_AesGcmDmaRequest* req =
+ (whMessageCrypto_AesGcmDmaRequest*)(req_packet + sizeof(*hdr));
+
+ memset(req_packet, 0, WOLFHSM_CFG_COMM_DATA_LEN);
+
+ hdr->algoType = WC_CIPHER_AES_GCM;
+ hdr->algoSubType = WH_MESSAGE_CRYPTO_ALGO_SUBTYPE_NONE;
+ hdr->affinity = 0;
+
+ req->enc = 1;
+ req->keySz = WH_TEST_GCM_KEY_SZ;
+ req->ivSz = WH_TEST_GCM_IV_SZ;
+ req->authTagSz = WH_TEST_GCM_TAG_SZ;
+ req->input.sz = 0;
+ req->input.addr = 0;
+ req->output.sz = 0;
+ req->output.addr = 0;
+ /* addr 0 with a non-zero size is the inline-AAD encoding */
+ req->aad.addr = 0;
+ req->aad.sz = aadBytes;
+
+ return (uint16_t)(sizeof(*hdr) + sizeof(*req) + WH_TEST_GCM_KEY_SZ +
+ WH_TEST_GCM_IV_SZ + aadBytes);
+}
+
+/* Overwrite the declared sizes so the header disagrees with the frame. */
+static void _DeclareSizes(uint8_t* req_packet, uint32_t ivSz, uint64_t aadSz)
+{
+ whMessageCrypto_AesGcmDmaRequest* req =
+ (whMessageCrypto_AesGcmDmaRequest*)(req_packet +
+ sizeof(whMessageCrypto_GenericRequestHeader));
+
+ req->ivSz = ivSz;
+ req->aad.sz = aadSz;
+}
+
+static int _Dispatch(TestCtx* ctx, uint8_t* req_packet, uint16_t req_size,
+ uint8_t* resp_packet)
+{
+ uint16_t resp_size = 0;
+
+ return wh_Server_HandleCryptoDmaRequest(
+ ctx->server, WH_COMM_MAGIC_NATIVE, WC_ALGO_TYPE_CIPHER, 0, req_size,
+ req_packet, &resp_size, resp_packet);
+}
+
+static int wh_Crypto_TestAesGcmDmaAadFraming(void)
+{
+ TestCtx ctx[1];
+ uint8_t req_packet[WOLFHSM_CFG_COMM_DATA_LEN];
+ uint8_t resp_packet[WOLFHSM_CFG_COMM_DATA_LEN];
+ uint16_t req_size;
+ uint64_t craft;
+ int ret;
+
+ WH_TEST_RETURN_ON_FAIL(_SetupServer(ctx));
+
+ /* Test 1: the frame declares an inline AAD, but req_size stops short of
+ * the AAD bytes it claims to carry. */
+ req_size = _BuildInlineAadRequest(req_packet, WH_TEST_GCM_AAD_SZ);
+ ret = _Dispatch(ctx, req_packet, (uint16_t)(req_size - WH_TEST_GCM_AAD_SZ),
+ resp_packet);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Short inline-AAD frame not rejected: %d\n", ret);
+ _CleanupServer(ctx);
+ return -1;
+ }
+
+ /* Test 2: a declared inline AAD larger than the message carrying it. */
+ req_size = _BuildInlineAadRequest(req_packet, WH_TEST_GCM_AAD_SZ);
+ _DeclareSizes(req_packet, WH_TEST_GCM_IV_SZ, (uint64_t)req_size + 1);
+ ret = _Dispatch(ctx, req_packet, req_size, resp_packet);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Oversized inline-AAD length not rejected: %d\n", ret);
+ _CleanupServer(ctx);
+ return -1;
+ }
+
+ /* Test 3: a declared length whose low 32 bits match the frame exactly, so
+ * only a check made before the truncation to uint32_t rejects it. */
+ req_size = _BuildInlineAadRequest(req_packet, WH_TEST_GCM_AAD_SZ);
+ _DeclareSizes(req_packet, WH_TEST_GCM_IV_SZ,
+ (uint64_t)0x100000000ull + WH_TEST_GCM_AAD_SZ);
+ ret = _Dispatch(ctx, req_packet, req_size, resp_packet);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Truncating inline-AAD length not rejected: %d\n", ret);
+ _CleanupServer(ctx);
+ return -1;
+ }
+
+ /* Test 4: the length the unbounded 64-bit sum would have wrapped back onto
+ * req_size, paired with an ivSz the equality check would then not bound. */
+ req_size = _BuildInlineAadRequest(req_packet, WH_TEST_GCM_AAD_SZ);
+ craft = (uint64_t)(req_size -
+ sizeof(whMessageCrypto_GenericRequestHeader)) -
+ ((uint64_t)sizeof(whMessageCrypto_AesGcmDmaRequest) +
+ (uint64_t)WH_TEST_GCM_KEY_SZ + (uint64_t)0x40000000u);
+ _DeclareSizes(req_packet, 0x40000000u, craft);
+ ret = _Dispatch(ctx, req_packet, req_size, resp_packet);
+ if (ret != WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Wrapped inline-AAD length not rejected: %d\n", ret);
+ _CleanupServer(ctx);
+ return -1;
+ }
+
+ /* Test 5: the correctly framed encoding must clear the framing check
+ * rather than being rejected as malformed. */
+ req_size = _BuildInlineAadRequest(req_packet, WH_TEST_GCM_AAD_SZ);
+ ret = _Dispatch(ctx, req_packet, req_size, resp_packet);
+ if (ret == WH_ERROR_BADARGS) {
+ WH_ERROR_PRINT("Well-formed inline-AAD frame rejected\n");
+ _CleanupServer(ctx);
+ return -1;
+ }
+
+ WH_TEST_PRINT("AES-GCM DMA inline-AAD framing tests: ALL PASSED\n");
+
+ _CleanupServer(ctx);
+ return 0;
+}
+
+int whTest_CryptoReqSize(void)
+{
+ WH_TEST_PRINT("Testing AES-GCM DMA request framing validation...\n");
+ return wh_Crypto_TestAesGcmDmaAadFraming();
+}
+
+#else /* server && !no-crypto && DMA && AESGCM */
+
+int whTest_CryptoReqSize(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/test/wh_test_crypto_reqsize.h b/test/wh_test_crypto_reqsize.h
new file mode 100644
index 000000000..70416532e
--- /dev/null
+++ b/test/wh_test_crypto_reqsize.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 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 .
+ */
+#ifndef WH_TEST_CRYPTO_REQSIZE_H_
+#define WH_TEST_CRYPTO_REQSIZE_H_
+
+int whTest_CryptoReqSize(void);
+
+#endif /* WH_TEST_CRYPTO_REQSIZE_H_ */
diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h
index 9a0e921c6..eeeba1b3d 100644
--- a/wolfhsm/wh_client_crypto.h
+++ b/wolfhsm/wh_client_crypto.h
@@ -2087,11 +2087,14 @@ int wh_Client_AesGcmResponse(whClientContext* ctx, Aes* aes, uint8_t* out,
/**
* @brief Send an AES-GCM DMA request to the server (non-blocking)
*
- * Performs PRE address translation for the input, output, and AAD buffers,
- * stashes the translated addresses in ctx->dma.asyncCtx.aes for POST
- * cleanup, and sends the DMA request to the server. Does NOT wait for a
- * reply. The IV, auth tag (for decrypt), and key are passed inline. Caller
- * must keep in, out, and authin valid until the matching
+ * Performs PRE address translation for the input and output buffers, stashes
+ * the translated addresses in ctx->dma.asyncCtx.aes for POST cleanup, and
+ * sends the DMA request to the server. Does NOT wait for a reply. The IV,
+ * auth tag (for decrypt), and key are passed inline. An AAD of at most
+ * WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE bytes is copied inline as well, and is
+ * therefore neither address-translated nor passed to the DMA callbacks; a
+ * larger AAD is translated and cleaned up like the other buffers. Caller must
+ * keep in, out, and authin valid until the matching
* wh_Client_AesGcmDmaResponse completes.
*
* Contract: at most one outstanding async request may be in flight per
@@ -2115,8 +2118,9 @@ int wh_Client_AesGcmDmaRequest(whClientContext* ctx, Aes* aes, int enc,
* Single-shot RecvResponse; returns WH_ERROR_NOTREADY if the server has not
* yet replied. The output data is written by the server directly to the
* client buffer passed to wh_Client_AesGcmDmaRequest; for encrypt the auth
- * tag is returned inline and copied into enc_tag. POST DMA cleanup for
- * input, output, and AAD buffers is performed on every non-NOTREADY return.
+ * tag is returned inline and copied into enc_tag. POST DMA cleanup is
+ * performed on every non-NOTREADY return for the input and output buffers,
+ * and for the AAD buffer when it was passed over DMA rather than inline.
*
* @param[in] ctx Pointer to the client context
* @param[in] aes Pointer to the AES structure
diff --git a/wolfhsm/wh_error.h b/wolfhsm/wh_error.h
index 5ca7096c7..e1dcce0e5 100644
--- a/wolfhsm/wh_error.h
+++ b/wolfhsm/wh_error.h
@@ -51,6 +51,9 @@ enum WH_ERROR_ENUM {
the comm layer. Unlike NOTREADY, retrying the same call will
not clear this -- drain the pending response or call
wh_CommClient_AbortPending before issuing a new request. */
+ WH_ERROR_REQUEST_SIZE =
+ -2012, /* Request does not fit WOLFHSM_CFG_COMM_DATA_LEN. No side
+ effects: nothing was sent and no local state changed. */
/* NVM and keystore specific status returns */
WH_ERROR_LOCKED = -2100, /* Unlock and retry if necessary */
diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h
index 809513bc9..1e73a0d5c 100644
--- a/wolfhsm/wh_message_crypto.h
+++ b/wolfhsm/wh_message_crypto.h
@@ -1582,6 +1582,7 @@ typedef struct {
* uint8_t iv[ivSz]
* uint8_t authTag[authTagSz]
* uint8_t key[keySz]
+ * uint8_t aad[aad.sz] only when aad.addr == 0
*/
} whMessageCrypto_AesGcmDmaRequest;
diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h
index f9974d905..fcdd45b5d 100644
--- a/wolfhsm/wh_settings.h
+++ b/wolfhsm/wh_settings.h
@@ -136,6 +136,14 @@
* operation in DMA requests.
* Default: Not defined
*
+ * WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE - Largest AAD, in bytes, that a DMA
+ * AEAD request carries inside the message instead of over DMA. An upper bound
+ * only, since the AAD shares the message with the request header, IV, tag and
+ * key. Client-side policy: the server reads whatever the request carries and
+ * never consults this value, though inline AAD does require a server new
+ * enough to understand it. 0 sends every AAD over DMA.
+ * Default: 128
+ *
* WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS - Maximum number of trusted root NVM IDs
* accepted in a single wh_Server_CertVerifyMultiRoot request. Bounded so the
* non-DMA wire request fits within WOLFHSM_CFG_COMM_DATA_LEN alongside the
@@ -711,6 +719,16 @@
#endif
#endif
+/* Largest AAD a DMA AEAD request carries inline rather than over DMA. An upper
+ * bound only: the AAD shares the message with the header, IV, tag and key. */
+#ifndef WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE
+#define WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE 128
+#endif
+
+#if WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE > WOLFHSM_CFG_COMM_DATA_LEN
+#error "WOLFHSM_CFG_DMA_INLINE_AAD_MAX_SIZE exceeds WOLFHSM_CFG_COMM_DATA_LEN"
+#endif
+
#endif /* WOLFHSM_CFG_DMA */
#endif /* !WOLFHSM_WH_SETTINGS_H_ */