fix(mbedtls): preserve AES-GCM stream state across multipart updates

This commit is contained in:
Ashish Sharma
2026-09-03 16:09:57 +08:00
parent 0ffb8daf92
commit 613469ffe7
2 changed files with 78 additions and 17 deletions
+74 -17
View File
@@ -314,6 +314,46 @@ static void esp_gcm_ghash(esp_gcm_context *ctx, const unsigned char *x, size_t x
}
}
/* Feed data into the running GHASH one full 16-byte block at a time, carrying
* any sub-block remainder across calls in ctx->ghash_buf.
*/
static void esp_gcm_ghash_buffered(esp_gcm_context *ctx, const unsigned char *data, size_t len)
{
size_t buffered = ctx->ghash_buf_len;
if (len == 0) {
return;
}
if (buffered) {
size_t fill = AES_BLOCK_BYTES - buffered;
if (fill > len) {
fill = len;
}
memcpy(ctx->ghash_buf + buffered, data, fill);
buffered += fill;
data += fill;
len -= fill;
if (buffered == AES_BLOCK_BYTES) {
esp_gcm_ghash(ctx, ctx->ghash_buf, AES_BLOCK_BYTES, ctx->ghash);
buffered = 0;
}
ctx->ghash_buf_len = buffered;
}
size_t full = len - (len % AES_BLOCK_BYTES);
if (full) {
esp_gcm_ghash(ctx, data, full, ctx->ghash);
data += full;
len -= full;
}
if (len) {
memcpy(ctx->ghash_buf, data, len);
ctx->ghash_buf_len = len;
}
}
/* Function to init AES GCM context to zero */
void esp_aes_gcm_init( esp_gcm_context *ctx)
@@ -369,6 +409,12 @@ int esp_aes_gcm_starts( esp_gcm_context *ctx,
ctx->aad = NULL;
ctx->aad_len = 0;
/* Reset the streaming state carried across esp_aes_gcm_update() calls */
ctx->nc_off = 0;
ctx->ghash_buf_len = 0;
memset(ctx->stream_block, 0, sizeof(ctx->stream_block));
memset(ctx->ghash_buf, 0, sizeof(ctx->ghash_buf));
ctx->iv = iv;
ctx->iv_len = iv_len;
ctx->mode = mode;
@@ -432,11 +478,13 @@ int esp_aes_gcm_update_ad( esp_gcm_context *ctx,
return PSA_ERROR_BAD_STATE;
}
/* Initialise associated data */
ctx->aad = aad;
ctx->aad_len = aad_len;
if ( ( ctx->aad_len + aad_len ) >> 29 != 0 ) {
return ( PSA_ERROR_INVALID_ARGUMENT );
}
esp_gcm_ghash(ctx, ctx->aad, ctx->aad_len, ctx->ghash);
/* Accumulate the data across (possibly multiple) calls. */
ctx->aad_len += aad_len;
esp_gcm_ghash_buffered(ctx, aad, aad_len);
return ( 0 );
}
@@ -452,9 +500,7 @@ int esp_aes_gcm_update( esp_gcm_context *ctx,
return -1;
}
size_t nc_off = 0;
uint8_t nonce_counter[AES_BLOCK_BYTES] = {0};
uint8_t stream[AES_BLOCK_BYTES] = {0};
if (!output_length) {
ESP_LOGE(TAG, "No output length supplied");
@@ -471,9 +517,8 @@ int esp_aes_gcm_update( esp_gcm_context *ctx,
return PSA_ERROR_INVALID_ARGUMENT;
}
/* Honor the documented contract: the output buffer must hold input_length bytes, which are
* written unconditionally below; without this check an undersized buffer overflows (CWE-20
* -> CWE-787). MBEDTLS_ERR_GCM_BAD_INPUT is #defined to PSA_ERROR_INVALID_ARGUMENT. */
/* The output buffer must hold input_length bytes, which are
* written unconditionally below; without this check an undersized buffer overflows. */
if ( output_size < input_length ) {
ESP_LOGE(TAG, "Output buffer too small");
return PSA_ERROR_INVALID_ARGUMENT;
@@ -490,6 +535,12 @@ int esp_aes_gcm_update( esp_gcm_context *ctx,
* operation will auto update it
*/
increment32_j0(ctx, nonce_counter);
/* Zero-pad and flush any buffered partial AAD block so the ciphertext
* GHASH starts on a fresh block */
if (ctx->ghash_buf_len) {
esp_gcm_ghash(ctx, ctx->ghash_buf, ctx->ghash_buf_len, ctx->ghash);
ctx->ghash_buf_len = 0;
}
ctx->gcm_state = ESP_AES_GCM_STATE_UPDATE;
} else if (ctx->gcm_state == ESP_AES_GCM_STATE_UPDATE) {
memcpy(nonce_counter, ctx->J0, AES_BLOCK_BYTES);
@@ -497,11 +548,11 @@ int esp_aes_gcm_update( esp_gcm_context *ctx,
/* Perform intermediate GHASH on "encrypted" data during decryption */
if (ctx->mode == ESP_AES_DECRYPT) {
esp_gcm_ghash(ctx, input, input_length, ctx->ghash);
esp_gcm_ghash_buffered(ctx, input, input_length);
}
/* Output = GCTR(J0, Input): Encrypt/Decrypt the input */
int ret = esp_aes_crypt_ctr(&ctx->aes_ctx, input_length, &nc_off, nonce_counter, stream, input, output);
/* Output = GCTR(J0, Input): Encrypt/Decrypt the input. */
int ret = esp_aes_crypt_ctr(&ctx->aes_ctx, input_length, &ctx->nc_off, nonce_counter, ctx->stream_block, input, output);
if (ret == 0) {
/* ICB gets auto incremented after GCTR operation here so update the context */
memcpy(ctx->J0, nonce_counter, AES_BLOCK_BYTES);
@@ -511,14 +562,13 @@ int esp_aes_gcm_update( esp_gcm_context *ctx,
/* Perform intermediate GHASH on "encrypted" data during encryption*/
if (ctx->mode == ESP_AES_ENCRYPT) {
esp_gcm_ghash(ctx, output, input_length, ctx->ghash);
esp_gcm_ghash_buffered(ctx, output, input_length);
}
}
/* stream holds the AES-CTR keystream and nonce_counter the live CTR state;
* both are key-derived secrets. Scrub them on every exit so they cannot be
* recovered from stack RAM. */
mbedtls_platform_zeroize(stream, sizeof(stream));
/* nonce_counter holds the live CTR state (key-derived); scrub the stack
* copy on every exit. The persistent keystream in ctx->stream_block is
* required for the next update and is scrubbed in esp_aes_gcm_free(). */
mbedtls_platform_zeroize(nonce_counter, sizeof(nonce_counter));
return ret;
}
@@ -541,6 +591,13 @@ int esp_aes_gcm_finish( esp_gcm_context *ctx,
return ( PSA_ERROR_INVALID_ARGUMENT );
}
/* Flush the final buffered partial block (zero-padded) into GHASH before
* the length block, completing the ciphertext portion of the hash. */
if (ctx->ghash_buf_len) {
esp_gcm_ghash(ctx, ctx->ghash_buf, ctx->ghash_buf_len, ctx->ghash);
ctx->ghash_buf_len = 0;
}
/* Calculate final GHASH on aad_len, data length */
ESP_PUT_BE64(len_block, ctx->aad_len * 8);
ESP_PUT_BE64(len_block + 8, ctx->data_len * 8);
@@ -40,6 +40,10 @@ typedef struct {
const unsigned char *aad; /*!< The additional data. */
esp_aes_context aes_ctx;
esp_aes_gcm_state gcm_state;
size_t nc_off; /*!< CTR keystream offset carried across update calls. */
uint8_t stream_block[16]; /*!< CTR keystream residual carried across update calls. */
uint8_t ghash_buf[16]; /*!< Partial (< 16 B) block pending GHASH. */
size_t ghash_buf_len; /*!< Number of valid bytes in ghash_buf (0..15). */
/* Software context needed for soft fallback for non-AES ciphers */
void *ctx_soft;
} esp_gcm_context;