mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
ci(esp_tee): Add test-case for verifying the TEE Secure Storage encryption
This commit is contained in:
@@ -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
|
||||
|
||||
+29
-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
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -483,6 +483,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 ----------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user