Merge branch 'feat/esp_tee_backports_v6.0' into 'release/v6.0'

feat(esp_tee): Feature/fixes backports to `release/v6.0`

See merge request espressif/esp-idf!48486
This commit is contained in:
Mahavir Jain
2026-06-29 11:21:13 +05:30
32 changed files with 877 additions and 719 deletions
@@ -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
*/
@@ -174,8 +174,31 @@ esp_err_t esp_att_utils_eat_data_to_json(struct esp_att_sw_claim_list *head, con
free(auth_challenge_hexstr);
json_gen_obj_set_int(&json_gen, "client_id", cfg->client_id);
json_gen_obj_set_int(&json_gen, "chip_id", cfg->chip_id);
json_gen_obj_set_int(&json_gen, "device_ver", cfg->device_ver);
json_gen_push_object(&json_gen, "ueid");
char mac_hexstr[ESP_ATT_EAT_UEID_MAC_SZ * 2 + 1] = {0};
err = esp_att_utils_hexbuf_to_hexstr(cfg->ueid_mac, sizeof(cfg->ueid_mac),
mac_hexstr, sizeof(mac_hexstr));
if (err != ESP_OK) {
free(json_buf);
return err;
}
json_gen_obj_set_string(&json_gen, "mac", mac_hexstr);
char opt_id_hexstr[ESP_ATT_EAT_UEID_OPT_ID_SZ * 2 + 1] = {0};
err = esp_att_utils_hexbuf_to_hexstr(cfg->ueid_opt_id, sizeof(cfg->ueid_opt_id),
opt_id_hexstr, sizeof(opt_id_hexstr));
if (err != ESP_OK) {
free(json_buf);
return err;
}
json_gen_obj_set_string(&json_gen, "optional_id", opt_id_hexstr);
json_gen_pop_object(&json_gen);
char dev_id_hexstr[ESP_ATT_EAT_DEV_ID_SZ * 2 + 1] = {0};
err = esp_att_utils_hexbuf_to_hexstr(cfg->device_id, sizeof(cfg->device_id), dev_id_hexstr, sizeof(dev_id_hexstr));
if (err != ESP_OK) {
@@ -201,6 +224,7 @@ esp_err_t esp_att_utils_eat_data_to_json(struct esp_att_sw_claim_list *head, con
esp_err_t err = part_metadata_to_json(&claim->metadata, &claim_json);
if (err != ESP_OK || claim_json == NULL) {
ESP_LOGE(TAG, "Failed to format the FW metadata to JSON!");
free(json_buf);
return err;
}
@@ -48,44 +48,23 @@ static void free_sw_claim_list(void)
}
}
static esp_err_t fetch_device_id(uint8_t *devid_buf)
static esp_err_t fetch_ueids(esp_att_token_cfg_t *cfg)
{
if (devid_buf == NULL) {
return ESP_ERR_INVALID_ARG;
}
uint8_t mac_addr[6] = {0};
esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_MAC, mac_addr, sizeof(mac_addr) * 8);
/* UEID: raw eFuse MAC */
esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_MAC, cfg->ueid_mac,
ESP_ATT_EAT_UEID_MAC_SZ * 8);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read MAC from eFuse!");
goto exit;
return err;
}
psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT;
psa_status_t status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256);
if (status != PSA_SUCCESS) {
return ESP_FAIL;
}
status = psa_hash_update(&hash_op, mac_addr, sizeof(mac_addr));
if (status != PSA_SUCCESS) {
return ESP_FAIL;
}
size_t digest_len = 0;
status = psa_hash_finish(&hash_op, devid_buf, SHA256_DIGEST_SZ, &digest_len);
if (status != PSA_SUCCESS) {
return ESP_FAIL;
}
if (digest_len != SHA256_DIGEST_SZ) {
return ESP_ERR_INVALID_SIZE;
/* UEID: 128-bit OPTIONAL_UNIQUE_ID */
err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, cfg->ueid_opt_id,
ESP_ATT_EAT_UEID_OPT_ID_SZ * 8);
if (err != ESP_OK) {
return err;
}
return ESP_OK;
exit:
return err;
}
static esp_err_t populate_att_token_cfg(esp_att_token_cfg_t *cfg, const esp_att_ecdsa_keypair_t *keypair)
@@ -94,18 +73,31 @@ static esp_err_t populate_att_token_cfg(esp_att_token_cfg_t *cfg, const esp_att_
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = fetch_device_id(cfg->device_id);
esp_err_t err = fetch_ueids(cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get the device ID!");
ESP_LOGE(TAG, "Failed to get the UEIDs!");
return err;
}
/* Device ID = SHA-256 of the MAC */
size_t digest_len = 0;
psa_status_t status = psa_hash_compute(PSA_ALG_SHA_256, cfg->ueid_mac, sizeof(cfg->ueid_mac),
cfg->device_id, sizeof(cfg->device_id), &digest_len);
if (status != PSA_SUCCESS || digest_len != sizeof(cfg->device_id)) {
ESP_LOGE(TAG, "Failed to derive the device ID!");
return ESP_FAIL;
}
err = esp_att_utils_ecdsa_get_pubkey_digest(keypair, cfg->instance_id, sizeof(cfg->instance_id));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get ECDSA public key hash!");
return err;
}
/* Chip ID read from the ROM */
extern const uint32_t _rom_chip_id;
cfg->chip_id = _rom_chip_id;
/* Chip revision read from eFuse */
cfg->device_ver = efuse_hal_chip_revision();
/* TODO: Decide what all fields we need here */
cfg->device_stat = 0xA5;
@@ -191,6 +183,13 @@ esp_err_t esp_att_generate_token(const uint8_t *auth_challenge, size_t challenge
}
esp_att_ecdsa_keypair_t keypair = {};
psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT;
psa_status_t status;
char *hdr_json = NULL;
char *eat_json = NULL;
char *pubkey_json = NULL;
char *sign_json = NULL;
err = esp_att_utils_ecdsa_gen_keypair_secp256r1(&keypair);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to generate ECDSA key-pair!");
@@ -214,10 +213,10 @@ esp_err_t esp_att_generate_token(const uint8_t *auth_challenge, size_t challenge
memset(token_buf, 0x00, token_buf_size);
psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT;
psa_status_t status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256);
status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256);
if (status != PSA_SUCCESS) {
return ESP_FAIL;
err = ESP_FAIL;
goto exit;
}
json_gen_str_t jstr;
@@ -226,79 +225,84 @@ esp_err_t esp_att_generate_token(const uint8_t *auth_challenge, size_t challenge
/* Pushing the Header object */
const esp_att_token_hdr_t tk_hdr = {};
char *hdr_json = NULL;
int hdr_len = -1;
/* NOTE: Token header is not yet configurable */
err = esp_att_utils_header_to_json(&tk_hdr, &hdr_json, &hdr_len);
if (err != ESP_OK || hdr_json == NULL || hdr_len <= 0) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to format the token header as JSON!");
return err;
goto exit;
}
json_gen_push_object_str(&jstr, "header", hdr_json);
status = psa_hash_update(&hash_op, (const unsigned char *)hdr_json, hdr_len - 1);
if (status != PSA_SUCCESS) {
psa_hash_abort(&hash_op);
return ESP_FAIL;
err = ESP_FAIL;
goto exit;
}
free(hdr_json);
hdr_json = NULL;
/* Pushing the EAT object */
char *eat_json = NULL;
int eat_len = -1;
err = esp_att_utils_eat_data_to_json(&sw_claim_data, &cfg, &eat_json, &eat_len);
if (err != ESP_OK || eat_json == NULL || eat_len <= 0) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to format the EAT data to JSON!");
return err;
goto exit;
}
json_gen_push_object_str(&jstr, "eat", eat_json);
status = psa_hash_update(&hash_op, (const unsigned char *)eat_json, eat_len - 1);
if (status != PSA_SUCCESS) {
psa_hash_abort(&hash_op);
return ESP_FAIL;
err = ESP_FAIL;
goto exit;
}
free(eat_json);
eat_json = NULL;
char *pubkey_json = NULL;
int pubkey_len = -1;
err = esp_att_utils_pubkey_to_json(&keypair, &pubkey_json, &pubkey_len);
if (err != ESP_OK || pubkey_json == NULL || pubkey_len <= 0) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to format the public key data to JSON!");
return err;
goto exit;
}
json_gen_push_object_str(&jstr, "public_key", pubkey_json);
status = psa_hash_update(&hash_op, (const unsigned char *)pubkey_json, pubkey_len - 1);
if (status != PSA_SUCCESS) {
psa_hash_abort(&hash_op);
return ESP_FAIL;
err = ESP_FAIL;
goto exit;
}
free(pubkey_json);
pubkey_json = NULL;
uint8_t digest[SHA256_DIGEST_SZ] = {0};
size_t digest_len = 0;
status = psa_hash_finish(&hash_op, digest, sizeof(digest), &digest_len);
if (status != PSA_SUCCESS) {
psa_hash_abort(&hash_op);
return ESP_FAIL;
err = ESP_FAIL;
goto exit;
}
char *sign_json = NULL;
int sign_len = -1;
err = esp_att_utils_sign_to_json(&keypair, digest, sizeof(digest), &sign_json, &sign_len);
if (err != ESP_OK || sign_json == NULL || sign_len <= 0) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to format the token signature to JSON!");
return err;
goto exit;
}
json_gen_push_object_str(&jstr, "sign", sign_json);
free(sign_json);
sign_json = NULL;
json_gen_end_object(&jstr);
*token_size = json_gen_str_end(&jstr);
err = ESP_OK;
exit:
psa_hash_abort(&hash_op);
free(hdr_json);
free(eat_json);
free(pubkey_json);
free(sign_json);
free_sw_claim_list();
return err;
}
@@ -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
*/
@@ -37,8 +37,10 @@ extern "C" {
#define ESP_ATT_HDR_JSON_MAX_SZ (128)
#define ESP_ATT_EAT_DEV_ID_SZ (32)
#define ESP_ATT_EAT_UEID_MAC_SZ (6) /* eFuse MAC */
#define ESP_ATT_EAT_UEID_OPT_ID_SZ (16) /* eFuse OPTIONAL_UNIQUE_ID */
#define ESP_ATT_CLAIM_JSON_MAX_SZ (448)
#define ESP_ATT_EAT_JSON_MAX_SZ (1344)
#define ESP_ATT_EAT_JSON_MAX_SZ (1600)
#define ESP_ATT_PUBKEY_JSON_MAX_SZ (128)
#define ESP_ATT_SIGN_JSON_MAX_SZ (192)
@@ -119,14 +121,17 @@ typedef struct {
* @brief Structure to hold the Entity Attestation Token initial configuration
*/
typedef struct {
uint8_t *auth_challenge; /**< Authentication challenge */
size_t challenge_size; /**< Challenge size */
uint32_t client_id; /**< Client identifier (Attestation relying party) */
uint32_t device_ver; /**< Device version */
uint8_t device_id[SHA256_DIGEST_SZ]; /**< Device identifier */
uint8_t instance_id[SHA256_DIGEST_SZ]; /**< Instance identifier */
char psa_cert_ref[32]; /**< PSA certificate reference */
uint8_t device_stat; /**< Flags indicating device status */
uint8_t *auth_challenge; /**< Authentication challenge */
size_t challenge_size; /**< Challenge size */
uint32_t client_id; /**< Client identifier (Attestation relying party) */
uint32_t chip_id; /**< Chip identifier */
uint32_t device_ver; /**< Device version */
uint8_t ueid_mac[ESP_ATT_EAT_UEID_MAC_SZ]; /**< Device UEID: MAC from eFuse*/
uint8_t ueid_opt_id[ESP_ATT_EAT_UEID_OPT_ID_SZ]; /**< Device UEID: OPTIONAL_UNIQUE_ID from eFuse*/
uint8_t device_id[SHA256_DIGEST_SZ]; /**< Device identifier (SHA-256 of MAC) */
uint8_t instance_id[SHA256_DIGEST_SZ]; /**< Instance identifier */
char psa_cert_ref[32]; /**< PSA certificate reference */
uint8_t device_stat; /**< Flags indicating device status */
} esp_att_token_cfg_t;
/**
@@ -22,6 +22,7 @@
#include "esp_hmac_pbkdf2.h"
#include "psa/crypto.h"
#include "mbedtls/platform_util.h"
#include "mbedtls/psa_util.h"
#include "esp_rom_sys.h"
@@ -193,7 +194,7 @@ static esp_err_t compute_nvs_keys_with_hmac(esp_efuse_block_t key_blk, nvs_sec_c
psa_reset_key_attributes(&attributes);
// Zero out the key buffer after import
memset(key_buf, 0x00, sizeof(key_buf));
mbedtls_platform_zeroize(key_buf, sizeof(key_buf));
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "Failed to import HMAC key: %d", status);
@@ -208,7 +209,7 @@ static esp_err_t compute_nvs_keys_with_hmac(esp_efuse_block_t key_blk, nvs_sec_c
(uint8_t *)cfg->eky, SHA256_DIGEST_SZ, &mac_length);
if (status != PSA_SUCCESS) {
psa_destroy_key(psa_key_id);
memset(cfg, 0x00, sizeof(nvs_sec_cfg_t));
mbedtls_platform_zeroize(cfg, sizeof(nvs_sec_cfg_t));
return ESP_FAIL;
}
ESP_FAULT_ASSERT(status == PSA_SUCCESS);
@@ -221,7 +222,7 @@ static esp_err_t compute_nvs_keys_with_hmac(esp_efuse_block_t key_blk, nvs_sec_c
psa_destroy_key(psa_key_id);
if (status != PSA_SUCCESS) {
memset(cfg, 0x00, sizeof(nvs_sec_cfg_t));
mbedtls_platform_zeroize(cfg, sizeof(nvs_sec_cfg_t));
return ESP_FAIL;
}
ESP_FAULT_ASSERT(status == PSA_SUCCESS);
@@ -322,20 +323,24 @@ esp_err_t esp_tee_sec_storage_clear_key(const char *key_id)
esp_err_t err = secure_storage_read(key_id, (void *)&keyctx, &keyctx_len);
if (err != ESP_OK) {
return err;
goto cleanup;
}
if (keyctx.flags & SEC_STORAGE_FLAG_WRITE_ONCE) {
ESP_LOGE(TAG, "Key is write-once only and cannot be cleared!");
return ESP_ERR_INVALID_STATE;
err = ESP_ERR_INVALID_STATE;
goto cleanup;
}
err = nvs_erase_key(tee_nvs_hdl, key_id);
if (err != ESP_OK) {
return err;
goto cleanup;
}
err = nvs_commit(tee_nvs_hdl);
cleanup:
mbedtls_platform_zeroize(&keyctx, sizeof(keyctx));
return err;
}
@@ -465,6 +470,7 @@ esp_err_t esp_tee_sec_storage_gen_key(const esp_tee_sec_storage_key_cfg_t *cfg)
return ESP_ERR_INVALID_STATE;
}
esp_err_t err;
sec_stg_key_t keyctx = {
.type = cfg->type,
.flags = cfg->flags,
@@ -477,21 +483,28 @@ esp_err_t esp_tee_sec_storage_gen_key(const esp_tee_sec_storage_key_cfg_t *cfg)
#endif
if (generate_ecdsa_key(&keyctx, cfg->type) != 0) {
ESP_LOGE(TAG, "Failed to generate ECDSA keypair");
return ESP_FAIL;
err = ESP_FAIL;
goto cleanup;
}
break;
case ESP_SEC_STG_KEY_AES256:
if (generate_aes256_key(&keyctx) != 0) {
ESP_LOGE(TAG, "Failed to generate AES key");
return ESP_FAIL;
err = ESP_FAIL;
goto cleanup;
}
break;
default:
ESP_LOGE(TAG, "Unsupported key-type!");
return ESP_ERR_NOT_SUPPORTED;
err = ESP_ERR_NOT_SUPPORTED;
goto cleanup;
}
return secure_storage_write(cfg->id, (void *)&keyctx, sizeof(keyctx));
err = secure_storage_write(cfg->id, (void *)&keyctx, sizeof(keyctx));
cleanup:
mbedtls_platform_zeroize(&keyctx, sizeof(keyctx));
return err;
}
esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cfg, const uint8_t *hash, size_t hlen, esp_tee_sec_storage_ecdsa_sign_t *out_sign)
@@ -514,19 +527,21 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cf
sec_stg_key_t keyctx;
size_t keyctx_len = sizeof(keyctx);
psa_key_id_t key_id = 0;
psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT;
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;
goto exit;
}
if (keyctx.type != cfg->type) {
ESP_LOGE(TAG, "Key type mismatch");
return ESP_ERR_INVALID_STATE;
err = ESP_ERR_INVALID_STATE;
goto exit;
}
psa_key_id_t key_id = 0;
psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_VERIFY_HASH);
psa_algorithm_t ecdsa_alg = PSA_ALG_ECDSA(PSA_ALG_SHA_256);
@@ -571,6 +586,7 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cf
exit:
psa_destroy_key(key_id);
psa_reset_key_attributes(&key_attributes);
mbedtls_platform_zeroize(&keyctx, sizeof(keyctx));
return err;
}
@@ -594,12 +610,13 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg
err = secure_storage_read(cfg->id, (void *)&keyctx, &keyctx_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read key from secure storage");
return err;
goto cleanup;
}
if (keyctx.type != cfg->type) {
ESP_LOGE(TAG, "Key type mismatch");
return ESP_ERR_INVALID_STATE;
err = ESP_ERR_INVALID_STATE;
goto cleanup;
}
/* Now determine the public key source and length based on key type */
@@ -619,13 +636,17 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg
#endif
default:
ESP_LOGE(TAG, "Unsupported key-type");
return ESP_ERR_INVALID_ARG;
err = ESP_ERR_INVALID_ARG;
goto cleanup;
}
memcpy(out_pubkey->pub_x, pub_key_src, pub_key_len);
memcpy(out_pubkey->pub_y, pub_key_src + pub_key_len, pub_key_len);
err = ESP_OK;
return ESP_OK;
cleanup:
mbedtls_platform_zeroize(&keyctx, sizeof(keyctx));
return err;
}
static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t *input, size_t len, const uint8_t *aad,
@@ -648,17 +669,22 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t
return err;
}
psa_key_id_t psa_key_id = 0;
uint8_t *aead_buf = NULL;
size_t aead_buf_len = 0;
sec_stg_key_t keyctx;
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;
goto cleanup;
}
if (keyctx.type != ESP_SEC_STG_KEY_AES256) {
ESP_LOGE(TAG, "Key type mismatch");
return ESP_ERR_INVALID_STATE;
err = ESP_ERR_INVALID_STATE;
goto cleanup;
}
// Setup PSA key attributes
@@ -670,70 +696,66 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE);
// Import the AES key
psa_key_id_t key_id_psa = 0;
psa_status_t status = psa_import_key(&attributes, keyctx.aes256.key, AES256_KEY_LEN, &key_id_psa);
psa_status_t status = psa_import_key(&attributes, keyctx.aes256.key, AES256_KEY_LEN, &psa_key_id);
psa_reset_key_attributes(&attributes);
if (status != PSA_SUCCESS) {
return ESP_FAIL;
err = ESP_FAIL;
goto cleanup;
}
/* PSA AEAD wants ciphertext+tag concatenated in a single buffer for both
* encrypt (output) and decrypt (input). */
aead_buf_len = len + tag_len;
aead_buf = malloc(aead_buf_len);
if (!aead_buf) {
err = ESP_ERR_NO_MEM;
goto cleanup;
}
if (is_encrypt) {
// PSA AEAD encrypt outputs ciphertext+tag concatenated
uint8_t *output_with_tag = malloc(len + tag_len);
if (!output_with_tag) {
psa_destroy_key(key_id_psa);
return ESP_ERR_NO_MEM;
}
esp_fill_random(iv, iv_len);
size_t output_length = 0;
status = psa_aead_encrypt(key_id_psa, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len),
status = psa_aead_encrypt(psa_key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len),
iv, iv_len, aad, aad_len, input, len,
output_with_tag, len + tag_len, &output_length);
aead_buf, aead_buf_len, &output_length);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "Error in encrypting data: %d", status);
memset(output_with_tag, 0x00, len + tag_len);
free(output_with_tag);
psa_destroy_key(key_id_psa);
return ESP_FAIL;
err = ESP_FAIL;
goto cleanup;
}
// Separate ciphertext and tag
memcpy(output, output_with_tag, len);
memcpy(tag, output_with_tag + len, tag_len);
memset(output_with_tag, 0x00, len + tag_len);
free(output_with_tag);
memcpy(output, aead_buf, len);
memcpy(tag, aead_buf + len, tag_len);
} else {
// For decryption, PSA expects ciphertext + tag concatenated
uint8_t *input_with_tag = malloc(len + tag_len);
if (!input_with_tag) {
psa_destroy_key(key_id_psa);
return ESP_ERR_NO_MEM;
}
memcpy(input_with_tag, input, len);
memcpy(input_with_tag + len, tag, tag_len);
memcpy(aead_buf, input, len);
memcpy(aead_buf + len, tag, tag_len);
size_t output_length = 0;
status = psa_aead_decrypt(key_id_psa, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len),
iv, iv_len, aad, aad_len, input_with_tag, len + tag_len,
status = psa_aead_decrypt(psa_key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len),
iv, iv_len, aad, aad_len, aead_buf, aead_buf_len,
output, len, &output_length);
memset(input_with_tag, 0x00, len + tag_len);
free(input_with_tag);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "Error in decrypting data: %d", status);
psa_destroy_key(key_id_psa);
return ESP_FAIL;
err = ESP_FAIL;
goto cleanup;
}
}
psa_destroy_key(key_id_psa);
return ESP_OK;
err = ESP_OK;
cleanup:
if (aead_buf) {
mbedtls_platform_zeroize(aead_buf, aead_buf_len);
free(aead_buf);
}
if (psa_key_id != 0) {
psa_destroy_key(psa_key_id);
}
mbedtls_platform_zeroize(&keyctx, sizeof(keyctx));
return err;
}
esp_err_t esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *iv, size_t iv_len, uint8_t *tag, size_t tag_len, uint8_t *output)
@@ -819,24 +841,20 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pbkdf2
}
// Sign the hash
memset(out_sign, 0x00, sizeof(esp_tee_sec_storage_ecdsa_sign_t));
size_t signature_length = 0;
status = psa_sign_hash(psa_key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256),
hash, hlen,
out_sign->signature, sizeof(out_sign->signature), &signature_length);
if (status != PSA_SUCCESS) {
memset(out_sign, 0x00, sizeof(esp_tee_sec_storage_ecdsa_sign_t));
err = ESP_FAIL;
goto exit;
}
// Export public key
memset(out_pubkey, 0x00, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t));
uint8_t public_key[PSA_EXPORT_PUBLIC_KEY_MAX_SIZE];
size_t public_key_length = 0;
status = psa_export_public_key(psa_key_id, public_key, sizeof(public_key), &public_key_length);
if (status != PSA_SUCCESS) {
memset(out_pubkey, 0x00, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t));
err = ESP_FAIL;
goto exit;
}
@@ -844,7 +862,6 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pbkdf2
// PSA exports public key in uncompressed format: 0x04 || X || Y
// Skip the first byte (0x04) and copy X and Y coordinates
if (public_key_length != (1 + 2 * key_len) || public_key[0] != 0x04) {
memset(out_pubkey, 0x00, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t));
err = ESP_FAIL;
goto exit;
}
@@ -859,7 +876,7 @@ exit:
psa_destroy_key(psa_key_id);
}
if (derived_key) {
memset(derived_key, 0x00, key_len);
mbedtls_platform_zeroize(derived_key, key_len);
free(derived_key);
}
return err;
@@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Shared assembly helpers (macros + assembler-time constants) for the TEE
* M-mode runtime. Included from esp_tee_vectors_{plic,clic}.S
*/
#pragma once
#include "sdkconfig.h"
#include "riscv/rvruntime-frames.h"
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
#include "esp_private/hw_stack_guard.h"
#endif
/* Shared assembler-time constants used by the macros */
.equ SAVE_REGS, 32
.equ CONTEXT_SIZE, (SAVE_REGS * 4)
.equ MAGIC, 0x1f
/* Macro which first allocates space on the stack to save general
* purpose registers, and then save them. GP register is excluded.
* The default size allocated on the stack is CONTEXT_SIZE, but it
* can be overridden. */
.macro save_general_regs cxt_size=CONTEXT_SIZE
addi sp, sp, -\cxt_size
sw ra, RV_STK_RA(sp)
sw tp, RV_STK_TP(sp)
sw t0, RV_STK_T0(sp)
sw t1, RV_STK_T1(sp)
sw t2, RV_STK_T2(sp)
sw s0, RV_STK_S0(sp)
sw s1, RV_STK_S1(sp)
sw a0, RV_STK_A0(sp)
sw a1, RV_STK_A1(sp)
sw a2, RV_STK_A2(sp)
sw a3, RV_STK_A3(sp)
sw a4, RV_STK_A4(sp)
sw a5, RV_STK_A5(sp)
sw a6, RV_STK_A6(sp)
sw a7, RV_STK_A7(sp)
sw s2, RV_STK_S2(sp)
sw s3, RV_STK_S3(sp)
sw s4, RV_STK_S4(sp)
sw s5, RV_STK_S5(sp)
sw s6, RV_STK_S6(sp)
sw s7, RV_STK_S7(sp)
sw s8, RV_STK_S8(sp)
sw s9, RV_STK_S9(sp)
sw s10, RV_STK_S10(sp)
sw s11, RV_STK_S11(sp)
sw t3, RV_STK_T3(sp)
sw t4, RV_STK_T4(sp)
sw t5, RV_STK_T5(sp)
sw t6, RV_STK_T6(sp)
.endm
.macro save_mepc
csrr t0, mepc
sw t0, RV_STK_MEPC(sp)
.endm
.macro save_mcsr
csrr t0, mstatus
sw t0, RV_STK_MSTATUS(sp)
csrr t0, mtvec
sw t0, RV_STK_MTVEC(sp)
csrr t0, mtval
sw t0, RV_STK_MTVAL(sp)
csrr t0, mhartid
sw t0, RV_STK_MHARTID(sp)
csrr t0, mcause
sw t0, RV_STK_MCAUSE(sp)
.endm
/* Restore the general purpose registers (excluding gp) from the context on
* the stack. The context is then deallocated. The default size is CONTEXT_SIZE
* but it can be overridden. */
.macro restore_general_regs cxt_size=CONTEXT_SIZE
lw ra, RV_STK_RA(sp)
lw tp, RV_STK_TP(sp)
lw t0, RV_STK_T0(sp)
lw t1, RV_STK_T1(sp)
lw t2, RV_STK_T2(sp)
lw s0, RV_STK_S0(sp)
lw s1, RV_STK_S1(sp)
lw a0, RV_STK_A0(sp)
lw a1, RV_STK_A1(sp)
lw a2, RV_STK_A2(sp)
lw a3, RV_STK_A3(sp)
lw a4, RV_STK_A4(sp)
lw a5, RV_STK_A5(sp)
lw a6, RV_STK_A6(sp)
lw a7, RV_STK_A7(sp)
lw s2, RV_STK_S2(sp)
lw s3, RV_STK_S3(sp)
lw s4, RV_STK_S4(sp)
lw s5, RV_STK_S5(sp)
lw s6, RV_STK_S6(sp)
lw s7, RV_STK_S7(sp)
lw s8, RV_STK_S8(sp)
lw s9, RV_STK_S9(sp)
lw s10, RV_STK_S10(sp)
lw s11, RV_STK_S11(sp)
lw t3, RV_STK_T3(sp)
lw t4, RV_STK_T4(sp)
lw t5, RV_STK_T5(sp)
lw t6, RV_STK_T6(sp)
addi sp, sp, \cxt_size
.endm
.macro restore_mepc
lw t0, RV_STK_MEPC(sp)
csrw mepc, t0
.endm
.macro store_magic_general_regs
lui ra, MAGIC
lui tp, MAGIC
lui t0, MAGIC
lui t1, MAGIC
lui t2, MAGIC
lui s0, MAGIC
lui s1, MAGIC
lui a0, MAGIC
lui a1, MAGIC
lui a2, MAGIC
lui a3, MAGIC
lui a4, MAGIC
lui a5, MAGIC
lui a6, MAGIC
lui a7, MAGIC
lui s2, MAGIC
lui s3, MAGIC
lui s4, MAGIC
lui s5, MAGIC
lui s6, MAGIC
lui s7, MAGIC
lui s8, MAGIC
lui s9, MAGIC
lui s10, MAGIC
lui s11, MAGIC
lui t3, MAGIC
lui t4, MAGIC
lui t5, MAGIC
lui t6, MAGIC
.endm
/**
* STACK_GUARD_PRE_SWITCH
* Stops HW stack-guard monitoring and optionally saves current bounds.
*
* Args:
* op_reg – output register for "monitoring enabled" state (must be reused)
* to_save – 1=save bounds to memory, 0=skip saving
* sp_min – symbol to store lower bound (if to_save=1)
* sp_max – symbol to store upper bound (if to_save=1)
*
* Clobbers: t0, t1, t2, op_reg
*/
.macro STACK_GUARD_PRE_SWITCH op_reg, to_save, sp_min, sp_max
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
/* Query if monitoring is enabled: result goes into \op_reg */
ESP_HW_STACK_GUARD_MONITOR_QUERY_CUR_CORE t2 \op_reg
beqz \op_reg, 1f
.if \to_save
/* Save current REE/U-mode stack bounds */
ESP_HW_STACK_GUARD_GET_BOUNDS_CUR_CORE t2 t0 t1
la t2, \sp_min
sw t0, 0(t2)
la t2, \sp_max
sw t1, 0(t2)
.endif
/* Stop monitoring */
ESP_HW_STACK_GUARD_MONITOR_STOP_CUR_CORE t0 t1
fence
1:
#endif
.endm
/**
* STACK_GUARD_POST_SWITCH
* Restores or applies new bounds after switching stacks, then restarts monitoring.
*
* Args:
* op_reg – saved monitoring state from PRE_SWITCH
* to_restore – 1=restore from memory, 0=set static bounds
* sp_min – saved bound (restore) or static lower bound (S-mode)
* sp_max – saved bound (restore) or static upper bound (S-mode)
*
* Clobbers: t0, t1, t2
*/
.macro STACK_GUARD_POST_SWITCH op_reg, to_restore, sp_min, sp_max
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
/* Check if monitoring was enabled (using saved state from op_reg) */
beqz \op_reg, 1f
.if \to_restore
/* Restore saved REE/U-mode bounds from memory */
la t2, \sp_min
lw t0, 0(t2)
la t2, \sp_max
lw t1, 0(t2)
.else
/* Use new TEE/S-mode stack bounds (static symbols) */
la t0, \sp_min
la t1, \sp_max
.endif
/* Apply bounds to hardware stack guard */
ESP_HW_STACK_GUARD_SET_BOUNDS_CUR_CORE t2 t0 t1
/* Restart monitoring */
ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE t0 t1
1:
#endif
.endm
@@ -16,15 +16,10 @@
#include "esp_tee_intr_defs.h"
#include "sdkconfig.h"
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
#include "esp_private/hw_stack_guard.h"
#endif
#include "esp_tee_asm_utils.inc"
.equ SAVE_REGS, 32
.equ CONTEXT_SIZE, (SAVE_REGS * 4)
.equ panic_from_exception, tee_panic_from_exc
.equ panic_from_isr, tee_panic_from_isr
.equ MAGIC, 0x1f
.equ RTNVAL, 0xc0de
.equ ECALL_U_MODE, 0x8
.equ ECALL_M_MODE, 0xb
@@ -60,205 +55,6 @@ _ns_sp_min:
_ns_sp_max:
.word 0
/**
* STACK_GUARD_PRE_SWITCH
* Stops HW stack-guard monitoring and optionally saves current bounds.
*
* Args:
* op_reg – output register for “monitoring enabled” state (must be reused)
* to_save – 1=save bounds to memory, 0=skip saving
* sp_min – symbol to store lower bound (if to_save=1)
* sp_max – symbol to store upper bound (if to_save=1)
*
* Clobbers: t0, t1, t2, op_reg
*/
.macro STACK_GUARD_PRE_SWITCH op_reg, to_save, sp_min, sp_max
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
/* Query if monitoring is enabled: result goes into \op_reg */
ESP_HW_STACK_GUARD_MONITOR_QUERY_CUR_CORE t2 \op_reg
beqz \op_reg, 1f
.if \to_save
/* Save current REE/U-mode stack bounds */
ESP_HW_STACK_GUARD_GET_BOUNDS_CUR_CORE t2 t0 t1
la t2, \sp_min
sw t0, 0(t2)
la t2, \sp_max
sw t1, 0(t2)
.endif
/* Stop monitoring */
ESP_HW_STACK_GUARD_MONITOR_STOP_CUR_CORE t0 t1
fence
1:
#endif
.endm
/**
* STACK_GUARD_POST_SWITCH
* Restores or applies new bounds after switching stacks, then restarts monitoring.
*
* Args:
* op_reg – saved monitoring state from PRE_SWITCH
* to_restore – 1=restore from memory, 0=set static bounds
* sp_min – saved bound (restore) or static lower bound (S-mode)
* sp_max – saved bound (restore) or static upper bound (S-mode)
*
* Clobbers: t0, t1, t2
*/
.macro STACK_GUARD_POST_SWITCH op_reg, to_restore, sp_min, sp_max
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
/* Check if monitoring was enabled (using saved state from op_reg) */
beqz \op_reg, 1f
.if \to_restore
/* Restore saved REE/U-mode bounds from memory */
la t2, \sp_min
lw t0, 0(t2)
la t2, \sp_max
lw t1, 0(t2)
.else
/* Use new TEE/S-mode stack bounds (static symbols) */
la t0, \sp_min
la t1, \sp_max
.endif
/* Apply bounds to hardware stack guard */
ESP_HW_STACK_GUARD_SET_BOUNDS_CUR_CORE t2 t0 t1
/* Restart monitoring */
ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE t0 t1
1:
#endif
.endm
/* Macro which first allocates space on the stack to save general
* purpose registers, and then save them. GP register is excluded.
* The default size allocated on the stack is CONTEXT_SIZE, but it
* can be overridden. */
.macro save_general_regs cxt_size=CONTEXT_SIZE
addi sp, sp, -\cxt_size
sw ra, RV_STK_RA(sp)
sw tp, RV_STK_TP(sp)
sw t0, RV_STK_T0(sp)
sw t1, RV_STK_T1(sp)
sw t2, RV_STK_T2(sp)
sw s0, RV_STK_S0(sp)
sw s1, RV_STK_S1(sp)
sw a0, RV_STK_A0(sp)
sw a1, RV_STK_A1(sp)
sw a2, RV_STK_A2(sp)
sw a3, RV_STK_A3(sp)
sw a4, RV_STK_A4(sp)
sw a5, RV_STK_A5(sp)
sw a6, RV_STK_A6(sp)
sw a7, RV_STK_A7(sp)
sw s2, RV_STK_S2(sp)
sw s3, RV_STK_S3(sp)
sw s4, RV_STK_S4(sp)
sw s5, RV_STK_S5(sp)
sw s6, RV_STK_S6(sp)
sw s7, RV_STK_S7(sp)
sw s8, RV_STK_S8(sp)
sw s9, RV_STK_S9(sp)
sw s10, RV_STK_S10(sp)
sw s11, RV_STK_S11(sp)
sw t3, RV_STK_T3(sp)
sw t4, RV_STK_T4(sp)
sw t5, RV_STK_T5(sp)
sw t6, RV_STK_T6(sp)
.endm
.macro save_mepc
csrr t0, mepc
sw t0, RV_STK_MEPC(sp)
.endm
.macro save_mcsr
csrr t0, mstatus
sw t0, RV_STK_MSTATUS(sp)
csrr t0, mtvec
sw t0, RV_STK_MTVEC(sp)
csrr t0, mtval
sw t0, RV_STK_MTVAL(sp)
csrr t0, mhartid
sw t0, RV_STK_MHARTID(sp)
csrr t0, mcause
sw t0, RV_STK_MCAUSE(sp)
.endm
/* Restore the general purpose registers (excluding gp) from the context on
* the stack. The context is then deallocated. The default size is CONTEXT_SIZE
* but it can be overridden. */
.macro restore_general_regs cxt_size=CONTEXT_SIZE
lw ra, RV_STK_RA(sp)
lw tp, RV_STK_TP(sp)
lw t0, RV_STK_T0(sp)
lw t1, RV_STK_T1(sp)
lw t2, RV_STK_T2(sp)
lw s0, RV_STK_S0(sp)
lw s1, RV_STK_S1(sp)
lw a0, RV_STK_A0(sp)
lw a1, RV_STK_A1(sp)
lw a2, RV_STK_A2(sp)
lw a3, RV_STK_A3(sp)
lw a4, RV_STK_A4(sp)
lw a5, RV_STK_A5(sp)
lw a6, RV_STK_A6(sp)
lw a7, RV_STK_A7(sp)
lw s2, RV_STK_S2(sp)
lw s3, RV_STK_S3(sp)
lw s4, RV_STK_S4(sp)
lw s5, RV_STK_S5(sp)
lw s6, RV_STK_S6(sp)
lw s7, RV_STK_S7(sp)
lw s8, RV_STK_S8(sp)
lw s9, RV_STK_S9(sp)
lw s10, RV_STK_S10(sp)
lw s11, RV_STK_S11(sp)
lw t3, RV_STK_T3(sp)
lw t4, RV_STK_T4(sp)
lw t5, RV_STK_T5(sp)
lw t6, RV_STK_T6(sp)
addi sp,sp, \cxt_size
.endm
.macro restore_mepc
lw t0, RV_STK_MEPC(sp)
csrw mepc, t0
.endm
.macro store_magic_general_regs
lui ra, MAGIC
lui tp, MAGIC
lui t0, MAGIC
lui t1, MAGIC
lui t2, MAGIC
lui s0, MAGIC
lui s1, MAGIC
lui a0, MAGIC
lui a1, MAGIC
lui a2, MAGIC
lui a3, MAGIC
lui a4, MAGIC
lui a5, MAGIC
lui a6, MAGIC
lui a7, MAGIC
lui s2, MAGIC
lui s3, MAGIC
lui s4, MAGIC
lui s5, MAGIC
lui s6, MAGIC
lui s7, MAGIC
lui s8, MAGIC
lui s9, MAGIC
lui s10, MAGIC
lui s11, MAGIC
lui t3, MAGIC
lui t4, MAGIC
lui t5, MAGIC
lui t6, MAGIC
.endm
.section .exception_vectors.text, "ax"
/* Exception handler. */
@@ -394,13 +190,14 @@ _skip_thresh_restore:
STACK_GUARD_POST_SWITCH t3 1 _ns_sp_min _ns_sp_max
fence
call syscall_exit_tee
/* Backup the A0 register
* This point is reached after an ecall is triggered after executing the secure service.
* The A0 register contains the return value of the corresponding service.
* After restoring the entire register context, we assign A0 the value back to the return value. */
csrw mscratch, a0
call syscall_exit_tee
restore_general_regs
csrrw a0, mscratch, zero
@@ -16,15 +16,10 @@
#include "esp_tee_intr_defs.h"
#include "sdkconfig.h"
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
#include "esp_private/hw_stack_guard.h"
#endif
#include "esp_tee_asm_utils.inc"
.equ SAVE_REGS, 32
.equ CONTEXT_SIZE, (SAVE_REGS * 4)
.equ panic_from_exception, tee_panic_from_exc
.equ panic_from_isr, tee_panic_from_isr
.equ MAGIC, 0x1f
.equ RTNVAL, 0xc0de
.equ ECALL_U_MODE, 0x8
.equ ECALL_M_MODE, 0xb
@@ -65,205 +60,6 @@ _ns_sp_min:
_ns_sp_max:
.word 0
/**
* STACK_GUARD_PRE_SWITCH
* Stops HW stack-guard monitoring and optionally saves current bounds.
*
* Args:
* op_reg – output register for “monitoring enabled” state (must be reused)
* to_save – 1=save bounds to memory, 0=skip saving
* sp_min – symbol to store lower bound (if to_save=1)
* sp_max – symbol to store upper bound (if to_save=1)
*
* Clobbers: t0, t1, t2, op_reg
*/
.macro STACK_GUARD_PRE_SWITCH op_reg, to_save, sp_min, sp_max
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
/* Query if monitoring is enabled: result goes into \op_reg */
ESP_HW_STACK_GUARD_MONITOR_QUERY_CUR_CORE t2 \op_reg
beqz \op_reg, 1f
.if \to_save
/* Save current REE/U-mode stack bounds */
ESP_HW_STACK_GUARD_GET_BOUNDS_CUR_CORE t2 t0 t1
la t2, \sp_min
sw t0, 0(t2)
la t2, \sp_max
sw t1, 0(t2)
.endif
/* Stop monitoring */
ESP_HW_STACK_GUARD_MONITOR_STOP_CUR_CORE t0 t1
fence
1:
#endif
.endm
/**
* STACK_GUARD_POST_SWITCH
* Restores or applies new bounds after switching stacks, then restarts monitoring.
*
* Args:
* op_reg – saved monitoring state from PRE_SWITCH
* to_restore – 1=restore from memory, 0=set static bounds
* sp_min – saved bound (restore) or static lower bound (S-mode)
* sp_max – saved bound (restore) or static upper bound (S-mode)
*
* Clobbers: t0, t1, t2
*/
.macro STACK_GUARD_POST_SWITCH op_reg, to_restore, sp_min, sp_max
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
/* Check if monitoring was enabled (using saved state from op_reg) */
beqz \op_reg, 1f
.if \to_restore
/* Restore saved REE/U-mode bounds from memory */
la t2, \sp_min
lw t0, 0(t2)
la t2, \sp_max
lw t1, 0(t2)
.else
/* Use new TEE/S-mode stack bounds (static symbols) */
la t0, \sp_min
la t1, \sp_max
.endif
/* Apply bounds to hardware stack guard */
ESP_HW_STACK_GUARD_SET_BOUNDS_CUR_CORE t2 t0 t1
/* Restart monitoring */
ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE t0 t1
1:
#endif
.endm
/* Macro which first allocates space on the stack to save general
* purpose registers, and then save them. GP register is excluded.
* The default size allocated on the stack is CONTEXT_SIZE, but it
* can be overridden. */
.macro save_general_regs cxt_size=CONTEXT_SIZE
addi sp, sp, -\cxt_size
sw ra, RV_STK_RA(sp)
sw tp, RV_STK_TP(sp)
sw t0, RV_STK_T0(sp)
sw t1, RV_STK_T1(sp)
sw t2, RV_STK_T2(sp)
sw s0, RV_STK_S0(sp)
sw s1, RV_STK_S1(sp)
sw a0, RV_STK_A0(sp)
sw a1, RV_STK_A1(sp)
sw a2, RV_STK_A2(sp)
sw a3, RV_STK_A3(sp)
sw a4, RV_STK_A4(sp)
sw a5, RV_STK_A5(sp)
sw a6, RV_STK_A6(sp)
sw a7, RV_STK_A7(sp)
sw s2, RV_STK_S2(sp)
sw s3, RV_STK_S3(sp)
sw s4, RV_STK_S4(sp)
sw s5, RV_STK_S5(sp)
sw s6, RV_STK_S6(sp)
sw s7, RV_STK_S7(sp)
sw s8, RV_STK_S8(sp)
sw s9, RV_STK_S9(sp)
sw s10, RV_STK_S10(sp)
sw s11, RV_STK_S11(sp)
sw t3, RV_STK_T3(sp)
sw t4, RV_STK_T4(sp)
sw t5, RV_STK_T5(sp)
sw t6, RV_STK_T6(sp)
.endm
.macro save_mepc
csrr t0, mepc
sw t0, RV_STK_MEPC(sp)
.endm
.macro save_mcsr
csrr t0, mstatus
sw t0, RV_STK_MSTATUS(sp)
csrr t0, mtvec
sw t0, RV_STK_MTVEC(sp)
csrr t0, mtval
sw t0, RV_STK_MTVAL(sp)
csrr t0, mhartid
sw t0, RV_STK_MHARTID(sp)
csrr t0, mcause
sw t0, RV_STK_MCAUSE(sp)
.endm
/* Restore the general purpose registers (excluding gp) from the context on
* the stack. The context is then deallocated. The default size is CONTEXT_SIZE
* but it can be overridden. */
.macro restore_general_regs cxt_size=CONTEXT_SIZE
lw ra, RV_STK_RA(sp)
lw tp, RV_STK_TP(sp)
lw t0, RV_STK_T0(sp)
lw t1, RV_STK_T1(sp)
lw t2, RV_STK_T2(sp)
lw s0, RV_STK_S0(sp)
lw s1, RV_STK_S1(sp)
lw a0, RV_STK_A0(sp)
lw a1, RV_STK_A1(sp)
lw a2, RV_STK_A2(sp)
lw a3, RV_STK_A3(sp)
lw a4, RV_STK_A4(sp)
lw a5, RV_STK_A5(sp)
lw a6, RV_STK_A6(sp)
lw a7, RV_STK_A7(sp)
lw s2, RV_STK_S2(sp)
lw s3, RV_STK_S3(sp)
lw s4, RV_STK_S4(sp)
lw s5, RV_STK_S5(sp)
lw s6, RV_STK_S6(sp)
lw s7, RV_STK_S7(sp)
lw s8, RV_STK_S8(sp)
lw s9, RV_STK_S9(sp)
lw s10, RV_STK_S10(sp)
lw s11, RV_STK_S11(sp)
lw t3, RV_STK_T3(sp)
lw t4, RV_STK_T4(sp)
lw t5, RV_STK_T5(sp)
lw t6, RV_STK_T6(sp)
addi sp,sp, \cxt_size
.endm
.macro restore_mepc
lw t0, RV_STK_MEPC(sp)
csrw mepc, t0
.endm
.macro store_magic_general_regs
lui ra, MAGIC
lui tp, MAGIC
lui t0, MAGIC
lui t1, MAGIC
lui t2, MAGIC
lui s0, MAGIC
lui s1, MAGIC
lui a0, MAGIC
lui a1, MAGIC
lui a2, MAGIC
lui a3, MAGIC
lui a4, MAGIC
lui a5, MAGIC
lui a6, MAGIC
lui a7, MAGIC
lui s2, MAGIC
lui s3, MAGIC
lui s4, MAGIC
lui s5, MAGIC
lui s6, MAGIC
lui s7, MAGIC
lui s8, MAGIC
lui s9, MAGIC
lui s10, MAGIC
lui s11, MAGIC
lui t3, MAGIC
lui t4, MAGIC
lui t5, MAGIC
lui t6, MAGIC
.endm
.section .exception_vectors.text, "ax"
/* Exception handler. */
@@ -386,13 +182,14 @@ _skip_thresh_restore:
STACK_GUARD_POST_SWITCH t3 1 _ns_sp_min _ns_sp_max
fence
call syscall_exit_tee
/* Backup the A0 register
* This point is reached after an ecall is triggered after executing the secure service.
* The A0 register contains the return value of the corresponding service.
* After restoring the entire register context, we assign A0 the value back to the return value. */
csrw mscratch, a0
call syscall_exit_tee
restore_general_regs
csrrw a0, mscratch, zero
@@ -4,6 +4,8 @@ components/esp_tee/test_apps/tee_cli_app:
disable:
- if: IDF_TARGET not in ["esp32c6", "esp32c5", "esp32c61"]
reason: only supported with c6, c5 and c61
depends_components:
- esp_tee
components/esp_tee/test_apps/tee_test_fw:
disable:
@@ -138,9 +138,9 @@ help [<string>] [-v <0|1>]
```log
esp32c6> tee_att_info
I (8180) tee_attest: Attestation token - Length: 1587
I (8180) tee_attest: Attestation token - Length: 1705
I (8180) tee_attest: Attestation token - Data:
'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":"tee_att_key0"},"eat":{"auth_challenge":"dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678","client_id":262974944,"device_ver":0,"device_id":"cd9c173cb3675c7adfae243f0cd9841e4bce003237cb5321927a85a86cb4b32e","instance_id":"9616ef0ecf02cdc89a3749f8fc16b3103d5100bd42d9312fcd04593baa7bac64","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"v0.3.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"94536998e1dcb2a036477cb2feb01ed4fff67ba6208f30482346c62bca64b280","digest_validated":true,"sign_verified":true}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"3d4c038fcec76852b4d07acb9e94afaf5fca69fc2eb212a32032d09ce5b4f2b3","digest_validated":true,"sign_verified":true,"secure_padding":true}},"bootloader":{"type":0,"ver":"","idf_ver":"","secure_ver":-1,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"1bef421beb1a4642c6fcefb3e37fd4afad60cb4074e538f42605b012c482b946","digest_validated":true,"sign_verified":true}}}},"public_key":{"compressed":"02039c4bfab0762af1aff2fe5596b037f629cf839da8c4a9c0018afedfccf519a6"},"sign":{"r":"915e749f5a780bc21a2b21821cfeb54286dc742e9f12f2387e3de9b8b1a70bc9","s":"1e583236f2630b0fe8e291645ffa35d429f14035182e19868508d4dac0e1a441"}}'
'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":"tee_att_key0"},"eat":{"auth_challenge":"dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678","client_id":262974944,"chip_id":13,"device_ver":0,"ueid":{"mac":"d885ac67c978","optional_id":"94fa4d7e305682714d48e7bbd710c961"},"device_id":"cd9c173cb3675c7adfae243f0cd9841e4bce003237cb5321927a85a86cb4b32e","instance_id":"9616ef0ecf02cdc89a3749f8fc16b3103d5100bd42d9312fcd04593baa7bac64","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"v0.3.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"94536998e1dcb2a036477cb2feb01ed4fff67ba6208f30482346c62bca64b280","digest_validated":true,"sign_verified":true}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"3d4c038fcec76852b4d07acb9e94afaf5fca69fc2eb212a32032d09ce5b4f2b3","digest_validated":true,"sign_verified":true,"secure_padding":true}},"bootloader":{"type":0,"ver":"","idf_ver":"","secure_ver":-1,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"1bef421beb1a4642c6fcefb3e37fd4afad60cb4074e538f42605b012c482b946","digest_validated":true,"sign_verified":true}}}},"public_key":{"compressed":"02039c4bfab0762af1aff2fe5596b037f629cf839da8c4a9c0018afedfccf519a6"},"sign":{"r":"915e749f5a780bc21a2b21821cfeb54286dc742e9f12f2387e3de9b8b1a70bc9","s":"1e583236f2630b0fe8e291645ffa35d429f14035182e19868508d4dac0e1a441"}}'
```
</details>
@@ -20,3 +20,6 @@ CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=30000
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="test_certs/server_cert.pem"
# Takes effect only when Secure boot is enabled
CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y
@@ -77,3 +77,7 @@ secure_services:
type: custom
function: esp_tee_test_stack_underflow
args: 0
- id: 219
type: custom
function: esp_tee_test_read_sec_stg
args: 1
@@ -1,13 +1,21 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stddef.h>
#include "esp_cpu.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_tee.h"
#include "esp_tee_flash.h"
#include "esp_tee_memory_utils.h"
#include "esp_tee_test.h"
#include "esp_attr.h"
#include "esp_flash_partitions.h"
static const char *TAG = "test_sec_srv";
/* Sample Trusted App */
@@ -54,3 +62,23 @@ uint32_t _ss_esp_tee_test_priv_mode_switch(uint32_t *a, uint32_t *b)
return c;
}
esp_err_t _ss_esp_tee_test_read_sec_stg(uint8_t *buf)
{
if (!esp_tee_buf_in_ree(buf, FLASH_SECTOR_SIZE)) {
return ESP_ERR_INVALID_ARG;
}
esp_partition_info_t pinfo;
esp_err_t err = esp_tee_flash_find_partition(PART_TYPE_DATA, PART_SUBTYPE_DATA_WIFI,
ESP_TEE_SEC_STG_PART_LABEL, &pinfo);
if (err != ESP_OK) {
return err;
}
if (pinfo.pos.size < FLASH_SECTOR_SIZE) {
return ESP_ERR_INVALID_SIZE;
}
return (esp_err_t)esp_tee_flash_read(pinfo.pos.offset, (uint32_t *)buf, FLASH_SECTOR_SIZE, false);
}
@@ -4,6 +4,7 @@
import base64
import csv
import os
import re
import shutil
import subprocess
import sys
@@ -202,25 +203,6 @@ class TEESerial(IdfSerial):
def _get_flash_size(self) -> Any:
return self.app.sdkconfig.get('ESPTOOLPY_FLASHSIZE', '')
@EspSerial.use_esptool()
def bootloader_force_flash_if_req(self) -> None:
# Forcefully flash the bootloader only if security features are enabled
if any(
(
self.app.sdkconfig.get('SECURE_BOOT', True),
self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED', True),
)
):
offs = int(self.app.sdkconfig.get('BOOTLOADER_OFFSET_IN_FLASH', 0))
bootloader_path = os.path.join(self.app.binary_path, 'bootloader', 'bootloader.bin')
encrypt = '--encrypt' if self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED') else ''
flash_size = self._get_flash_size()
esptool.main(
f'--no-stub write-flash {offs} {bootloader_path} --force {encrypt} --flash-size {flash_size}'.split(),
esp=self.esp,
)
@EspSerial.use_esptool()
def custom_erase_partition(self, partition: str) -> None:
if self.app.sdkconfig.get('SECURE_ENABLE_SECURE_ROM_DL_MODE'):
@@ -294,29 +276,133 @@ class TEESerial(IdfSerial):
if os.path.exists(file):
os.remove(file)
@EspSerial.use_esptool()
def custom_flash(self) -> None:
self.bootloader_force_flash_if_req()
self.flash()
@EspSerial.use_esptool()
def custom_flash_w_test_tee_img_gen(self) -> None:
self.bootloader_force_flash_if_req()
self.flash()
self.copy_test_tee_img('ota_1', False)
@EspSerial.use_esptool()
def custom_flash_w_test_tee_img_rb(self) -> None:
self.bootloader_force_flash_if_req()
self.flash()
self.copy_test_tee_img('ota_1', True)
@EspSerial.use_esptool()
def custom_flash_with_empty_sec_stg(self) -> None:
self.bootloader_force_flash_if_req()
self.flash()
self.custom_erase_partition('secure_storage')
KEY_DEFS_ENCRYPTION_TEST: list[str] = [
'aes256_key0',
'aes256_key1',
'attest_key',
'ecdsa_p256_key0',
]
# TEE Secure Storage Development mode
# NVS XTS-AES keys: E-key=0x33*32 || T-key=0xCC*32
NVS_KEYS_DEV_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA=='
@property
def nvs_partition_gen(self) -> str:
return str(
Path(os.environ['IDF_PATH'])
/ 'components'
/ 'nvs_flash'
/ 'nvs_partition_generator'
/ 'nvs_partition_gen.py'
)
def derive_sec_stg_nvs_keys(self, out_path: Path) -> None:
out_path.parent.mkdir(parents=True, exist_ok=True)
if self.app.sdkconfig.get('SECURE_TEE_SEC_STG_MODE_RELEASE'):
hmac_key_src = self.TEST_KEYS_DIR / 'tee_sec_stg_hmac_key.bin'
self.run_command(
[
sys.executable,
self.nvs_partition_gen,
'generate-key',
'--key_protect_hmac',
'--kp_hmac_inputkey',
str(hmac_key_src),
'--keyfile',
out_path.name,
'--outdir',
str(out_path.parent),
]
)
(out_path.parent / 'keys' / out_path.name).replace(out_path)
else:
self.write_keys_to_file(self.NVS_KEYS_DEV_B64, out_path)
def decrypt_sec_stg_partition(self, dump_path: Path, keys_path: Path, decrypted_path: Path) -> None:
self.run_command(
[sys.executable, self.nvs_partition_gen, 'decrypt', str(dump_path), str(keys_path), str(decrypted_path)]
)
_SEC_STG_HEX_LINE_RE = re.compile(
rb'test_esp_tee_sec_storage:\s+((?:[0-9a-f]{2} ){0,15}[0-9a-f]{2})',
)
SEC_STG_DUMP_SZ = 4096
def capture_sec_stg_partition_dump(self, dut: Any, timeout: float = 60) -> bytes:
dut.expect_exact('SEC_STG_DUMP_BEGIN', timeout=timeout)
blob = dut.expect_exact('SEC_STG_DUMP_END', timeout=timeout, return_what_before_match=True)
raw = bytearray()
for match in self._SEC_STG_HEX_LINE_RE.finditer(blob):
for tok in match.group(1).split():
raw.append(int(tok, 16))
if len(raw) != self.SEC_STG_DUMP_SZ:
raise RuntimeError(
f'Hex dump parse mismatch: got {len(raw)} bytes, expected {self.SEC_STG_DUMP_SZ}.\n'
f'Blob (first 256 bytes): {blob[:256]!r}'
)
# Make sure the Unity case actually passed before we trust the dump.
m = dut.expect(re.compile(rb'(\d+) Tests (\d+) Failures (\d+) Ignored'), timeout=timeout)
if int(m.group(2)) != 0:
raise RuntimeError(f'Unity reported {m.group(2).decode()} failures while running encryption test')
return bytes(raw)
def _run_nvs_tool_minimal(self, partition_file: Path) -> subprocess.CompletedProcess:
nvs_tool = Path(os.environ['IDF_PATH']) / 'components' / 'nvs_flash' / 'nvs_partition_tool' / 'nvs_tool.py'
return subprocess.run(
[sys.executable, str(nvs_tool), '-d', 'minimal', '--color', 'never', str(partition_file)],
capture_output=True,
text=True,
)
def verify_tee_sec_stg_encryption(self, dut: Any) -> None:
tmp_dir = self.TMP_DIR / 'sec_stg_encryption'
tmp_dir.mkdir(parents=True, exist_ok=True)
raw_path = tmp_dir / 'tee_sec_stg_dump.bin'
keys_path = tmp_dir / self.NVS_KEYS_FILE
decrypted_path = tmp_dir / 'tee_sec_stg_decr.bin'
expected_key_ids = self.KEY_DEFS_ENCRYPTION_TEST
print('Verifying TEE Secure Storage NVS partition encryption (XTS-AES-512: 256-bit AES, 512-bit total key)')
try:
raw_bytes = self.capture_sec_stg_partition_dump(dut)
raw_path.write_bytes(raw_bytes)
self.derive_sec_stg_nvs_keys(keys_path)
self.decrypt_sec_stg_partition(raw_path, keys_path, decrypted_path)
print('Confirming key IDs are NOT present in the raw (encrypted) NVS dump')
raw_parse = self._run_nvs_tool_minimal(raw_path)
for key_id in expected_key_ids:
assert key_id not in raw_parse.stdout, f'{key_id!r} surfaced in raw dump (not encrypted)'
print('Confirming key IDs ARE present after XTS-AES decrypt with the derived NVS keys')
decrypted_parse = self._run_nvs_tool_minimal(decrypted_path)
assert decrypted_parse.returncode == 0, f'nvs_tool exit {decrypted_parse.returncode} on decrypted dump'
for key_id in expected_key_ids:
assert key_id in decrypted_parse.stdout, f'{key_id!r} missing after decrypt (wrong XTS-AES key?)'
finally:
shutil.rmtree(tmp_dir, ignore_errors=True)
KEY_DEFS: list[dict[str, Any]] = [
{'key': 'aes256_key0', 'type': 'aes256', 'input': None, 'write_once': True},
{
@@ -354,12 +440,11 @@ class TEESerial(IdfSerial):
},
]
NVS_KEYS_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA=='
TEST_KEYS_DIR = Path(__file__).parent / 'test_keys'
TMP_DIR = Path('tmp')
NVS_KEYS_PATH = TMP_DIR / 'nvs_keys.bin'
NVS_CSV_PATH = TMP_DIR / 'tee_sec_stg_val.csv'
NVS_BIN_PATH = TMP_DIR / 'tee_sec_stg_nvs.bin'
NVS_KEYS_FILE = 'tee_sec_stg_nvs_keys.bin'
def run_command(self, command: list[str]) -> None:
try:
@@ -391,16 +476,19 @@ class TEESerial(IdfSerial):
input_path = tmp_dir / entry['input']
self.write_keys_to_file(entry['b64'], input_path)
entry['input'] = str(input_path)
self.write_keys_to_file(self.NVS_KEYS_B64, self.NVS_KEYS_PATH)
idf_path = Path(os.environ['IDF_PATH'])
ESP_TEE_SEC_STG_KEYGEN = os.path.join(
idf_path, 'components', 'esp_tee', 'scripts', 'esp_tee_sec_stg_keygen', 'esp_tee_sec_stg_keygen.py'
)
NVS_PARTITION_GEN = os.path.join(
idf_path, 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py'
os.environ['IDF_PATH'],
'components',
'esp_tee',
'scripts',
'esp_tee_sec_stg_keygen',
'esp_tee_sec_stg_keygen.py',
)
nvs_keys = tmp_dir / self.NVS_KEYS_FILE
self.derive_sec_stg_nvs_keys(nvs_keys)
cmds = [
[sys.executable, ESP_TEE_SEC_STG_KEYGEN, '-k', entry['type'], '-o', str(tmp_dir / f'{entry["key"]}.bin')]
+ (['-i', entry['input']] if entry['input'] else [])
@@ -410,13 +498,12 @@ class TEESerial(IdfSerial):
csv_path = self.create_tee_sec_stg_csv(tmp_dir)
nvs_bin = self.NVS_BIN_PATH
nvs_keys = self.NVS_KEYS_PATH
size = self.app.partition_table['secure_storage']['size']
cmds.append(
[
sys.executable,
NVS_PARTITION_GEN,
self.nvs_partition_gen,
'encrypt',
str(csv_path),
str(nvs_bin),
@@ -430,10 +517,9 @@ class TEESerial(IdfSerial):
for cmd in cmds:
self.run_command(cmd)
self.bootloader_force_flash_if_req()
self.flash()
self.custom_erase_partition('secure_storage')
self.custom_write_partition('secure_storage', nvs_bin)
self.custom_write_partition('secure_storage', str(nvs_bin))
finally:
shutil.rmtree(tmp_dir)
@@ -127,8 +127,8 @@ TEST_CASE("Test TEE OTA - Corrupted image", "[ota_neg_2]")
/* Corrupting the image */
ESP_LOGI(TAG, "Corrupting the image at some offset...");
uint32_t corrupt[8] = {[0 ... 7] = 0x0BADC0DE};
curr_write_offset -= (2 * FLASH_SECTOR_SIZE + sizeof(corrupt));
TEST_ESP_OK(esp_tee_ota_write(curr_write_offset, (const void *)corrupt, sizeof(corrupt)));
uint32_t offs = SOC_MMU_PAGE_SIZE + 0x200;
TEST_ESP_OK(esp_tee_ota_write(offs, (const void *)corrupt, sizeof(corrupt)));
TEST_ESP_ERR(ESP_ERR_IMAGE_INVALID, esp_tee_ota_end());
}
@@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
@@ -364,6 +365,45 @@ TEST_CASE("Test TEE Secure Storage - Null Pointer and Zero Length", "[sec_storag
TEST_ESP_OK(esp_tee_sec_storage_clear_key(key_cfg.id));
}
TEST_CASE("Test TEE Secure Storage - Verify data encryption", "[sec_storage_encr]")
{
ESP_LOGI(TAG, "Populating NVS-based TEE Secure Storage; encrypted with XTS-AES-512");
static const struct {
const char *id;
esp_tee_sec_storage_type_t type;
uint32_t flags;
} key_cfgs[] = {
{ "aes256_key0", ESP_SEC_STG_KEY_AES256, SEC_STORAGE_FLAG_WRITE_ONCE },
{ "aes256_key1", ESP_SEC_STG_KEY_AES256, SEC_STORAGE_FLAG_NONE },
{ "attest_key", ESP_SEC_STG_KEY_ECDSA_SECP256R1, SEC_STORAGE_FLAG_WRITE_ONCE },
{ "ecdsa_p256_key0", ESP_SEC_STG_KEY_ECDSA_SECP256R1, SEC_STORAGE_FLAG_NONE },
};
for (size_t i = 0; i < sizeof(key_cfgs) / sizeof(key_cfgs[0]); i++) {
esp_tee_sec_storage_key_cfg_t cfg = {
.id = key_cfgs[i].id,
.type = key_cfgs[i].type,
.flags = key_cfgs[i].flags,
};
if ((cfg.flags & SEC_STORAGE_FLAG_WRITE_ONCE) == 0) {
esp_err_t err = esp_tee_sec_storage_clear_key(cfg.id);
TEST_ASSERT_TRUE(err == ESP_OK || err == ESP_ERR_NOT_FOUND);
}
TEST_ESP_OK(esp_tee_sec_storage_gen_key(&cfg));
}
const size_t dump_sz = 4096;
uint8_t *buf = heap_caps_malloc(dump_sz, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
TEST_ASSERT_NOT_NULL(buf);
TEST_ESP_OK((esp_err_t)esp_tee_service_call(2, SS_ESP_TEE_TEST_READ_SEC_STG, buf));
printf("\nSEC_STG_DUMP_BEGIN\n");
ESP_LOG_BUFFER_HEX(TAG, buf, dump_sz);
printf("SEC_STG_DUMP_END\n");
free(buf);
}
TEST_CASE("Test TEE Secure Storage - WRITE_ONCE keys", "[sec_storage]")
{
const char *key_id = "key_id_test_wo";
@@ -425,6 +465,10 @@ static void do_ecdsa_sign_and_verify(const esp_tee_sec_storage_key_cfg_t *cfg, c
TEST_ESP_OK(verify_ecdsa_sign(cfg->type, digest, digest_len, &pubkey, &sign));
}
/* NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test expects
* the eFuse-burned HMAC key used for TEE secure storage to be available at
* the path "test_keys/tee_sec_stg_hmac_key.bin"
*/
TEST_CASE("Test TEE Secure Storage - Host-generated keys", "[sec_storage_host_keygen]")
{
const char *aes_key_ids[] = { "aes256_key0", "aes256_key1" };
@@ -6,7 +6,7 @@ from enum import Enum
import pytest
from pytest_embedded_idf import IdfDut
from pytest_embedded_idf.utils import idf_parametrize
from tee_exception_cfg import TEE_EXCEPTION_TEST_MAP
from tee_exception_test_map import TEE_EXCEPTION_TEST_MAP
# ---------------- Pytest build parameters ----------------
@@ -20,6 +20,12 @@ CONFIG_DEFAULT = [
]
CONFIG_OTA = [
# 'config, target, markers',
('tee_ota', target, (pytest.mark.generic,))
for target in TESTING_TARGETS
]
CONFIG_OTA_NO_AUTOFLASH = [
# 'config, target, skip_autoflash, markers',
('tee_ota', target, 'y', (pytest.mark.generic,))
for target in TESTING_TARGETS
@@ -50,8 +56,10 @@ def test_esp_tee(dut: IdfDut) -> None:
CONFIG_ALL,
indirect=['config', 'target'],
)
@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported')
def test_esp_tee_crypto_aes(dut: IdfDut) -> None:
if dut.target == 'esp32c61':
pytest.skip(f'AES not supported on {dut.target}')
dut.run_all_single_board_cases(group='aes')
dut.run_all_single_board_cases(group='aes-gcm')
@@ -72,8 +80,10 @@ def test_esp_tee_crypto_sha(dut: IdfDut) -> None:
CONFIG_ALL,
indirect=['config', 'target'],
)
@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported')
def test_esp_tee_aes_perf(dut: IdfDut) -> None:
if dut.target == 'esp32c61':
pytest.skip(f'AES not supported on {dut.target}')
for i in range(10):
dut.run_all_single_board_cases(name=['mbedtls AES performance'])
@@ -121,6 +131,13 @@ def test_esp_tee_isolation_checks(dut: IdfDut) -> None:
for test_name, expected in cfg.items():
run_exception_case(dut, 'Test REE-TEE isolation', test_name, expected, check_origin=True)
# ESP32-C61: MMU-spillover gracefully reboots instead of panicking
if dut.target == 'esp32c61':
dut.skip_decode_panic = True
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('"Test REE-TEE isolation: MMU-spillover"')
dut.expect_exact('Failed MMU operation, rebooting!', timeout=10)
@idf_parametrize(
'config, target, markers',
@@ -238,8 +255,6 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) -
# Panics are expected during these tests
dut.skip_decode_panic = True
dut.serial.custom_flash()
extra_data = dut._parse_test_menu()
test_case = next((tc for tc in extra_data if tc.name == test_name), None)
@@ -250,9 +265,9 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) -
@idf_parametrize(
'config, target, skip_autoflash, markers',
'config, target, markers',
CONFIG_OTA,
indirect=['config', 'target', 'skip_autoflash'],
indirect=['config', 'target'],
)
def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None:
run_flash_access_test(
@@ -261,9 +276,9 @@ def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
'config, target, markers',
CONFIG_OTA,
indirect=['config', 'target', 'skip_autoflash'],
indirect=['config', 'target'],
)
def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None:
run_flash_access_test(
@@ -272,9 +287,9 @@ def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
'config, target, markers',
CONFIG_OTA,
indirect=['config', 'target', 'skip_autoflash'],
indirect=['config', 'target'],
)
def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None:
run_flash_access_test(
@@ -283,18 +298,18 @@ def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
'config, target, markers',
CONFIG_OTA,
indirect=['config', 'target', 'skip_autoflash'],
indirect=['config', 'target'],
)
def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None:
run_flash_access_test(dut, TeeFlashAccessApi.ESP_PARTITION, 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)')
@idf_parametrize(
'config, target, skip_autoflash, markers',
'config, target, markers',
CONFIG_OTA,
indirect=['config', 'target', 'skip_autoflash'],
indirect=['config', 'target'],
)
def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None:
run_flash_access_test(dut, TeeFlashAccessApi.ESP_FLASH, 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)')
@@ -303,9 +318,11 @@ def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None:
# ---------------- TEE Local OTA tests ----------------
@pytest.mark.generic
@idf_parametrize('config', ['tee_ota'], indirect=['config'])
@idf_parametrize('target', TESTING_TARGETS, indirect=['target'])
@idf_parametrize(
'config, target, markers',
CONFIG_OTA,
indirect=['config', 'target'],
)
def test_esp_tee_ota_negative(dut: IdfDut) -> None:
# start test
dut.run_all_single_board_cases(group='ota_neg_1', timeout=10)
@@ -313,7 +330,7 @@ def test_esp_tee_ota_negative(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_ota_corrupted_img(dut: IdfDut) -> None:
@@ -347,7 +364,7 @@ def tee_ota_stage_checks(dut: IdfDut, stage: TeeOtaStage, offset: str) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None:
@@ -370,7 +387,7 @@ def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_ota_valid_img(dut: IdfDut) -> None:
@@ -401,7 +418,7 @@ def test_esp_tee_ota_valid_img(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_ota_rollback(dut: IdfDut) -> None:
@@ -440,7 +457,7 @@ def test_esp_tee_ota_rollback(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_secure_storage(dut: IdfDut) -> None:
@@ -452,22 +469,43 @@ def test_esp_tee_secure_storage(dut: IdfDut) -> None:
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None:
# Flash image and write the secure_storage partition with host-generated keys
# NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test
# expects the eFuse-burned HMAC key used for TEE secure storage to be available
# at the path "test_keys/tee_sec_stg_hmac_key.bin"
dut.serial.custom_flash_with_host_gen_sec_stg_img()
dut.run_all_single_board_cases(group='sec_storage_host_keygen')
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_secure_storage_encryption(dut: IdfDut) -> None:
dut.serial.custom_flash_with_empty_sec_stg()
# NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test
# expects the eFuse-burned HMAC key used for TEE secure storage to be available
# at the path "test_keys/tee_sec_stg_hmac_key.bin"
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('"Test TEE Secure Storage - Verify data encryption"')
dut.serial.verify_tee_sec_stg_encryption(dut)
# ---------------- TEE Attestation tests ----------------
@idf_parametrize(
'config, target, skip_autoflash, markers',
CONFIG_OTA,
CONFIG_OTA_NO_AUTOFLASH,
indirect=['config', 'target', 'skip_autoflash'],
)
def test_esp_tee_attestation(dut: IdfDut) -> None:
@@ -15,3 +15,6 @@ CONFIG_PARTITION_TABLE_OFFSET=0xF000
# Increasing TEE I/DRAM size
CONFIG_SECURE_TEE_IRAM_SIZE=0x8800
CONFIG_SECURE_TEE_DRAM_SIZE=0x5800
# Takes effect only when Secure boot is enabled
CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y
@@ -78,6 +78,12 @@ _TARGET_OVERRIDES: dict[str, dict[str, Any]] = {
},
},
'esp32c61': {
# NOTE: On ESP32-C61, MMU-spillover does not raise a CPU panic — the TEE
# test fills the bad mapping with a poison pattern and calls esp_restart().
# Verified separately in the pytest, so drop it from the panic-driven map.
'ree_isolation': {
'_remove': ['MMU-spillover'],
},
# NOTE: ESP32-C61 does not support the following peripherals
'apm_violation': {
'_remove': ['AES', 'HMAC', 'DS'],