Merge branch 'fix/mbedtls_gcm_multipart_and_ecp_pubkey_v6.1' into 'release/v6.1'

Fix/mbedTLS port layer security fixes (394, 1214, 1206) (v6.1)

See merge request espressif/esp-idf!52194
This commit is contained in:
Jiang Jiang Jian
2026-09-04 16:00:34 +08:00
5 changed files with 223 additions and 28 deletions

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);

View File

@@ -5,6 +5,8 @@
*/
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#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;

View File

@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#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;

View File

@@ -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;

View File

@@ -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