Merge branch 'feature/ttfw_esp32c3fpgadut' into 'master'

Automate flash encryption and secure boot tests on FPGA

See merge request espressif/esp-idf!12367
This commit is contained in:
Mahavir Jain 2021-08-30 10:24:24 +00:00
commit f1c6c68f19
5 changed files with 463 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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
```

View File

@ -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('<I', zlib.crc32(data + new_sig) & 0xffffffff)
else:
crc = struct.pack('<I', zlib.crc32(crc))
result = data + new_sig + crc + padding
assert len(result) == len(sig_block)
return result
def dut_start_secure_app(dut): # type: (ttfw_idf.IDFDUT) -> 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()