mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-26 18:14:11 +00:00
355 lines
14 KiB
Python
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)
|