ESP32-S3 Secure boot+Flash Encryption - Correct procedure?
Posted: Fri May 17, 2024 9:00 am
Hi guys,
I have tried to enable Secure boot and Flash Encryption on ESP32-S3 for the past few days with limited success.
In the past, we have used ESP32 (ESP32-WROVER-E N16R8) in our product, where we used conbination of secure boot and flash encryption and it worked really well. We have implemented whole procedure per guide: https://docs.espressif.com/projects/esp ... flows.html and it worked as expected. But guide for ESP32-S3 (https://docs.espressif.com/projects/esp ... flows.html seems to be incomplete or faulty, because no matter what I did, it does not work as I was expecting.
For ESP32-S3, we have choosen ESP32-S3-WROOM-1 N16R8. All test were done on WROOM modules in programmer board such as https://www.aliexpress.com/item/1005006825819098.html.
Regarding ESP-IDF, I am using ESP-IDF "release\v4.4" as it's validated and working oka-ish for my use-case. On top of that I have did some fixes so moving to newer version is not priority right now.
My partition table for ESP32-S3 is below. I let partition tool calculate all offsets, I have only set first offset at 0x11000, partition table itself is located at offset 0x10000.
Current state:
My workflow is as this:
1) Flash Encryption key
I flash host-generated AES-256 (512 bit) key to "BLOCK_KEY_0" with function "XTS_AES_256_KEY", which flashes to "BLOCK_KEY0" and "BLOCK_KEY1" AES-256 key.
I do this with.
2) Flash public key digest for Secure boot.
I flash host-generated RSA3070 key digest to "BLOCK_KEY2" with function "SECURE_BOOT_DIGEST0".
I do this with
3) I burn eFuses.
I have set of eFuses, which are burned in sequence as written.
4) After these eFuses are burned, I enable secure boot, secure download mode and disable read for BLOCK4-BLOCK10 (keys).
*** Efuse *** *** Val ***
"SECURE_BOOT_EN" : "True",
"RD_DIS" : "3",
"ENABLE_SECURITY_DOWNLOAD" : "True",
For burning I send these command, in sequence as written from top to buttom.
NOTE: Once, I have enabled security download, I can no longer read/write any eFuse. Thats allright and expected behaviour.
5) I build binaries.
I use standard command "idf.py build" in Visual Studio code.
6) Erase whole flash memory
As security download is enabled, I was unable to use standard command "idf.py erase_flash" is ROM did not allow it. So I came up with variant that i generated fixed size bin files fulled with 0xFF values (=empty data) and write these to all relevant offsets.
This way whole address block from 0x0 up to end of factory_partition is erased.
7) Sign binaries.
I sign all binaries (bootloader, partition table, ota_init, factory fw).
8) Encryption signed binaries.
I enrypt all binaries (bootloader, partition table, ota_init, factory fw).
9) Flash signed and encrypted binaries.
10) Controller is in bootloader and waiting for reset.
I open VSCode console and controller runs for the first time and everything works as expected. It boot in bootloader, 2nd stage bootloader and then validates successfully factory partition and runs FW as it should.
But once I reset controller, It's unable to validate factory partition checksum and start to restart in loop.
I have tried to do OTA update for this first run if it will finish and it restart with several critical erros.
I have attached below my sdkconfig and log file, where I captured correct and incorrect run for first and second boot respectively.
I have tried to validate firmware image for chechsum with command
It returned correct value for plaintext and signed firmware binary. For encrypted file it returned error, but this should be correct behaviour. I have tried this validation for old ESP32 flow and it produced same results.
NOTE: checksum values in log and image_info are not same as I have tried to build FW several times and captured only handful of outputs to console.
Here is output to console:
Do you have any idea what I'm missing out or what is wrong? I'm slowly going crazy as I have been debugging this issue for the past week. :/ Any help appreciated.
I have tried to enable Secure boot and Flash Encryption on ESP32-S3 for the past few days with limited success.
In the past, we have used ESP32 (ESP32-WROVER-E N16R8) in our product, where we used conbination of secure boot and flash encryption and it worked really well. We have implemented whole procedure per guide: https://docs.espressif.com/projects/esp ... flows.html and it worked as expected. But guide for ESP32-S3 (https://docs.espressif.com/projects/esp ... flows.html seems to be incomplete or faulty, because no matter what I did, it does not work as I was expecting.
For ESP32-S3, we have choosen ESP32-S3-WROOM-1 N16R8. All test were done on WROOM modules in programmer board such as https://www.aliexpress.com/item/1005006825819098.html.
Regarding ESP-IDF, I am using ESP-IDF "release\v4.4" as it's validated and working oka-ish for my use-case. On top of that I have did some fixes so moving to newer version is not priority right now.
My partition table for ESP32-S3 is below. I let partition tool calculate all offsets, I have only set first offset at 0x11000, partition table itself is located at offset 0x10000.
Code: Select all
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x11000, 128K,
nvs_key, data, nvs_keys, 0x31000, 4K,
otadata, data, ota, 0x32000, 8K,
phy_init, data, phy, 0x34000, 4K,
factory, app, factory, 0x40000, 4M,
ota_0, app, ota_0, 0x440000, 4M,
ota_1, app, ota_1, 0x840000, 4M,
storage, data, spiffs, 0xc40000, 2880K,
fw_nvs_data,data, nvs, 0xf10000, 256K,
My workflow is as this:
1) Flash Encryption key
I flash host-generated AES-256 (512 bit) key to "BLOCK_KEY_0" with function "XTS_AES_256_KEY", which flashes to "BLOCK_KEY0" and "BLOCK_KEY1" AES-256 key.
I do this with
Code: Select all
command = ["espefuse", "--port", COMport, "--chip", "esp32s3", "burn_key", "BLOCK_KEY0", EncryptionKeyPath, "XTS_AES_256_KEY"]
2) Flash public key digest for Secure boot.
I flash host-generated RSA3070 key digest to "BLOCK_KEY2" with function "SECURE_BOOT_DIGEST0".
I do this with
Code: Select all
command = ["espefuse", "--port", COMport, "--chip", "esp32s3", "burn_key", "BLOCK_KEY2", SignDigestKeyPath, "SECURE_BOOT_DIGEST0"]
I have set of eFuses, which are burned in sequence as written.
Code: Select all
*** Efuse *** *** Val ***
"SPI_BOOT_CRYPT_CNT" : "7",
"DIS_DOWNLOAD_ICACHE" : "0x1",
"DIS_DOWNLOAD_DCACHE" : "0x1",
"DIS_PAD_JTAG" : "0x1",
"SOFT_DIS_JTAG" : "0x1",
"DIS_DIRECT_BOOT" : "0x1",
"DIS_USB_JTAG" : "0x1",
"DIS_DOWNLOAD_MANUAL_ENCRYPT" : "0x1",
command = ["espefuse", "--port", COMport, "--chip", "esp32s3", "burn_efuse", Efuse, Val]
subprocess.run(command, input = str("BURN").encode())
*** Efuse *** *** Val ***
"SECURE_BOOT_EN" : "True",
"RD_DIS" : "3",
"ENABLE_SECURITY_DOWNLOAD" : "True",
For burning I send these command, in sequence as written from top to buttom.
Code: Select all
if Efuse == "ENABLE_SECURITY_DOWNLOAD":
command = ["espefuse", "--port", COMport, "--chip", "esp32s3", "burn_efuse", "ENABLE_SECURITY_DOWNLOAD"]
elif Efuse == "SECURE_BOOT_EN":
command = ["espefuse", "--port", COMport, "--chip", "esp32s3", "burn_efuse", "SECURE_BOOT_EN"]
elif Efuse == "RD_DIS":
command = ["espefuse", "--port", COMport, "write_protect_efuse", "RD_DIS"]
else:
continue
subprocess.run(command, input = str("BURN").encode())
5) I build binaries.
I use standard command "idf.py build" in Visual Studio code.
6) Erase whole flash memory
As security download is enabled, I was unable to use standard command "idf.py erase_flash" is ROM did not allow it. So I came up with variant that i generated fixed size bin files fulled with 0xFF values (=empty data) and write these to all relevant offsets.
This way whole address block from 0x0 up to end of factory_partition is erased.
Code: Select all
adress = ["0x0", "0x40000"]
binaries = ["512k_0xff_file.bin", "4096k_0xff_file.bin"]
for i in range(0, len(binaries)):
bins = [addresses[i], folderPath + "\\" + binaries[i]]
command = ["esptool", "-p", COMport, "-b", "460800", "--no-stub", "--before", "default_reset", "--after", "no_reset"]
command = command + ["--chip", "esp32s3", "write_flash", "--flash_mode", "dio"]
command = command + ["--flash_size", "16MB", "--flash_freq", "80m"]
command = command + bins + ["--force"]
subprocess.run(command)
I sign all binaries (bootloader, partition table, ota_init, factory fw).
Code: Select all
command = ["espsecure", "sign_data", "--version", "2", "--keyfile", SignKeyPath, "--output", OutputBinPath, InputBinPath]
subprocess.run(command)
I enrypt all binaries (bootloader, partition table, ota_init, factory fw).
Code: Select all
command = ["espsecure", "encrypt_flash_data", "--aes_xts", "--keyfile", EncryptKeyPath, "--address", BinAdsress, "--output", OutputBinPath, InputBinPath]
subprocess.run(command)
Code: Select all
adress = ["0x0", "0x10000", "0x40000", "0x32000"]
binaries = ["bootloader_signed_encrypted.bin", "partition-table_signed_encrypted.bin", "esp32_evse_signed_encrypted.bin", "ota_data_initial_signed_encrypted.bin"]
for i in range(0, len(binaries)):
bins = [addresses[i], binaries[i]]
command = ["esptool", "-p", COMport, "-b", "460800", "--no-stub", "--before", "default_reset", "--after", "no_reset"]
command = command + ["--chip", "esp32s3", "write_flash", "--flash_mode", "dio"]
command = command + ["--flash_size", "16MB", "--flash_freq", "80m"]
command = command + bins + ["--force"]
subprocess.run(command)
I open VSCode console and controller runs for the first time and everything works as expected. It boot in bootloader, 2nd stage bootloader and then validates successfully factory partition and runs FW as it should.
But once I reset controller, It's unable to validate factory partition checksum and start to restart in loop.
I have tried to do OTA update for this first run if it will finish and it restart with several critical erros.
I have attached below my sdkconfig and log file, where I captured correct and incorrect run for first and second boot respectively.
I have tried to validate firmware image for chechsum with command
Code: Select all
esptool image_info esp32_evse_signed.bin --version 2
NOTE: checksum values in log and image_info are not same as I have tried to build FW several times and captured only handful of outputs to console.
Here is output to console:
Code: Select all
esptool.py v4.6.2
File size: 1314816 (bytes)
Detected image type: ESP32-S3
ESP32-S3 image header
=====================
Image version: 1
Entry point: 0x40375594
Segments: 8
Flash size: 16MB
Flash freq: 80m
Flash mode: DIO
ESP32-S3 extended image header
==============================
WP pin: 0xee (disabled)
Flash pins drive settings: clk_drv: 0x0, q_drv: 0x0, d_drv: 0x0, cs0_drv: 0x0, hd_drv: 0x0, wp_drv: 0x0
Chip ID: 9 (ESP32-S3)
Minimal chip revision: v0.0, (legacy min_rev = 0)
Maximal chip revision: v0.0
Segments information
====================
Segment Length Load addr File offs Memory types
------- ------- ---------- ---------- ------------
1 0x3e574 0x3c0f0020 0x00000018 DROM
2 0x01a7c 0x3fc975b0 0x0003e594 BYTE_ACCESSIBLE, MEM_INTERNAL, DRAM
3 0xe1d80 0x42000020 0x00040018 IROM
4 0x035b8 0x3fc9902c 0x00121da0 BYTE_ACCESSIBLE, MEM_INTERNAL, DRAM
5 0x135a4 0x40374000 0x00125360 MEM_INTERNAL, IRAM
6 0x00010 0x50000000 0x0013890c RTC_DATA
7 0x00028 0x600fe000 0x00138924 RTC_DRAM, RTC_IRAM
8 0x07674 0x00000000 0x00138954 PADDING
ESP32-S3 image footer
=====================
Checksum: 0xaa (valid)
Validation hash: 554343f3cc6ca7eb9e2c6ec0926d67cac7f57ed19de081623cb64367f7d8097f (valid)
Application information
=======================
Project name: esp32_evse
App version: V1.2.2D-Litva-57-g80a89a3-dirty
Compile time: May 16 2024 18:13:31
ELF file SHA256: db3c3ef25804e68ebc8bfcd0be494376bc03e09d5dd5de0f27d6f08c547e5225
ESP-IDF: v4.4-dev-3703-gddc44956bf-dirty
Secure version: 0