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_ */