mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-09 23:54:33 +02:00
test_app: Added automated test cases for secure_boot
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
| Supported Targets | ESP32 | ESP32-S2 |
|
| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 |
|
||||||
| ----------------- | ----- | -------- |
|
| ----------------- | ----- | -------- | -------- |
|
||||||
|
|
||||||
# Secure Boot
|
# Secure Boot
|
||||||
|
|
||||||
@@ -9,7 +9,13 @@ The example checks if the secure boot feature is enabled/disabled and if enabled
|
|||||||
|
|
||||||
### Hardware Required
|
### 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)
|
||||||
|
|
||||||
|
It is recommended to use Secure Boot V2 from ESP32-ECO3 onwards.
|
||||||
|
|
||||||
### Configure the project
|
### Configure the project
|
||||||
|
|
||||||
@@ -54,3 +60,55 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
|
|||||||
|
|
||||||
## Troubleshooting
|
## 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 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
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
183
tools/test_apps/security/secure_boot/example_test.py
Normal file
183
tools/test_apps/security/secure_boot/example_test.py
Normal 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'], 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'], 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'], 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'], 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'], 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()
|
Reference in New Issue
Block a user