From 51ac92e73748edf83b1e7f2694daaafb429bc379 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Mon, 11 May 2026 16:56:57 +0530 Subject: [PATCH] ci(esp_tee): Add test-case for verifying the TEE Secure Storage encryption --- .../test_sec_srv/sec_srv_tbl_test.yml | 4 + .../test_sec_srv/src/test_sec_srv.c | 30 +++- .../esp_tee/test_apps/tee_test_fw/conftest.py | 151 ++++++++++++++---- .../tee_test_fw/main/test_esp_tee_sec_stg.c | 40 +++++ .../tee_test_fw/pytest_esp_tee_ut.py | 17 ++ 5 files changed, 212 insertions(+), 30 deletions(-) diff --git a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml index f0adf8dcad1..c7f37bba749 100644 --- a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml +++ b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml @@ -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 diff --git a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c index 6978a657803..0547c79d54c 100644 --- a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c +++ b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c @@ -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 +#include + #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); +} diff --git a/components/esp_tee/test_apps/tee_test_fw/conftest.py b/components/esp_tee/test_apps/tee_test_fw/conftest.py index 2a2de9e4f3c..b025bc4db43 100644 --- a/components/esp_tee/test_apps/tee_test_fw/conftest.py +++ b/components/esp_tee/test_apps/tee_test_fw/conftest.py @@ -4,6 +4,7 @@ import base64 import csv import os +import re import shutil import subprocess import sys @@ -290,6 +291,118 @@ class TEESerial(IdfSerial): 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}, { @@ -364,37 +477,17 @@ class TEESerial(IdfSerial): self.write_keys_to_file(entry['b64'], input_path) entry['input'] = str(input_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 - 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, - NVS_PARTITION_GEN, - 'generate-key', - '--key_protect_hmac', - '--kp_hmac_inputkey', - str(hmac_key_src), - '--keyfile', - self.NVS_KEYS_FILE, - '--outdir', - str(tmp_dir), - ] - ) - nvs_keys = tmp_dir / 'keys' / self.NVS_KEYS_FILE - else: - NVS_KEYS_DEV_B64 = ( - 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' - ) - self.write_keys_to_file(NVS_KEYS_DEV_B64, nvs_keys) + 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')] @@ -410,7 +503,7 @@ class TEESerial(IdfSerial): cmds.append( [ sys.executable, - NVS_PARTITION_GEN, + self.nvs_partition_gen, 'encrypt', str(csv_path), str(nvs_bin), @@ -426,7 +519,7 @@ class TEESerial(IdfSerial): 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) diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c index 8c43a8f316b..c5ea5f79781 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include #include #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"; diff --git a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py index e2f8bba2868..1df6e3ecf8b 100644 --- a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py +++ b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py @@ -475,6 +475,23 @@ def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None: 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 ----------------