mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 10:40:47 +03:00
fix(mbedtls): preserve AES-GCM stream state across multipart updates
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user