Merge branch 'fix/tee_sec_stg_aes_gcm_iv_reuse_v5.5' into 'release/v5.5'

fix(esp_tee): Prevent IV reuse in the TEE secure storage AES-GCM service (v5.5)

See merge request espressif/esp-idf!45682
This commit is contained in:
Mahavir Jain
2026-02-18 18:04:19 +05:30
13 changed files with 78 additions and 63 deletions
@@ -40,16 +40,13 @@ python esp_tee_sec_stg_keygen.py -k ecdsa_p256 -o ecdsa_p256_k1.bin -i ecdsa_p25
python esp_tee_sec_stg_keygen.py -k aes256 -o aes256_gcm_k0.bin --write-once
```
#### With custom key and IV
#### With custom key
```bash
# Generate 32 bytes AES key
openssl rand 32 > aes_key.bin
# Generate 12 bytes IV (optional)
openssl rand 12 >> aes_key.bin
# Generate AES key blob using custom key + IV
# Generate AES key blob using custom key
python esp_tee_sec_stg_keygen.py -k aes256 -o aes256_gcm_k1.bin -i aes_key.bin
```
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
@@ -17,8 +17,6 @@ from cryptography.hazmat.primitives.asymmetric import ec
# === Constants ===
SEC_STG_KEY_DATA_SZ = 256
AES_KEY_LEN = 32
AES_DEFAULT_IV_LEN = 16
AES_GCM_IV_LEN = 12
ECDSA_P256_LEN = 32
ECDSA_P192_LEN = 24
@@ -48,22 +46,10 @@ def generate_aes256_key(flags: Flags, key_file: Optional[str] = None) -> bytes:
raise ValueError('AES key file must be at least 32 bytes long')
key = key_data[:AES_KEY_LEN]
iv_data = key_data[AES_KEY_LEN:]
iv_len = len(iv_data)
if iv_len == 0:
iv = os.urandom(AES_DEFAULT_IV_LEN)
elif iv_len == AES_GCM_IV_LEN:
iv = iv_data + b'\x00' * (AES_DEFAULT_IV_LEN - AES_GCM_IV_LEN)
elif iv_len == AES_DEFAULT_IV_LEN:
iv = iv_data
else:
raise ValueError('IV length must be exactly 12 or 16 bytes, or omitted to generate one')
else:
key = os.urandom(AES_KEY_LEN)
iv = os.urandom(AES_DEFAULT_IV_LEN)
packed = struct.pack('<II32s16s', KeyType.AES256.value, flags.value, key, iv)
packed = struct.pack('<II32s', KeyType.AES256.value, flags.value, key)
return packed + b'\x00' * (SEC_STG_KEY_DATA_SZ - len(packed))
@@ -18,6 +18,7 @@ extern "C" {
#define MAX_ECDSA_SUPPORTED_KEY_LEN 32 /*!< Maximum supported size for the ECDSA key */
#define MAX_AES_SUPPORTED_KEY_LEN 32 /*!< Maximum supported size for the AES key */
#define AES_GCM_SUPPORTED_IV_LEN 12 /*!< Supported IV length for AES-GCM operations */
#define SEC_STORAGE_FLAG_NONE 0 /*!< No flags */
#define SEC_STORAGE_FLAG_WRITE_ONCE BIT(0) /*!< Data can only be written once */
@@ -53,6 +54,7 @@ typedef struct {
size_t aad_len; /*!< Length of additional authenticated data */
const uint8_t *input; /*!< Input data buffer */
size_t input_len; /*!< Length of input data */
uint8_t iv[AES_GCM_SUPPORTED_IV_LEN]; /*!< IV buffer: OUTPUT for encrypt, INPUT for decrypt */
} esp_tee_sec_storage_aead_ctx_t;
/**
@@ -145,7 +147,7 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg
*
* @return esp_err_t ESP_OK on success, appropriate error code otherwise.
*/
esp_err_t esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output);
esp_err_t esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output);
/**
* @brief Perform decryption using AES256-GCM with the key from secure storage
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -35,7 +35,7 @@
#define AES256_KEY_LEN 32
#define AES256_KEY_BITS (AES256_KEY_LEN * 8)
#define AES256_DEFAULT_IV_LEN 16
#define AES256_GCM_IV_LEN 12
#define AES256_GCM_IV_LEN (AES_GCM_SUPPORTED_IV_LEN)
#define ECDSA_SECP256R1_KEY_LEN 32
#define ECDSA_SECP192R1_KEY_LEN 24
@@ -58,10 +58,9 @@ typedef struct {
uint8_t pub_key[2 * ECDSA_SECP192R1_KEY_LEN]; /* Public key for ECDSA SECP192R1 (X and Y coordinates) */
} __attribute__((aligned(4))) __attribute__((__packed__)) sec_stg_ecdsa_secp192r1_t;
/* Structure to hold AES-256 key and IV */
/* Structure to hold AES-256 key */
typedef struct {
uint8_t key[AES256_KEY_LEN]; /* Key for AES-256 */
uint8_t iv[AES256_DEFAULT_IV_LEN]; /* Initialization vector for AES-256 */
} __attribute__((aligned(4))) __attribute__((__packed__)) sec_stg_aes256_t;
/* Structure to hold the cryptographic keys in NVS */
@@ -71,7 +70,7 @@ typedef struct {
union {
sec_stg_ecdsa_secp256r1_t ecdsa_secp256r1; /* ECDSA SECP256R1 key pair */
sec_stg_ecdsa_secp192r1_t ecdsa_secp192r1; /* ECDSA SECP192R1 key pair */
sec_stg_aes256_t aes256; /* AES-256 key and IV */
sec_stg_aes256_t aes256; /* AES-256 key */
};
uint32_t reserved[38]; /* Reserved space for future use */
} __attribute__((aligned(4))) __attribute__((__packed__)) sec_stg_key_t;
@@ -338,9 +337,7 @@ static int generate_aes256_key(sec_stg_key_t *keyctx)
}
ESP_LOGD(TAG, "Generating AES-256 key...");
esp_fill_random(&keyctx->aes256.key, AES256_KEY_LEN);
esp_fill_random(&keyctx->aes256.iv, AES256_DEFAULT_IV_LEN);
return 0;
}
@@ -408,7 +405,6 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cf
size_t keyctx_len = sizeof(keyctx);
err = secure_storage_read(cfg->id, (void *)&keyctx, &keyctx_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to fetch key from storage");
return err;
}
@@ -535,15 +531,14 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg
static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t *input, size_t len, const uint8_t *aad,
size_t aad_len, uint8_t *tag, size_t tag_len, uint8_t *output,
bool is_encrypt)
uint8_t *iv, size_t iv_len, bool is_encrypt)
{
if (key_id == NULL || input == NULL || output == NULL || tag == NULL) {
ESP_LOGE(TAG, "Invalid arguments");
if (key_id == NULL || input == NULL || output == NULL || tag == NULL || iv == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (len == 0 || tag_len == 0) {
ESP_LOGE(TAG, "Invalid input/tag length");
if (len == 0 || tag_len == 0 || iv_len != AES256_GCM_IV_LEN) {
ESP_LOGE(TAG, "Invalid input/tag/iv length");
return ESP_ERR_INVALID_SIZE;
}
@@ -557,7 +552,6 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t
size_t keyctx_len = sizeof(keyctx);
err = secure_storage_read(key_id, (void *)&keyctx, &keyctx_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to fetch key from storage");
return err;
}
@@ -577,7 +571,11 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t
}
if (is_encrypt) {
ret = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, len, keyctx.aes256.iv, AES256_GCM_IV_LEN,
/* Generate a fresh random IV for each encryption operation */
memset(iv, 0x00, iv_len);
esp_fill_random(iv, iv_len);
ret = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, len, iv, iv_len,
aad, aad_len, input, output, tag_len, tag);
if (ret != 0) {
ESP_LOGE(TAG, "Error in encrypting data: %d", ret);
@@ -585,7 +583,7 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t
goto exit;
}
} else {
ret = mbedtls_gcm_auth_decrypt(&gcm, len, keyctx.aes256.iv, AES256_GCM_IV_LEN,
ret = mbedtls_gcm_auth_decrypt(&gcm, len, iv, iv_len,
aad, aad_len, tag, tag_len, input, output);
if (ret != 0) {
ESP_LOGE(TAG, "Error in decrypting data: %d", ret);
@@ -600,14 +598,24 @@ exit:
return err;
}
esp_err_t esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output)
esp_err_t esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output)
{
return tee_sec_storage_crypt_common(ctx->key_id, ctx->input, ctx->input_len, ctx->aad, ctx->aad_len, tag, tag_len, output, true);
if (ctx == NULL) {
return ESP_ERR_INVALID_ARG;
}
return tee_sec_storage_crypt_common(ctx->key_id, ctx->input, ctx->input_len, ctx->aad, ctx->aad_len,
tag, tag_len, output, (uint8_t *)ctx->iv, AES256_GCM_IV_LEN, true);
}
esp_err_t esp_tee_sec_storage_aead_decrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, const uint8_t *tag, size_t tag_len, uint8_t *output)
{
return tee_sec_storage_crypt_common(ctx->key_id, ctx->input, ctx->input_len, ctx->aad, ctx->aad_len, (uint8_t *)tag, tag_len, output, false);
if (ctx == NULL) {
return ESP_ERR_INVALID_ARG;
}
return tee_sec_storage_crypt_common(ctx->key_id, ctx->input, ctx->input_len, ctx->aad, ctx->aad_len,
(uint8_t *)tag, tag_len, output, (uint8_t *)ctx->iv, AES256_GCM_IV_LEN, false);
}
esp_err_t esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pbkdf2_ctx_t *ctx,
@@ -28,7 +28,7 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg
return esp_tee_service_call_with_noniram_intr_disabled(3, SS_ESP_TEE_SEC_STORAGE_ECDSA_GET_PUBKEY, cfg, out_pubkey);
}
esp_err_t esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output)
esp_err_t esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output)
{
return esp_tee_service_call_with_noniram_intr_disabled(5, SS_ESP_TEE_SEC_STORAGE_AEAD_ENCRYPT, ctx, tag, tag_len, output);
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -151,13 +151,15 @@ esp_err_t _ss_esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key
return esp_tee_sec_storage_ecdsa_get_pubkey(cfg, out_pubkey);
}
esp_err_t _ss_esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output)
esp_err_t _ss_esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output)
{
bool valid_addr = (esp_tee_ptr_in_ree((void *)ctx->input) &&
esp_tee_ptr_in_ree((void *)ctx->iv) &&
esp_tee_ptr_in_ree((void *)tag) &&
esp_tee_ptr_in_ree((void *)output));
valid_addr &= (esp_tee_ptr_in_ree((void *)(ctx->input + ctx->input_len)) &&
esp_tee_ptr_in_ree((void *)(ctx->iv + AES_GCM_SUPPORTED_IV_LEN)) &&
esp_tee_ptr_in_ree((void *)(tag + tag_len)) &&
esp_tee_ptr_in_ree((void *)(output + ctx->input_len)));
@@ -176,10 +178,12 @@ esp_err_t _ss_esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ct
esp_err_t _ss_esp_tee_sec_storage_aead_decrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, const uint8_t *tag, size_t tag_len, uint8_t *output)
{
bool valid_addr = (esp_tee_ptr_in_ree((void *)ctx->input) &&
esp_tee_ptr_in_ree((void *)ctx->iv) &&
esp_tee_ptr_in_ree((void *)tag) &&
esp_tee_ptr_in_ree((void *)output));
valid_addr &= (esp_tee_ptr_in_ree((void *)(ctx->input + ctx->input_len)) &&
esp_tee_ptr_in_ree((void *)(ctx->iv + AES_GCM_SUPPORTED_IV_LEN)) &&
esp_tee_ptr_in_ree((void *)(tag + tag_len)) &&
esp_tee_ptr_in_ree((void *)(output + ctx->input_len)));
@@ -97,11 +97,12 @@ tee_sec_stg_encrypt <key_id> <plaintext>
<key_id> TEE Secure storage key ID
<plaintext> Plaintext to be encrypted
tee_sec_stg_decrypt <key_id> <ciphertext> <tag>
tee_sec_stg_decrypt <key_id> <ciphertext> <tag> <iv>
Decrypt data using AES-GCM key with the given ID from secure storage
<key_id> TEE Secure storage key ID
<ciphertext> Ciphertext to be decrypted
<tag> AES-GCM authentication tag
<iv> AES-GCM initialization vector
help [<string>] [-v <0|1>]
Print the summary of all registered commands if no arguments are given,
@@ -135,8 +136,8 @@ I (8180) tee_attest: Attestation token - Data:
- The TEE secure storage service provides the following commands:
- `tee_sec_stg_gen_key`: Generate and store a new key (ECDSA or AES) in the TEE secure storage with the specified ID
- `tee_sec_stg_sign`: Sign a message using an ECDSA `secp256r1` key pair with the specified ID and verify the signature
- `tee_sec_stg_encrypt`: Encrypt data with AES256-GCM using the key with the specified ID and outputs the ciphertext and tag
- `tee_sec_stg_decrypt`: Decrypt ciphertext using key with the specified ID and tag for integrity verification
- `tee_sec_stg_encrypt`: Encrypt data with AES256-GCM using the key with the specified ID and outputs the ciphertext, tag and the IV used
- `tee_sec_stg_decrypt`: Decrypt ciphertext using key with the specified ID, tag and used IV for integrity verification
- The `get_msg_sha256` command computes the SHA256 hash of a given message, which can be used as input for the `tee_sec_stg_sign` command.
<details>
@@ -163,14 +164,16 @@ I (6444) tee_sec_stg: Signature verified successfully!
```log
esp32c6> tee_sec_stg_gen_key aes256_k0 0
I (2784) tee_sec_stg: Generated AES256 key with ID key0
I (2784) tee_sec_stg: Generated AES256 key with ID aes256_k0
esp32c6> tee_sec_stg_encrypt aes256_k0 b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
I (3084) tee_sec_stg: Ciphertext -
58054310a96d48c2dccdf2e34005aa63b40817723d3ec3d597ab362efea084c1
I (3594) tee_sec_stg: Tag -
caeedb43e08dc3b4e35a58b2412908cc
esp32c6> tee_sec_stg_decrypt aes256_k0 58054310a96d48c2dccdf2e34005aa63b40817723d3ec3d597ab362efea084c1 caeedb43e08dc3b4e35a58b2412908cc
I (4314) tee_sec_stg: Decrypted plaintext -
40ff09c61af2f94611fb605806489380132b0000f2c63863366aad56ad327e95
I (3084) tee_sec_stg: Tag -
8136e8bfc3c70ca792fa486b3eeca72b
I (3084) tee_sec_stg: IV -
0f202954f1a1a138a2ab8b06
esp32c6> tee_sec_stg_decrypt aes256_k0 40ff09c61af2f94611fb605806489380132b0000f2c63863366aad56ad327e95 8136e8bfc3c70ca792fa486b3eeca72b 0f202954f1a1a138a2ab8b06
I (3594) tee_sec_stg: Decrypted plaintext -
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
```
@@ -33,7 +33,7 @@ static void setup_console(void)
* This can be customized, made dynamic, etc.
*/
repl_config.prompt = PROMPT_STR ">";
repl_config.max_cmdline_length = 128;
repl_config.max_cmdline_length = 256;
/* Register help command */
ESP_ERROR_CHECK(esp_console_register_help_command());
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -416,8 +416,12 @@ static int tee_sec_stg_encrypt(int argc, char **argv)
char tag_hexstr[AES256_GCM_TAG_LEN * 2 + 1];
hexbuf_to_hexstr(tag, sizeof(tag), tag_hexstr, sizeof(tag_hexstr));
char iv_hexstr[AES_GCM_SUPPORTED_IV_LEN * 2 + 1];
hexbuf_to_hexstr(ctx.iv, sizeof(ctx.iv), iv_hexstr, sizeof(iv_hexstr));
ESP_LOGI(TAG, "Ciphertext -\n%s", ciphertext);
ESP_LOGI(TAG, "Tag -\n%s", tag_hexstr);
ESP_LOGI(TAG, "IV -\n%s", iv_hexstr);
free(plaintext_buf);
free(ciphertext_buf);
@@ -448,6 +452,7 @@ static struct {
struct arg_str *key_str_id;
struct arg_str *ciphertext;
struct arg_str *tag;
struct arg_str *iv;
struct arg_end *end;
} tee_sec_stg_decrypt_args;
@@ -466,6 +471,10 @@ static int tee_sec_stg_decrypt(int argc, char **argv)
uint8_t tag[AES256_GCM_TAG_LEN];
hexstr_to_hexbuf(tag_hexstr, strlen(tag_hexstr), tag, sizeof(tag));
const char *iv_hexstr = tee_sec_stg_decrypt_args.iv->sval[0];
uint8_t iv[AES_GCM_SUPPORTED_IV_LEN];
hexstr_to_hexbuf(iv_hexstr, strlen(iv_hexstr), iv, sizeof(iv));
const char *ciphertext = tee_sec_stg_decrypt_args.ciphertext->sval[0];
size_t ciphertext_len = strnlen(ciphertext, MAX_AES_PLAINTEXT_LEN);
if (ciphertext_len == MAX_AES_PLAINTEXT_LEN && ciphertext[MAX_AES_PLAINTEXT_LEN] != '\0') {
@@ -499,6 +508,8 @@ static int tee_sec_stg_decrypt(int argc, char **argv)
.input = (uint8_t *)ciphertext_buf,
.input_len = ciphertext_buf_len
};
/* Copying the IV generated during encryption */
memcpy(ctx.iv, iv, sizeof(iv));
err = esp_tee_sec_storage_aead_decrypt(&ctx, tag, sizeof(tag), plaintext_buf);
if (err != ESP_OK) {
@@ -528,7 +539,8 @@ void register_srv_sec_stg_decrypt(void)
tee_sec_stg_decrypt_args.key_str_id = arg_str1(NULL, NULL, "<key_id>", "TEE Secure storage key ID");
tee_sec_stg_decrypt_args.ciphertext = arg_str1(NULL, NULL, "<ciphertext>", "Ciphertext to be decrypted");
tee_sec_stg_decrypt_args.tag = arg_str1(NULL, NULL, "<tag>", "AES-GCM authentication tag");
tee_sec_stg_decrypt_args.end = arg_end(3);
tee_sec_stg_decrypt_args.iv = arg_str1(NULL, NULL, "<iv>", "AES-GCM initialization vector");
tee_sec_stg_decrypt_args.end = arg_end(4);
const esp_console_cmd_t tee_sec_stg = {
.command = "tee_sec_stg_decrypt",
@@ -77,8 +77,9 @@ def test_tee_cli_secure_storage(dut: Dut) -> None:
dut.write(f'tee_sec_stg_encrypt {sec_stg_key_ids.get(i)} {test_msg_hash}')
test_msg_cipher = dut.expect(r'Ciphertext -\s*([0-9a-fA-F]{64})', timeout=30)[1].decode()
test_msg_tag = dut.expect(r'Tag -\s*([0-9a-fA-F]{32})', timeout=30)[1].decode()
test_msg_iv = dut.expect(r'IV -\s*([0-9a-fA-F]{24})', timeout=30)[1].decode()
dut.write(f'tee_sec_stg_decrypt {sec_stg_key_ids.get(i)} {test_msg_cipher} {test_msg_tag}')
dut.write(f'tee_sec_stg_decrypt {sec_stg_key_ids.get(i)} {test_msg_cipher} {test_msg_tag} {test_msg_iv}')
test_msg_decipher = dut.expect(r'Decrypted plaintext -\s*([0-9a-fA-F]{64})', timeout=30)[1].decode()
assert test_msg_decipher == test_msg_hash
@@ -246,7 +246,7 @@ TEST_CASE("Test TEE Secure Storage - Operations with invalid/non-existent keys",
.aad = aad,
.aad_len = sizeof(aad),
.input = plaintext,
.input_len = SZ
.input_len = SZ,
};
// Test ECDSA key with AES operation