pytest_secure_boot.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Unlicense OR CC0-1.0
  3. from __future__ import print_function
  4. import os
  5. import struct
  6. import zlib
  7. import pytest
  8. from pytest_embedded import Dut
  9. # To prepare a runner for these tests,
  10. # 1. Connect an FPGA with C3 image
  11. # 2. Use a COM port for programming and export it as ESPPORT
  12. # e.g export ESPPORT=/dev/ttyUSB0
  13. # 3. Use another COM port for resetting efuses and connect its DTR pin to efuse reset pin on the FPGA board.
  14. # Export it as EFUSEPORT
  15. # e.g export EFUSEPORT=/dev/ttyUSB1
  16. # 4. Run these tests
  17. def corrupt_signature(signed_bootloader, seed=0, corrupt_sig=True, corrupt_crc=False, corrupt_single_block=None):
  18. # type: (bytes, int, bool, bool, int) -> bytes
  19. image = signed_bootloader[:-4096]
  20. signature = signed_bootloader[-4096:]
  21. sig_blocks = (signature[0:1216], signature[1216:2432], signature[2432:3648])
  22. new_blocks = tuple(corrupt_sig_block(s, seed, corrupt_sig, corrupt_crc) for s in sig_blocks)
  23. # if corrupt_single_block is None, corrupt all blocks
  24. # otherwise, only corrupt the one with that index set
  25. corr_sig_blocks = tuple(new_blocks[n] if corrupt_single_block in [None, n] else sig_blocks[n] for n in range(3))
  26. return image + b''.join(corr_sig_blocks) + signature[3648:]
  27. def corrupt_sig_block(sig_block, seed=0, corrupt_sig=True, corrupt_crc=False):
  28. # type: (bytes, int, bool, bool) -> bytes
  29. assert len(sig_block) == 1216
  30. magic = sig_block[0]
  31. assert magic in [0xe7, 0xff]
  32. if magic != 0xe7:
  33. return sig_block # not valid
  34. data = sig_block[:812]
  35. new_sig = sig = sig_block[812:1196]
  36. crc = sig_block[1196:1200]
  37. padding = sig_block[1200:1216]
  38. if corrupt_sig:
  39. corrupt_idx = seed % len(sig)
  40. corrupt_delta = zlib.crc32(bytes(seed)) & 0xFF
  41. if corrupt_delta == 0:
  42. corrupt_delta = 1
  43. new_byte = sig[corrupt_idx] ^ corrupt_delta
  44. new_sig = sig[0:corrupt_idx] + bytes([new_byte]) + sig[corrupt_idx + 1:]
  45. assert new_sig != sig
  46. if not corrupt_crc:
  47. crc = struct.pack('<I', zlib.crc32(data + new_sig) & 0xffffffff)
  48. else:
  49. crc = struct.pack('<I', zlib.crc32(crc))
  50. result = data + new_sig + crc + padding
  51. assert len(result) == len(sig_block)
  52. return result
  53. def dut_start_secure_app(dut: Dut) -> None:
  54. dut.serial.erase_app_header()
  55. dut.serial.reset_efuses()
  56. dut.burn_wafer_version()
  57. dut.serial.bootloader_flash(os.path.join(dut.app.binary_path, 'bootloader/bootloader.bin'))
  58. dut.serial.partition_table_flash(os.path.join(dut.app.binary_path, 'partition_table/partition-table.bin'))
  59. dut.serial.app_flash(os.path.join(dut.app.binary_path, 'secure_boot.bin'))
  60. # Test secure boot flow.
  61. # Correctly signed bootloader + correctly signed app should work
  62. @pytest.mark.esp32c3
  63. @pytest.mark.esp32s3
  64. @pytest.mark.esp32p4
  65. def test_examples_security_secure_boot(dut: Dut) -> None:
  66. dut_start_secure_app(dut)
  67. dut.expect('Secure Boot is enabled', timeout=10)
  68. dut.serial.reset_efuses()
  69. dut.burn_wafer_version()
  70. # Test efuse key index and key block combination.
  71. # Any key index can be written to any key block and should work
  72. @pytest.mark.esp32c3
  73. @pytest.mark.esp32s3
  74. @pytest.mark.esp32p4
  75. # Increasing the test timeout to 1200s as the test runs for 18 iterations
  76. # and thus the default 600s timeout is not sufficient
  77. @pytest.mark.timeout(1200)
  78. def test_examples_security_secure_boot_key_combo(dut: Dut) -> None:
  79. dut_start_secure_app(dut)
  80. dut.expect('Secure Boot is enabled', timeout=10)
  81. for index in range(3):
  82. for block in range(6):
  83. dut.serial.reset_efuses()
  84. dut.burn_wafer_version()
  85. dut.secure_boot_burn_en_bit()
  86. dut.secure_boot_burn_digest('test_rsa_3072_key.pem', index, block)
  87. dut.expect('Secure Boot is enabled', timeout=10)
  88. dut.serial.reset_efuses()
  89. dut.burn_wafer_version()
  90. # Test secure boot key revoke.
  91. # If a key is revoked, bootloader signed with that key should fail verification
  92. @pytest.mark.esp32c3
  93. @pytest.mark.esp32s3
  94. @pytest.mark.esp32p4
  95. def test_examples_security_secure_boot_key_revoke(dut: Dut) -> None:
  96. dut_start_secure_app(dut)
  97. dut.expect('Secure Boot is enabled', timeout=10)
  98. for index in range(3):
  99. dut.serial.reset_efuses()
  100. dut.burn_wafer_version()
  101. dut.secure_boot_burn_en_bit()
  102. dut.serial.burn_efuse('SECURE_BOOT_KEY_REVOKE%d' % index, 1)
  103. dut.secure_boot_burn_digest('test_rsa_3072_key.pem', index, 0)
  104. dut.expect('secure boot verification failed', timeout=5)
  105. dut.serial.reset_efuses()
  106. dut.burn_wafer_version()
  107. # Test bootloader signature corruption.
  108. # Corrupt one byte at a time of bootloader signature and test that the verification fails
  109. @pytest.mark.esp32c3
  110. @pytest.mark.esp32s3
  111. @pytest.mark.esp32p4
  112. @pytest.mark.timeout(18000)
  113. # Increasing the test timeout to 18000s as the test runs for 384 iterations
  114. # and thus the default 600s timeout is not sufficient
  115. def test_examples_security_secure_boot_corrupt_bl_sig(dut: Dut) -> None:
  116. dut_start_secure_app(dut)
  117. dut.expect('Secure Boot is enabled', timeout=10)
  118. bootloader_bin = os.path.join(dut.app.binary_path, 'bootloader/bootloader.bin')
  119. with open(bootloader_bin, 'rb') as f:
  120. signed_bl = f.read()
  121. seeds = range(0, 384)
  122. max_seed = max(seeds)
  123. for seed in seeds:
  124. print('Case %d / %d' % (seed, max_seed))
  125. corrupt_bl = corrupt_signature(signed_bl, seed=seed)
  126. with open('corrupt_bl.bin', 'wb') as corrupt_file:
  127. corrupt_file.write(corrupt_bl)
  128. dut.serial.reset_efuses()
  129. dut.burn_wafer_version()
  130. dut.serial.bootloader_flash('corrupt_bl.bin')
  131. dut.secure_boot_burn_en_bit()
  132. dut.secure_boot_burn_digest('test_rsa_3072_key.pem', 0, 0)
  133. # Though the order of flashing and burning efuse would not effect the test,
  134. # if we flash bootlader before burning en bit, even with no_stub = True
  135. # it still calls run_stub() and throws an error as it fails to start stub.
  136. dut.expect('Signature Check Failed', timeout=10)
  137. dut.serial.reset_efuses()
  138. dut.burn_wafer_version()
  139. # Test app signature corruption.
  140. # Corrupt app signature, one byte at a time, and test that the verification fails
  141. @pytest.mark.esp32c3
  142. @pytest.mark.esp32s3
  143. @pytest.mark.esp32p4
  144. @pytest.mark.timeout(18000)
  145. # Increasing the test timeout to 18000s as the test runs for 385 iterations
  146. # and thus the default 600s timeout is not sufficient
  147. def test_examples_security_secure_boot_corrupt_app_sig(dut: Dut) -> None:
  148. dut_start_secure_app(dut)
  149. dut.expect('Secure Boot is enabled', timeout=10)
  150. app_bin = os.path.join(dut.app.binary_path, 'secure_boot.bin')
  151. with open(app_bin, 'rb') as f:
  152. signed_app = f.read()
  153. seeds = range(0, 384)
  154. max_seed = max(seeds)
  155. for seed in seeds:
  156. print('Case %d / %d' % (seed, max_seed))
  157. corrupt_app = corrupt_signature(signed_app, seed=seed)
  158. with open('corrupt_app.bin', 'wb') as corrupt_file:
  159. corrupt_file.write(corrupt_app)
  160. dut.serial.reset_efuses()
  161. dut.burn_wafer_version()
  162. dut.serial.app_flash('corrupt_app.bin')
  163. dut.secure_boot_burn_en_bit()
  164. dut.secure_boot_burn_digest('test_rsa_3072_key.pem', 0, 0)
  165. dut.expect('Signature Check Failed', timeout=10)
  166. dut.expect('image valid, signature bad', timeout=2)
  167. print('Testing invalid CRC...')
  168. # Valid signature but invalid CRC
  169. dut.serial.reset_efuses()
  170. dut.burn_wafer_version()
  171. corrupt_app = corrupt_signature(signed_app, corrupt_sig=False, corrupt_crc=True)
  172. with open('corrupt_app.bin', 'wb') as corrupt_file:
  173. corrupt_file.write(corrupt_app)
  174. dut.serial.app_flash('corrupt_app.bin')
  175. dut.secure_boot_burn_en_bit()
  176. dut.secure_boot_burn_digest('test_rsa_3072_key.pem', 0, 0)
  177. dut.expect('Sig block 0 invalid: {}'.format('CRC mismatch' if dut.target == 'esp32p4' else 'Stored CRC ends'), timeout=2)
  178. dut.expect('Secure boot signature verification failed', timeout=2)
  179. dut.expect('No bootable app partitions in the partition table', timeout=2)