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/dynamic/esp_mbedtls_dynamic_impl.c b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c index dec0ca78626..f608f2fb8a6 100644 --- a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c +++ b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c @@ -5,6 +5,8 @@ */ #include +#include +#include #include "esp_mbedtls_dynamic_impl.h" #include "sdkconfig.h" @@ -85,7 +87,7 @@ static void init_tx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) * In mbedtls, ssl->MBEDTLS_PRIVATE(out_msg) = ssl->MBEDTLS_PRIVATE(out_buf) + offset; */ if (!buf) { - int out_msg_off = (int)ssl->MBEDTLS_PRIVATE(out_msg) - (int)ssl->MBEDTLS_PRIVATE(out_buf); + ptrdiff_t out_msg_off = ssl->MBEDTLS_PRIVATE(out_msg) - ssl->MBEDTLS_PRIVATE(out_buf); if (!out_msg_off) { out_msg_off = MBEDTLS_SSL_HEADER_LEN; @@ -96,9 +98,11 @@ static void init_tx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) ssl->MBEDTLS_PRIVATE(out_hdr) = NULL; ssl->MBEDTLS_PRIVATE(out_len) = NULL; ssl->MBEDTLS_PRIVATE(out_iv) = NULL; - ssl->MBEDTLS_PRIVATE(out_msg) = (unsigned char *)out_msg_off; + /* Stash the offset in the pointer field while the buffer is freed; it is + * restored to a real pointer on the next allocation below. */ + ssl->MBEDTLS_PRIVATE(out_msg) = (unsigned char *)(uintptr_t)out_msg_off; } else { - int out_msg_off = (int)ssl->MBEDTLS_PRIVATE(out_msg); + uintptr_t out_msg_off = (uintptr_t)ssl->MBEDTLS_PRIVATE(out_msg); ssl->MBEDTLS_PRIVATE(out_buf) = buf; ssl->MBEDTLS_PRIVATE(out_ctr) = ssl->MBEDTLS_PRIVATE(out_buf); @@ -107,7 +111,7 @@ static void init_tx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) ssl->MBEDTLS_PRIVATE(out_iv) = ssl->MBEDTLS_PRIVATE(out_buf) + MBEDTLS_SSL_HEADER_LEN; ssl->MBEDTLS_PRIVATE(out_msg) = ssl->MBEDTLS_PRIVATE(out_buf) + out_msg_off; - ESP_LOGV(TAG, "out msg offset is %d", out_msg_off); + ESP_LOGV(TAG, "out msg offset is %u", (unsigned)out_msg_off); } ssl->MBEDTLS_PRIVATE(out_msgtype) = 0; @@ -121,7 +125,7 @@ static void init_rx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) * In mbedtls, ssl->MBEDTLS_PRIVATE(in_msg) = ssl->MBEDTLS_PRIVATE(in_buf) + offset; */ if (!buf) { - int in_msg_off = (int)ssl->MBEDTLS_PRIVATE(in_msg) - (int)ssl->MBEDTLS_PRIVATE(in_buf); + ptrdiff_t in_msg_off = ssl->MBEDTLS_PRIVATE(in_msg) - ssl->MBEDTLS_PRIVATE(in_buf); if (!in_msg_off) { in_msg_off = MBEDTLS_SSL_HEADER_LEN; @@ -132,9 +136,11 @@ static void init_rx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) ssl->MBEDTLS_PRIVATE(in_hdr) = NULL; ssl->MBEDTLS_PRIVATE(in_len) = NULL; ssl->MBEDTLS_PRIVATE(in_iv) = NULL; - ssl->MBEDTLS_PRIVATE(in_msg) = (unsigned char *)in_msg_off; + /* Stash the offset in the pointer field while the buffer is freed; it is + * restored to a real pointer on the next allocation below. */ + ssl->MBEDTLS_PRIVATE(in_msg) = (unsigned char *)(uintptr_t)in_msg_off; } else { - int in_msg_off = (int)ssl->MBEDTLS_PRIVATE(in_msg); + uintptr_t in_msg_off = (uintptr_t)ssl->MBEDTLS_PRIVATE(in_msg); ssl->MBEDTLS_PRIVATE(in_buf) = buf; ssl->MBEDTLS_PRIVATE(in_ctr) = ssl->MBEDTLS_PRIVATE(in_buf); @@ -143,7 +149,7 @@ static void init_rx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) ssl->MBEDTLS_PRIVATE(in_iv) = ssl->MBEDTLS_PRIVATE(in_buf) + MBEDTLS_SSL_HEADER_LEN; ssl->MBEDTLS_PRIVATE(in_msg) = ssl->MBEDTLS_PRIVATE(in_buf) + in_msg_off; - ESP_LOGV(TAG, "in msg offset is %d", in_msg_off); + ESP_LOGV(TAG, "in msg offset is %u", (unsigned)in_msg_off); } ssl->MBEDTLS_PRIVATE(in_msgtype) = 0; diff --git a/components/mbedtls/port/ecc/ecc_alt.c b/components/mbedtls/port/ecc/ecc_alt.c index df5af8e6c43..4ec12d54314 100644 --- a/components/mbedtls/port/ecc/ecc_alt.c +++ b/components/mbedtls/port/ecc/ecc_alt.c @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include #include "soc/hwcrypto_periph.h" #include "ecc_impl.h" #include "hal/ecc_ll.h" @@ -118,8 +117,10 @@ int mbedtls_ecp_check_pubkey( const mbedtls_ecp_group *grp, mbedtls_platform_zeroize((void *)&point, sizeof(ecc_point_t)); - memcpy(&point.x, pt->MBEDTLS_PRIVATE(X).MBEDTLS_PRIVATE(p), mbedtls_mpi_size(&pt->MBEDTLS_PRIVATE(X))); - memcpy(&point.y, pt->MBEDTLS_PRIVATE(Y).MBEDTLS_PRIVATE(p), mbedtls_mpi_size(&pt->MBEDTLS_PRIVATE(Y))); + if (mbedtls_mpi_write_binary_le(&pt->MBEDTLS_PRIVATE(X), point.x, sizeof(point.x)) != 0 || + mbedtls_mpi_write_binary_le(&pt->MBEDTLS_PRIVATE(Y), point.y, sizeof(point.y)) != 0) { + return MBEDTLS_ERR_ECP_INVALID_KEY; + } point.len = grp->pbits / 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; diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_aes_gcm.c b/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_aes_gcm.c index 635d7d304bc..93c1856b985 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_aes_gcm.c +++ b/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_aes_gcm.c @@ -181,6 +181,133 @@ TEST_CASE("mbedtls GCM stream test", "[aes-gcm]") free(decryptedtext); } +/* Drive a multipart AEAD encryption feeding AAD in aad_step-byte chunks and + * plaintext in data_step-byte chunks (deliberately non-block-aligned). */ +static void gcm_encrypt_chunked(psa_key_id_t key_id, const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, size_t aad_step, + const uint8_t *pt, size_t len, size_t data_step, + uint8_t *ct, size_t ct_size, uint8_t *tag, size_t *tag_len) +{ + psa_aead_operation_t op = PSA_AEAD_OPERATION_INIT; + size_t olen, total = 0; + + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_encrypt_setup(&op, key_id, PSA_ALG_GCM)); + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_set_nonce(&op, nonce, nonce_len)); + for (size_t i = 0; i < aad_len; i += aad_step) { + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_update_ad(&op, aad + i, MIN(aad_step, aad_len - i))); + } + for (size_t i = 0; i < len; i += data_step) { + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_update(&op, pt + i, MIN(data_step, len - i), + ct + total, ct_size - total, &olen)); + total += olen; + } + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_finish(&op, ct + total, ct_size - total, &olen, tag, 16, tag_len)); + total += olen; + TEST_ASSERT_EQUAL(len, total); + psa_aead_abort(&op); +} + +/* Multipart AEAD decrypt + verify, chunked the same non-block-aligned way. */ +static void gcm_decrypt_chunked(psa_key_id_t key_id, const uint8_t *nonce, size_t nonce_len, + const uint8_t *aad, size_t aad_len, size_t aad_step, + const uint8_t *ct, size_t len, size_t data_step, + const uint8_t *tag, size_t tag_len, uint8_t *pt, size_t pt_size) +{ + psa_aead_operation_t op = PSA_AEAD_OPERATION_INIT; + size_t olen, total = 0; + + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_decrypt_setup(&op, key_id, PSA_ALG_GCM)); + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_set_nonce(&op, nonce, nonce_len)); + for (size_t i = 0; i < aad_len; i += aad_step) { + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_update_ad(&op, aad + i, MIN(aad_step, aad_len - i))); + } + for (size_t i = 0; i < len; i += data_step) { + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_update(&op, ct + i, MIN(data_step, len - i), + pt + total, pt_size - total, &olen)); + total += olen; + } + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_verify(&op, pt + total, pt_size - total, &olen, tag, tag_len)); + total += olen; + TEST_ASSERT_EQUAL(len, total); + psa_aead_abort(&op); +} + +/* Regression test for the multipart GCM streaming bug: feeding a non-block- + * aligned stream (and non-block-aligned AAD) across several update calls must + * yield the same ciphertext and tag as a single one-shot operation over the + * same bytes. The prior implementation reset the CTR keystream offset every + * call and zero-padded each GHASH chunk independently, so both ciphertext and + * tag diverged for any chunk size that was not a multiple of 16. */ +TEST_CASE("mbedtls GCM unaligned multipart matches one-shot", "[aes-gcm]") +{ + const size_t SZ = 100; + const size_t AAD_SZ = 30; + psa_key_id_t key_id; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + uint8_t nonce[12], key[16], tag[16]; + size_t tag_len; + + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *aad = heap_caps_malloc(AAD_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *ref = heap_caps_malloc(SZ + 16, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *ct = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *dec = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(aad); + TEST_ASSERT_NOT_NULL(ref); + TEST_ASSERT_NOT_NULL(ct); + TEST_ASSERT_NOT_NULL(dec); + + for (size_t i = 0; i < SZ; i++) { + plaintext[i] = (uint8_t)(i * 7 + 3); + } + for (size_t i = 0; i < AAD_SZ; i++) { + aad[i] = (uint8_t)(i * 5 + 1); + } + memset(nonce, 0x24, sizeof(nonce)); + memset(key, 0x9a, sizeof(key)); + + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_crypto_init()); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attributes, PSA_ALG_GCM); + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attributes, 128); + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_import_key(&attributes, key, 16, &key_id)); + + /* One-shot reference: ciphertext followed by the 16-byte tag. */ + size_t ref_len = 0; + TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_aead_encrypt(key_id, PSA_ALG_GCM, nonce, sizeof(nonce), + aad, AAD_SZ, plaintext, SZ, ref, SZ + 16, &ref_len)); + TEST_ASSERT_EQUAL(SZ + 16, ref_len); + + const size_t data_steps[] = {1, 7, 13, 20}; + const size_t aad_steps[] = {1, 7, 13}; + for (size_t d = 0; d < sizeof(data_steps) / sizeof(data_steps[0]); d++) { + for (size_t a = 0; a < sizeof(aad_steps) / sizeof(aad_steps[0]); a++) { + memset(ct, 0, SZ); + memset(dec, 0, SZ); + memset(tag, 0, sizeof(tag)); + + gcm_encrypt_chunked(key_id, nonce, sizeof(nonce), aad, AAD_SZ, aad_steps[a], + plaintext, SZ, data_steps[d], ct, SZ, tag, &tag_len); + TEST_ASSERT_EQUAL(16, tag_len); + TEST_ASSERT_EQUAL_HEX8_ARRAY(ref, ct, SZ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(ref + SZ, tag, 16); + + gcm_decrypt_chunked(key_id, nonce, sizeof(nonce), aad, AAD_SZ, aad_steps[a], + ct, SZ, data_steps[d], tag, tag_len, dec, SZ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, dec, SZ); + } + } + + psa_destroy_key(key_id); + free(plaintext); + free(aad); + free(ref); + free(ct); + free(dec); +} + // Note: PSA Crypto API does not provide a direct equivalent to mbedtls_gcm_self_test() // The self-test functionality is validated through the individual test cases below