diff --git a/components/mbedtls/port/aes/esp_aes_gcm.c b/components/mbedtls/port/aes/esp_aes_gcm.c index 845785ab914..ee44a816c2f 100644 --- a/components/mbedtls/port/aes/esp_aes_gcm.c +++ b/components/mbedtls/port/aes/esp_aes_gcm.c @@ -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); diff --git a/components/mbedtls/port/include/aes/esp_aes_gcm.h b/components/mbedtls/port/include/aes/esp_aes_gcm.h index dd89f39205b..438c570ef91 100644 --- a/components/mbedtls/port/include/aes/esp_aes_gcm.h +++ b/components/mbedtls/port/include/aes/esp_aes_gcm.h @@ -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;