Files
esp-idf/components/esp_tee/test_apps/tee_test_fw/conftest.py

355 lines
14 KiB
Python

# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
import base64
import csv
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
import espsecure
import esptool
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from pytest_embedded_idf.serial import IdfSerial
from pytest_embedded_serial_esp.serial import EspSerial
# fmt: off
esp_tee_empty_bin = {
'esp32c6': [
0xE9, 0x04, 0x02, 0x10, 0x00, 0x00, 0x80, 0x40, 0xEE, 0x00, 0x00, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
# DROM segment
0x20, 0x00, 0x00, 0x42, 0x00, 0x02, 0x00, 0x00,
# esp_app_desc structure
0x32, 0x54, 0xCD, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x76, 0x35, 0x2E, 0x35, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x65, 0x73, 0x70, 0x5F, 0x74, 0x65, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4E, 0x6F, 0x76, 0x20, 0x31, 0x31, 0x20, 0x32,
0x30, 0x32, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x35, 0x2E, 0x35,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2D, 0x63, 0x66, 0x8B, 0x75, 0xFA, 0x59, 0x05,
0x53, 0x34, 0x91, 0x71, 0x51, 0x33, 0x91, 0xDD, 0xF8, 0xB1, 0xFE, 0x83,
0x06, 0xEB, 0x03, 0x80, 0x45, 0xC9, 0x18, 0x20, 0x83, 0x7E, 0x2E, 0x43,
*([0x00] * 0x58),
# Padding
*([0x00] * 0x100),
# IRAM segment
0x00, 0x00, 0x80, 0x40, 0x20, 0x00, 0x00, 0x00,
*([0x00] * 0x20),
# PADDING segment
0x00, 0x00, 0x00, 0x00, 0xC8, 0x7D, 0x00, 0x00,
*([0x00] * 0x7DC8),
# IROM segment
0x20, 0x80, 0x00, 0x42, 0x00, 0x01, 0x00, 0x00,
*([0x00] * 0x100),
# Padding
*([0x00] * 0x0F),
# CRC8 checksum
0x56,
# Image SHA256
0xF4, 0xA4, 0xCF, 0x06, 0xAE, 0x94, 0x75, 0x47, 0xBC, 0x88, 0xA2, 0xCA,
0x52, 0x97, 0x7A, 0x5C, 0x55, 0x43, 0xD9, 0xF5, 0xD3, 0x45, 0xD1, 0x34,
0xFC, 0x74, 0xB2, 0xB9, 0x34, 0x72, 0xC3, 0x00
],
'esp32h2': [
0xE9, 0x04, 0x02, 0x1F, 0x00, 0x00, 0x80, 0x40, 0xEE, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
# DROM segment
0x20, 0x00, 0x00, 0x42, 0x00, 0x02, 0x00, 0x00,
# esp_app_desc structure
0x32, 0x54, 0xCD, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x76, 0x35, 0x2E, 0x35, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x65, 0x73, 0x70, 0x5F, 0x74, 0x65, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4E, 0x6F, 0x76, 0x20, 0x31, 0x31, 0x20, 0x32,
0x30, 0x32, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x35, 0x2E, 0x35,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2D, 0x63, 0x66, 0x8B, 0x75, 0xFA, 0x59, 0x05,
0x53, 0x34, 0x91, 0x71, 0x51, 0x33, 0x91, 0xDD, 0xF8, 0xB1, 0xFE, 0x83,
0x06, 0xEB, 0x03, 0x80, 0x45, 0xC9, 0x18, 0x20, 0x83, 0x7E, 0x2E, 0x43,
*([0x00] * 0x58),
# Padding
*([0x00] * 0x100),
# IRAM segment
0x00, 0x00, 0x80, 0x40, 0x20, 0x00, 0x00, 0x00,
*([0x00] * 0x20),
# PADDING segment
0x00, 0x00, 0x00, 0x00, 0xC8, 0x7D, 0x00, 0x00,
*([0x00] * 0x7DC8),
# IROM segment
0x20, 0x80, 0x00, 0x42, 0x00, 0x01, 0x00, 0x00,
*([0x00] * 0x100),
# Padding
*([0x00] * 0x0F),
# CRC8 checksum
0x56,
# Image SHA256
0xDC, 0x60, 0x86, 0x6C, 0x37, 0x76, 0xAA, 0x30, 0x1F, 0x61, 0x48, 0x23,
0xEA, 0x34, 0xAA, 0x19, 0xE8, 0xDE, 0x04, 0x7D, 0x2A, 0x30, 0xC1, 0xDD,
0x61, 0x38, 0x9D, 0xB5, 0xCA, 0x13, 0x5A, 0x79
]
}
# fmt: on
# This is a custom IdfSerial class to support custom functionality
# which is required only for this test
class TEESerial(IdfSerial):
def __init__(self, *args, **kwargs) -> None: # type: ignore
super().__init__(*args, **kwargs)
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'):
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
offs = self.app.partition_table[partition]['offset']
size = self.app.partition_table[partition]['size']
flash_size = self._get_flash_size()
binstr = b'\xff' * int(size)
temp_file.write(binstr)
temp_file.flush()
esptool.main(
f'--no-stub write_flash {offs} {temp_file.name} --flash_size {flash_size}'.split(), esp=self.esp
)
else:
self.erase_partition(partition)
@EspSerial.use_esptool()
def custom_write_partition(self, partition: str, bin_path: str, encrypt: bool = False) -> None:
offs = self.app.partition_table[partition]['offset']
no_stub = '--no-stub' if self.app.sdkconfig.get('SECURE_ENABLE_SECURE_ROM_DL_MODE') else ''
flash_size = self._get_flash_size()
flash_file = bin_path
args = f'{no_stub} write_flash {offs} {flash_file}'.split()
if encrypt:
args.append('--encrypt')
args += f'--flash_size {flash_size}'.split()
esptool.main(args, esp=self.esp)
@EspSerial.use_esptool()
def copy_test_tee_img(self, partition: str, is_rollback: bool = False) -> None:
flash_file = os.path.join(self.app.binary_path, 'esp_tee', 'esp_tee.bin')
encrypt = self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED', False)
if is_rollback:
datafile = 'esp_tee_empty.bin'
datafile_signed = 'esp_tee_empty_signed.bin'
flash_file = datafile
with open(datafile, 'wb') as data_file:
bin_data = esp_tee_empty_bin.get(self.app.sdkconfig.get('IDF_TARGET'), None)
if bin_data is not None:
data_file.write(bytes(bin_data))
data_file.flush()
if self.app.sdkconfig.get('SECURE_BOOT'):
keyfile = self.app.sdkconfig.get('SECURE_BOOT_SIGNING_KEY')
espsecure.main(
[
'sign_data',
'--version',
'2',
'--append_signatures',
'--keyfile',
keyfile,
'--output',
datafile_signed,
datafile,
]
)
flash_file = datafile_signed
self.custom_write_partition(partition, flash_file, encrypt=encrypt)
if is_rollback:
for file in [datafile, datafile_signed]:
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: List[Dict[str, Any]] = [
{'key': 'aes256_key0', 'type': 'aes256', 'input': None, 'write_once': True},
{
'key': 'aes256_key1',
'type': 'aes256',
'input': 'aes256_key.bin',
'write_once': False,
'b64': 'qZxftt2T8mOpLxALIfsDqI65srqPxrJtCVnDU8wrKXbFCJekDRzXqINlU5s=',
},
{'key': 'p256_key0', 'type': 'ecdsa_p256', 'input': None, 'write_once': False},
{
'key': 'attest_key',
'type': 'ecdsa_p256',
'input': 'ecdsa_p256_key.pem',
'write_once': True,
'b64': (
'LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUlNU1VpUktHaVZjSTIvbUZFekI3eXRIOVJj'
'd0wyUThkNDhONHNFUHFYc0RvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFSkYxYXRZQUxrdnB4cCt4N3c1dmVPQ1Vj'
'RUhFRTY5azkvcFB5eFdTbEZkbW5wMnBmbVJpZwp5NnRTMDNaM2tnN2hYcitTQmNLbmRCV2RlZW81Vm9XV29nPT0K'
'LS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo='
),
},
]
NVS_KEYS_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA=='
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'
def run_command(self, command: List[str]) -> None:
try:
subprocess.check_call(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
print(f'Command failed: {" ".join(command)}')
raise
def write_keys_to_file(self, b64_data: str, path: Path) -> None:
path.write_bytes(base64.b64decode(b64_data))
def create_tee_sec_stg_csv(self, tmp_dir: Path) -> Path:
csv_path = self.NVS_CSV_PATH
rows: List[List[str]] = [
['key', 'type', 'encoding', 'value'],
['tee_sec_stg_ns', 'namespace', '', ''],
]
rows += [[entry['key'], 'file', 'binary', str(tmp_dir / f'{entry["key"]}.bin')] for entry in self.KEY_DEFS]
with csv_path.open('w', newline='') as f:
csv.writer(f).writerows(rows)
return csv_path
def custom_flash_with_host_gen_sec_stg_img(self) -> None:
tmp_dir = self.TMP_DIR
tmp_dir.mkdir(parents=True, exist_ok=True)
for entry in self.KEY_DEFS:
if entry['input']:
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'
)
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 [])
+ (['--write-once'] if entry['write_once'] else [])
for entry in self.KEY_DEFS
]
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,
'encrypt',
str(csv_path),
str(nvs_bin),
str(size),
'--inputkey',
str(nvs_keys),
]
)
try:
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)
finally:
shutil.rmtree(tmp_dir)
@pytest.fixture(scope='module')
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
mp = MonkeyPatch()
request.addfinalizer(mp.undo)
return mp
@pytest.fixture(scope='module', autouse=True)
def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None:
monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', TEESerial)