diff --git a/tools/ci/python_packages/ttfw_idf/IDFApp.py b/tools/ci/python_packages/ttfw_idf/IDFApp.py index 20496d54e0..f073e8f9ca 100644 --- a/tools/ci/python_packages/ttfw_idf/IDFApp.py +++ b/tools/ci/python_packages/ttfw_idf/IDFApp.py @@ -296,6 +296,13 @@ class IDFApp(App.BaseApp): d[configs[0]] = configs[1].rstrip() return d + def get_sdkconfig_config_value(self, config_key): # type: (str) -> Any + sdkconfig_dict = self.get_sdkconfig() + value = None + if (config_key in sdkconfig_dict): + value = sdkconfig_dict[config_key] + return value + @abstractmethod def _try_get_binary_from_local_fs(self): # type: () -> Optional[str] pass diff --git a/tools/ci/python_packages/ttfw_idf/IDFDUT.py b/tools/ci/python_packages/ttfw_idf/IDFDUT.py index 9025db8daa..13a8b0977b 100644 --- a/tools/ci/python_packages/ttfw_idf/IDFDUT.py +++ b/tools/ci/python_packages/ttfw_idf/IDFDUT.py @@ -13,6 +13,7 @@ # limitations under the License. """ DUT for IDF applications """ +import collections import functools import io import os @@ -24,6 +25,7 @@ import tempfile import time import pexpect +import serial # python2 and python3 queue package name is different try: @@ -43,6 +45,9 @@ except ImportError: # cheat and use IDF's copy of esptool if available sys.path.insert(0, os.path.join(idf_path, 'components', 'esptool_py', 'esptool')) import esptool +import espefuse +import espsecure + class IDFToolError(OSError): pass @@ -122,10 +127,18 @@ def _uses_esptool(func): settings = self.port_inst.get_settings() try: - if not self._rom_inst: - self._rom_inst = esptool.ESPLoader.detect_chip(self.port_inst) - self._rom_inst.connect('hard_reset') - esp = self._rom_inst.run_stub() + if not self.rom_inst: + if not self.secure_boot_en: + self.rom_inst = esptool.ESPLoader.detect_chip(self.port_inst) + else: + self.rom_inst = self.get_rom()(self.port_inst) + self.rom_inst.connect('hard_reset') + + if (self.secure_boot_en): + esp = self.rom_inst + esp.flash_spi_attach(0) + else: + esp = self.rom_inst.run_stub() ret = func(self, esp, *args, **kwargs) # do hard reset after use esptool @@ -158,10 +171,12 @@ class IDFDUT(DUT.SerialDUT): self.allow_dut_exception = allow_dut_exception self.exceptions = _queue.Queue() self.performance_items = _queue.Queue() - self._rom_inst = None + self.rom_inst = None + self.secure_boot_en = self.app.get_sdkconfig_config_value('CONFIG_SECURE_BOOT') and \ + not self.app.get_sdkconfig_config_value('CONFIG_EFUSE_VIRTUAL') @classmethod - def _get_rom(cls): + def get_rom(cls): raise NotImplementedError('This is an abstraction class, method not defined.') @classmethod @@ -175,7 +190,7 @@ class IDFDUT(DUT.SerialDUT): """ esp = None try: - esp = cls._get_rom()(port) + esp = cls.get_rom()(port) esp.connect() return esp.read_mac() except RuntimeError: @@ -190,7 +205,7 @@ class IDFDUT(DUT.SerialDUT): def confirm_dut(cls, port, **kwargs): inst = None try: - expected_rom_class = cls._get_rom() + expected_rom_class = cls.get_rom() except NotImplementedError: expected_rom_class = None @@ -258,7 +273,7 @@ class IDFDUT(DUT.SerialDUT): else: encrypt_files.append((address, nvs_file)) - self._write_flash(flash_files, encrypt_files, False, encrypt) + self.write_flash_data(flash_files, encrypt_files, False, encrypt) finally: for (_, f) in flash_files: f.close() @@ -266,7 +281,7 @@ class IDFDUT(DUT.SerialDUT): f.close() @_uses_esptool - def _write_flash(self, esp, flash_files=None, encrypt_files=None, ignore_flash_encryption_efuse_setting=True, encrypt=False): + def write_flash_data(self, esp, flash_files=None, encrypt_files=None, ignore_flash_encryption_efuse_setting=True, encrypt=False): """ Try flashing at a particular baud rate. @@ -291,8 +306,8 @@ class IDFDUT(DUT.SerialDUT): 'flash_freq': self.app.flash_settings['flash_freq'], 'addr_filename': flash_files or None, 'encrypt_files': encrypt_files or None, - 'no_stub': False, - 'compress': True, + 'no_stub': self.secure_boot_en, + 'compress': not self.secure_boot_en, 'verify': False, 'encrypt': encrypt, 'ignore_flash_encryption_efuse_setting': ignore_flash_encryption_efuse_setting, @@ -369,7 +384,7 @@ class IDFDUT(DUT.SerialDUT): if encrypt_files: encrypt_offs_files = [(offs, open(path, 'rb')) for (offs, path) in encrypt_files] - self._write_flash(flash_offs_files, encrypt_offs_files, ignore_flash_encryption_efuse_setting, encrypt) + self.write_flash_data(flash_offs_files, encrypt_offs_files, ignore_flash_encryption_efuse_setting, encrypt) finally: for (_, f) in flash_offs_files: f.close() @@ -562,7 +577,7 @@ class ESP32DUT(IDFDUT): TOOLCHAIN_PREFIX = 'xtensa-esp32-elf-' @classmethod - def _get_rom(cls): + def get_rom(cls): return esptool.ESP32ROM @@ -571,7 +586,7 @@ class ESP32S2DUT(IDFDUT): TOOLCHAIN_PREFIX = 'xtensa-esp32s2-elf-' @classmethod - def _get_rom(cls): + def get_rom(cls): return esptool.ESP32S2ROM @@ -580,7 +595,7 @@ class ESP32S3DUT(IDFDUT): TOOLCHAIN_PREFIX = 'xtensa-esp32s3-elf-' @classmethod - def _get_rom(cls): + def get_rom(cls): return esptool.ESP32S3ROM def erase_partition(self, esp, partition): @@ -592,7 +607,7 @@ class ESP32C3DUT(IDFDUT): TOOLCHAIN_PREFIX = 'riscv32-esp-elf-' @classmethod - def _get_rom(cls): + def get_rom(cls): return esptool.ESP32C3ROM @@ -601,13 +616,13 @@ class ESP8266DUT(IDFDUT): TOOLCHAIN_PREFIX = 'xtensa-lx106-elf-' @classmethod - def _get_rom(cls): + def get_rom(cls): return esptool.ESP8266ROM def get_target_by_rom_class(cls): for c in [ESP32DUT, ESP32S2DUT, ESP32S3DUT, ESP32C3DUT, ESP8266DUT, IDFQEMUDUT]: - if c._get_rom() == cls: + if c.get_rom() == cls: return c.TARGET return None @@ -654,7 +669,7 @@ class IDFQEMUDUT(IDFDUT): self.flash_image.flush() @classmethod - def _get_rom(cls): + def get_rom(cls): return esptool.ESP32ROM @classmethod @@ -704,3 +719,170 @@ class IDFQEMUDUT(IDFDUT): class ESP32QEMUDUT(IDFQEMUDUT): TARGET = 'esp32' # type: ignore TOOLCHAIN_PREFIX = 'xtensa-esp32-elf-' # type: ignore + + +class IDFFPGADUT(IDFDUT): + TARGET = None # type: str + TOOLCHAIN_PREFIX = None # type: str + ERASE_NVS = True + FLASH_ENCRYPT_SCHEME = None # type: str + FLASH_ENCRYPT_CNT_KEY = None # type: str + FLASH_ENCRYPT_CNT_VAL = 0 + FLASH_ENCRYPT_PURPOSE = None # type: str + SECURE_BOOT_EN_KEY = None # type: str + SECURE_BOOT_EN_VAL = 0 + FLASH_SECTOR_SIZE = 4096 + + def __init__(self, name, port, log_file, app, allow_dut_exception=False, efuse_reset_port=None, **kwargs): + super(IDFFPGADUT, self).__init__(name, port, log_file, app, allow_dut_exception=allow_dut_exception, **kwargs) + self.esp = self.get_rom()(port) + self.efuses = None + self.efuse_operations = None + self.efuse_reset_port = efuse_reset_port + + @classmethod + def get_rom(cls): + raise NotImplementedError('This is an abstraction class, method not defined.') + + def erase_partition(self, esp, partition): + raise NotImplementedError() + + def enable_efuses(self): + # We use an extra COM port to reset the efuses on FPGA. + # Connect DTR pin of the COM port to the efuse reset pin on daughter board + # Set EFUSEPORT env variable to the extra COM port + if not self.efuse_reset_port: + raise RuntimeError('EFUSEPORT not specified') + + # Stop any previous serial port operation + self.stop_receive() + if self.secure_boot_en: + self.esp.connect() + self.efuses, self.efuse_operations = espefuse.get_efuses(self.esp, False, False, True) + + def burn_efuse(self, field, val): + if not self.efuse_operations: + self.enable_efuses() + BurnEfuseArgs = collections.namedtuple('burn_efuse_args', ['name_value_pairs', 'only_burn_at_end']) + args = BurnEfuseArgs({field: val}, False) + self.efuse_operations.burn_efuse(self.esp, self.efuses, args) + + def burn_efuse_key(self, key, purpose, block): + if not self.efuse_operations: + self.enable_efuses() + BurnKeyArgs = collections.namedtuple('burn_key_args', + ['keyfile', 'keypurpose', 'block', + 'force_write_always', 'no_write_protect', 'no_read_protect', 'only_burn_at_end']) + args = BurnKeyArgs([key], + [purpose], + [block], + False, False, False, False) + self.efuse_operations.burn_key(self.esp, self.efuses, args) + + def burn_efuse_key_digest(self, key, purpose, block): + if not self.efuse_operations: + self.enable_efuses() + BurnDigestArgs = collections.namedtuple('burn_key_digest_args', + ['keyfile', 'keypurpose', 'block', + 'force_write_always', 'no_write_protect', 'no_read_protect', 'only_burn_at_end']) + args = BurnDigestArgs([open(key, 'rb')], + [purpose], + [block], + False, False, True, False) + self.efuse_operations.burn_key_digest(self.esp, self.efuses, args) + + def reset_efuses(self): + if not self.efuse_reset_port: + raise RuntimeError('EFUSEPORT not specified') + with serial.Serial(self.efuse_reset_port) as efuseport: + print('Resetting efuses') + efuseport.dtr = 0 + self.port_inst.setRTS(1) + self.port_inst.setRTS(0) + time.sleep(1) + efuseport.dtr = 1 + self.efuse_operations = None + self.efuses = None + + def sign_data(self, data_file, key_files, version, append_signature=0): + SignDataArgs = collections.namedtuple('sign_data_args', + ['datafile','keyfile','output', 'version', 'append_signatures']) + outfile = tempfile.NamedTemporaryFile() + args = SignDataArgs(data_file, key_files, outfile.name, str(version), append_signature) + espsecure.sign_data(args) + outfile.seek(0) + return outfile.read() + + +class ESP32C3FPGADUT(IDFFPGADUT): + TARGET = 'esp32c3' + TOOLCHAIN_PREFIX = 'riscv32-esp-elf-' + FLASH_ENCRYPT_SCHEME = 'AES-XTS' + FLASH_ENCRYPT_CNT_KEY = 'SPI_BOOT_CRYPT_CNT' + FLASH_ENCRYPT_CNT_VAL = 1 + FLASH_ENCRYPT_PURPOSE = 'XTS_AES_128_KEY' + SECURE_BOOT_EN_KEY = 'SECURE_BOOT_EN' + SECURE_BOOT_EN_VAL = 1 + + @classmethod + def get_rom(cls): + return esptool.ESP32C3ROM + + def erase_partition(self, esp, partition): + raise NotImplementedError() + + def flash_encrypt_burn_cnt(self): + self.burn_efuse(self.FLASH_ENCRYPT_CNT_KEY, self.FLASH_ENCRYPT_CNT_VAL) + + def flash_encrypt_burn_key(self, key, block=0): + self.burn_efuse_key(key, self.FLASH_ENCRYPT_PURPOSE, 'BLOCK_KEY%d' % block) + + def flash_encrypt_get_scheme(self): + return self.FLASH_ENCRYPT_SCHEME + + def secure_boot_burn_en_bit(self): + self.burn_efuse(self.SECURE_BOOT_EN_KEY, self.SECURE_BOOT_EN_VAL) + + def secure_boot_burn_digest(self, digest, key_index=0, block=0): + self.burn_efuse_key_digest(digest, 'SECURE_BOOT_DIGEST%d' % key_index, 'BLOCK_KEY%d' % block) + + @classmethod + def confirm_dut(cls, port, **kwargs): + return True, cls.TARGET + + +class ESP32S3FPGADUT(IDFFPGADUT): + TARGET = 'esp32s3' + TOOLCHAIN_PREFIX = 'xtensa-esp32s3-elf-' + FLASH_ENCRYPT_SCHEME = 'AES-XTS' + FLASH_ENCRYPT_CNT_KEY = 'SPI_BOOT_CRYPT_CNT' + FLASH_ENCRYPT_CNT_VAL = 1 + FLASH_ENCRYPT_PURPOSE = 'XTS_AES_128_KEY' + SECURE_BOOT_EN_KEY = 'SECURE_BOOT_EN' + SECURE_BOOT_EN_VAL = 1 + + @classmethod + def get_rom(cls): + return esptool.ESP32S3ROM + + def erase_partition(self, esp, partition): + raise NotImplementedError() + + def flash_encrypt_burn_cnt(self): + self.burn_efuse(self.FLASH_ENCRYPT_CNT_KEY, self.FLASH_ENCRYPT_CNT_VAL) + + def flash_encrypt_burn_key(self, key, block=0): + self.burn_efuse_key(key, self.FLASH_ENCRYPT_PURPOSE, 'BLOCK_KEY%d' % block) + + def flash_encrypt_get_scheme(self): + return self.FLASH_ENCRYPT_SCHEME + + def secure_boot_burn_en_bit(self): + self.burn_efuse(self.SECURE_BOOT_EN_KEY, self.SECURE_BOOT_EN_VAL) + + def secure_boot_burn_digest(self, digest, key_index=0, block=0): + self.burn_efuse_key_digest(digest, 'SECURE_BOOT_DIGEST%d' % key_index, 'BLOCK_KEY%d' % block) + + @classmethod + def confirm_dut(cls, port, **kwargs): + return True, cls.TARGET diff --git a/tools/ci/python_packages/ttfw_idf/__init__.py b/tools/ci/python_packages/ttfw_idf/__init__.py index d4e2768473..51c7d587ec 100644 --- a/tools/ci/python_packages/ttfw_idf/__init__.py +++ b/tools/ci/python_packages/ttfw_idf/__init__.py @@ -23,8 +23,8 @@ from tiny_test_fw import TinyFW, Utility from .DebugUtils import CustomProcess, GDBBackend, OCDBackend # noqa: export DebugUtils for users from .IDFApp import UT, ComponentUTApp, Example, IDFApp, LoadableElfTestApp, TestApp # noqa: export all Apps for users -from .IDFDUT import (ESP32C3DUT, ESP32DUT, ESP32QEMUDUT, ESP32S2DUT, ESP32S3DUT, # noqa: export DUTs for users - ESP8266DUT, IDFDUT) +from .IDFDUT import (ESP32C3DUT, ESP32C3FPGADUT, ESP32DUT, ESP32QEMUDUT, ESP32S2DUT, # noqa: export DUTs for users + ESP32S3DUT, ESP32S3FPGADUT, ESP8266DUT, IDFDUT) from .unity_test_parser import TestFormat, TestResults # pass TARGET_DUT_CLS_DICT to Env.py to avoid circular dependency issue. @@ -33,6 +33,8 @@ TARGET_DUT_CLS_DICT = { 'ESP32S2': ESP32S2DUT, 'ESP32S3': ESP32S3DUT, 'ESP32C3': ESP32C3DUT, + 'ESP32C3FPGA': ESP32C3FPGADUT, + 'ESP32S3FPGA': ESP32S3FPGADUT, } @@ -81,7 +83,11 @@ def local_test_check(decorator_target): if isinstance(decorator_target, list): if idf_target not in decorator_target: - raise ValueError('IDF_TARGET set to {}, not in decorator target value'.format(idf_target)) + fpga_target = ''.join((idf_target, 'FPGA')) + if fpga_target not in decorator_target: + raise ValueError('IDF_TARGET set to {}, not in decorator target value'.format(idf_target)) + else: + idf_target = fpga_target else: if idf_target != decorator_target: raise ValueError('IDF_TARGET set to {}, not equal to decorator target value'.format(idf_target)) diff --git a/tools/test_apps/security/secure_boot/README.md b/tools/test_apps/security/secure_boot/README.md index fede569c6f..3d30576a4c 100644 --- a/tools/test_apps/security/secure_boot/README.md +++ b/tools/test_apps/security/secure_boot/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-S2 | -| ----------------- | ----- | -------- | +| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | # Secure Boot @@ -9,7 +9,14 @@ The example checks if the secure boot feature is enabled/disabled and if enabled ### Hardware Required -ESP32 (supports Secure Boot V1) or ESP32-ECO3 (supports Secure Boot V2 & Secure Boot V1). It is recommended to use Secure Boot V2 from ESP32-ECO3 onwards. +Any of the following ESP module: +* ESP32 (supports Secure Boot V1) +* ESP32-ECO3 (supports Secure Boot V2 & Secure Boot V1) +* ESP32S2 (supports Secure Boot V2) +* ESP32C3-ECO3 (supports Secure Boot V2) +* ESP32S3 (supports Secure Boot V2) + +It is recommended to use Secure Boot V2 from ESP32-ECO3 onwards. ### Configure the project @@ -54,3 +61,55 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui ## Troubleshooting +--- + +# Secure Boot tests (For internal use only) + +Purpose of the example test cases (`example_test.py`) is to test the secure boot implementation and detect if it is broken. It consists of positive and negative test cases. + +### Hardware required + +* FPGA setup with ESP32C3/ESP32S3 image + +* COM port for programming and export it as ESPPORT + e.g `export ESPPORT=/dev/ttyUSB0` + +* Use another COM port for resetting efuses and connect its DTR pin to efuse reset pin on the FPGA board. Export it as EFUSEPORT + e.g `export EFUSEPORT=/dev/ttyUSB1` + +### Configure the project + +``` +export IDF_ENV_FPGA=1 + +idf.py set-target esp32c3 #(or esp32s3) + +idf.py menuconfig +``` + +Under `Security features` + +- Enable the `Enable hardware Secure Boot` + +- Set the secure boot signing key ("test_rsa_3072_key.pem") + +- Set UART ROM download mode to ENABLED (Required for the script to read the EFUSE) + +- Install and export TTFW requirements +``` +python -m pip install -r $IDF_PATH/tools/ci/python_packages/ttfw_idf/requirements.txt + +export PYTHONPATH="$IDF_PATH/tools:$IDF_PATH/tools/ci/python_packages" +``` + +### Build and test + +- Build the example +``` +idf.py build +``` + +- Run the example test +``` +python example_test.py +``` diff --git a/tools/test_apps/security/secure_boot/example_test.py b/tools/test_apps/security/secure_boot/example_test.py new file mode 100644 index 0000000000..ac2a14ad50 --- /dev/null +++ b/tools/test_apps/security/secure_boot/example_test.py @@ -0,0 +1,183 @@ +from __future__ import print_function + +import os +import struct +import zlib +from io import BytesIO + +import ttfw_idf + +# To prepare a runner for these tests, +# 1. Connect an FPGA with C3 image +# 2. Use a COM port for programming and export it as ESPPORT +# e.g export ESPPORT=/dev/ttyUSB0 +# 3. Use another COM port for resetting efuses and connect its DTR pin to efuse reset pin on the FPGA board. +# Export it as EFUSEPORT +# e.g export EFUSEPORT=/dev/ttyUSB1 +# 4. Run these tests + + +def corrupt_signature(signed_bootloader, seed=0, corrupt_sig=True, corrupt_crc=False, corrupt_single_block=None): + # type: (bytes, int, bool, bool, int) -> bytes + image = signed_bootloader[:-4096] + signature = signed_bootloader[-4096:] + sig_blocks = (signature[0:1216], signature[1216:2432], signature[2432:3648]) + new_blocks = tuple(corrupt_sig_block(s, seed, corrupt_sig, corrupt_crc) for s in sig_blocks) + + # if corrupt_single_block is None, corrupt all blocks + # otherwise, only corrupt the one with that index set + corr_sig_blocks = tuple(new_blocks[n] if corrupt_single_block in [None, n] else sig_blocks[n] for n in range(3)) + + return image + b''.join(corr_sig_blocks) + signature[3648:] + + +def corrupt_sig_block(sig_block, seed=0, corrupt_sig=True, corrupt_crc=False): + # type: (bytes, int, bool, bool) -> bytes + assert len(sig_block) == 1216 + magic = sig_block[0] + assert magic in [0xe7, 0xff] + if magic != 0xe7: + return sig_block # not valid + data = sig_block[:812] + new_sig = sig = sig_block[812:1196] + crc = sig_block[1196:1200] + padding = sig_block[1200:1216] + + if corrupt_sig: + corrupt_idx = seed % len(sig) + corrupt_delta = zlib.crc32(bytes(seed)) & 0xFF + if corrupt_delta == 0: + corrupt_delta = 1 + + new_byte = sig[corrupt_idx] ^ corrupt_delta + + new_sig = sig[0:corrupt_idx] + bytes([new_byte]) + sig[corrupt_idx + 1:] + + assert new_sig != sig + + if not corrupt_crc: + crc = struct.pack(' None + dut.reset_efuses() + bootloader_bin = os.path.join(dut.app.binary_path, 'bootloader/bootloader.bin') + with open(bootloader_bin, 'rb') as f: + dut.write_flash_data([(0x0, f)], None, True, False) + dut.start_app() + + +# Test secure boot flow. +# Correctly signed bootloader + correctly signed app should work +@ttfw_idf.idf_custom_test(env_tag='Example_Secure_Boot', target=['esp32c3fpga', 'esp32s3fpga'], ignore=True) +def test_examples_security_secure_boot(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None + efuse_port = os.getenv('EFUSEPORT') + dut = env.get_dut('secure_boot', 'tools/test_apps/security/secure_boot', efuse_reset_port=efuse_port) + dut_start_secure_app(dut) + dut.expect('Secure Boot is enabled', timeout=2) + + +# Test efuse key index and key block combination. +# Any key index can be written to any key block and should work +@ttfw_idf.idf_custom_test(env_tag='Example_Secure_Boot', target=['esp32c3fpga', 'esp32s3fpga'], ignore=True) +def test_examples_security_secure_boot_key_combo(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None + efuse_port = os.getenv('EFUSEPORT') + dut = env.get_dut('secure_boot', 'tools/test_apps/security/secure_boot', efuse_reset_port=efuse_port) + dut_start_secure_app(dut) + for index in range(3): + for block in range(6): + dut.reset_efuses() + dut.secure_boot_burn_en_bit() + dut.secure_boot_burn_digest('test_rsa_3072_key.pem', index, block) + dut.reset() + dut.expect('Secure Boot is enabled', timeout=2) + + +# Test secure boot key revoke. +# If a key is revoked, bootloader signed with that key should fail verification +@ttfw_idf.idf_custom_test(env_tag='Example_Secure_Boot', target=['esp32c3fpga', 'esp32s3fpga'], ignore=True) +def test_examples_security_secure_boot_key_revoke(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None + efuse_port = os.getenv('EFUSEPORT') + dut = env.get_dut('secure_boot', 'tools/test_apps/security/secure_boot', efuse_reset_port=efuse_port) + dut_start_secure_app(dut) + for index in range(3): + dut.reset_efuses() + dut.secure_boot_burn_en_bit() + dut.secure_boot_burn_digest('test_rsa_3072_key.pem', index, 0) + dut.burn_efuse('SECURE_BOOT_KEY_REVOKE%d' % index, 1) + dut.reset() + dut.expect('secure boot verification failed', timeout=2) + + +# Test bootloader signature corruption. +# Corrupt one byte at a time of bootloader signature and test that the verification fails +@ttfw_idf.idf_custom_test(env_tag='Example_Secure_Boot', target=['esp32c3fpga', 'esp32s3fpga'], ignore=True) +def test_examples_security_secure_boot_corrupt_bl_sig(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None + efuse_port = os.getenv('EFUSEPORT') + dut = env.get_dut('secure_boot', 'tools/test_apps/security/secure_boot', efuse_reset_port=efuse_port) + dut.reset_efuses() + dut.secure_boot_burn_en_bit() + dut.secure_boot_burn_digest('test_rsa_3072_key.pem', 0, 0) + bootloader_bin = os.path.join(dut.app.binary_path, 'bootloader/bootloader.bin') + with open(bootloader_bin, 'rb') as f: + signed_bl = f.read() + + seeds = range(0, 384) + max_seed = max(seeds) + + for seed in seeds: + print('Case %d / %d' % (seed, max_seed)) + corrupt_bl = corrupt_signature(signed_bl, seed=seed) + dut.write_flash_data([(0x0, BytesIO(corrupt_bl))]) + dut.expect('Signature Check Failed', timeout=2) + + +# Test app signature corruption. +# Corrupt app signature, one byte at a time, and test that the verification fails +@ttfw_idf.idf_custom_test(env_tag='Example_Secure_Boot', target=['esp32c3fpga', 'esp32s3fpga'], ignore=True) +def test_examples_security_secure_boot_corrupt_app_sig(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None + efuse_port = os.getenv('EFUSEPORT') + dut = env.get_dut('secure_boot', 'tools/test_apps/security/secure_boot', efuse_reset_port=efuse_port) + dut_start_secure_app(dut) + dut.reset_efuses() + dut.secure_boot_burn_en_bit() + dut.secure_boot_burn_digest('test_rsa_3072_key.pem', 0, 0) + app_bin = os.path.join(dut.app.binary_path, 'secure_boot.bin') + with open(app_bin, 'rb') as f: + signed_app = f.read() + + seeds = range(0, 384) + max_seed = max(seeds) + + for seed in seeds: + print('Case %d / %d' % (seed, max_seed)) + corrupt_app = corrupt_signature(signed_app, seed=seed) + dut.write_flash_data([(0x20000, BytesIO(corrupt_app))]) + dut.expect('Signature Check Failed', timeout=2) + dut.expect('image valid, signature bad', timeout=2) + + print('Testing invalid CRC...') + # Valid signature but invalid CRC + dut.reset_efuses() + dut.secure_boot_burn_en_bit() + dut.secure_boot_burn_digest('test_rsa_3072_key.pem', 0, 0) + + corrupt_app = corrupt_signature(signed_app, corrupt_sig=False, corrupt_crc=True) + dut.write_flash_data([(0x20000, BytesIO(corrupt_app))]) + dut.expect('Sig block 0 invalid: Stored CRC ends', timeout=2) + dut.expect('Secure boot signature verification failed', timeout=2) + dut.expect('No bootable app partitions in the partition table', timeout=2) + + +if __name__ == '__main__': + test_examples_security_secure_boot() + test_examples_security_secure_boot_key_combo() + test_examples_security_secure_boot_key_revoke() + test_examples_security_secure_boot_corrupt_bl_sig() + test_examples_security_secure_boot_corrupt_app_sig()