We will assume the IDF bootloader and the associated application images, and use
them as an example in this guide. For enabling SBV2 with the MCUboot bootloader
and Zephyr application images, please refer to this
document.
Useful References
Here are some useful references to help understand secure boot and secure boot
enablement operations for ESP32 platforms
Required Hardware
Two units of unfused ESP32 ECO3 and later: one for development fusing, the other
for production fusing. Tested on the following development board
In Development
Steps to Enable SBV2 in Development
-
On a development machine running Linux, install the Docker
engine.
-
Build Docker container images for secure boot fuse blowing and firmware
signing on development machine.
git clone https://github.com/thistletech/esp32-devenvs.git
cd esp32-devenvs/esp32
docker build -f Dockerfile.esp32_fuseblower -t esp32fb:dev \
--build-arg IDF_SDKCONFIG=sdkconfig.sbv2_nojtag \
--build-arg SBV2_PRIVATE_KEY=sbv2_private_dev.pem \
.
-
Connect an unfused ESP32 development board to development machine through
USB, and run Docker container
mkdir -p shared
chmod 777 -R shared/
# Modify the device node /dev/ttyUSB0 as needed for direct board interactions
# from inside the container
docker run --device=/dev/ttyUSB0 -v "$(pwd)/shared":/home/esp/shared -it esp32fb:dev
-
Inside the container, blow eFuses by flashing the prebuilt (and signed) “void
app” firmware images
esp@c29e740b2630:~$ source ${HOME}/esp-idf/export.sh
esp@c29e740b2630:~$ cd apps/void_app
# Flash bootloader
# Adjust device node (-p option) as needed.
# ESP32's bootloader shall be flashed at offset 0x1000
esp@c29e740b2630:~/apps/void_app$ esptool.py --chip esp32 \
--port=/dev/ttyUSB0 \
--baud=460800 \
--before=default_reset \
--after=no_reset \
--no-stub \
write_flash \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x1000 build/bootloader/bootloader.bin
# Flash partition table and app
# Adjust device node (-p option) as needed
esp@c29e740b2630:~/apps/void_app$ esptool.py -c esp32 \
-p /dev/ttyUSB0 \
-b 460800 \
--before=default_reset \
--after=hard_reset \
--no-stub \
write_flash \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x20000 build/void_app.bin \
0x10000 build/partition_table/partition-table.bin
# Should see "I'm the void app. I do nothing." in serial console output
esp@c29e740b2630:~/apps/void_app$ idf.py -p /dev/ttyUSB0 monitor
Inspect the serial console output, and you should see log entries such as
I (206) secure_boot_v2: Verifying with RSA-PSS...
I (212) secure_boot_v2: Signature verified successfully!
Now secure boot is enabled. From now on, any update in the application image
will required the image to be signed for it to boot on this board. You may
try to flash an unsigned image and observe the boot failure.
# Erase flash
esp@c29e740b2630:~/apps/void_app$ esptool.py --chip esp32 erase_flash --force
# Flash the bootloader image, with the --force option this time as required
# by SBV2-enabled device
esp@c29e740b2630:~/apps/void_app$ esptool.py --chip esp32 \
--port=/dev/ttyUSB0 \
--baud=460800 \
--before=default_reset \
--after=no_reset \
--no-stub \
write_flash \
--force \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x1000 build/bootloader/bootloader.bin
# Flash the unsigned void app image as well as the partition table image
esp@c29e740b2630:~/apps/void_app$ esptool.py -c esp32 \
-p /dev/ttyUSB0 \
-b 460800 \
--before=default_reset \
--after=hard_reset \
--no-stub \
write_flash \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x20000 build/void_app-unsigned.bin \
0x10000 build/partition_table/partition-table.bin
If you run idf.py monitor
now, you should be able to see the device
entering a boot loop showing log entries as below
I (222) esp_image: Verifying image signature...
I (223) secure_boot_v2: Verifying with RSA-PSS...
No signature block magic byte found at signature sector (found 0xff not 0xe7). Image not V2 signed?
E (231) secure_boot_v2: Secure Boot V2 verification failed.
E (237) esp_image: Secure boot signature verification failed
I (244) esp_image: Calculating simple hash to check for corruption...
W (304) esp_image: image valid, signature bad
Steps to Build and Sign Custom Application Image in Development
-
On host machine, drop the application source folder into the shared/
directory, and inside the container, copy the application source folder to
/home/esp/app/
. We will use an ESP-IDF sample application as an example.
Inside container
# Set up environment. PWD is /home/esp
esp@c29e740b2630:~$ . esp-idf/export.sh
# Get sample app - hello_world and build for esp32 target
esp@c29e740b2630:~$ cp -r esp-idf/examples/get-started/hello_world/ apps/
esp@c29e740b2630:~$ cd apps/hello_world
# Configure app. You may adjust the sdkconfig settings as needed for your app
esp@c29e740b2630:~/apps/hello_world$ rm -rf sdkconfig sdkconfig.ci
esp@c29e740b2630:~/apps/hello_world$ ln -s ../sbv2_private_pem.app sbv2_private.pem
esp@c29e740b2630:~/apps/hello_world$ ln -s ../sdkconfig.apps sdkconfig.defaults
esp@c29e740b2630:~/apps/hello_world$ idf.py set-target esp32
esp@c29e740b2630:~/apps/hello_world$ idf.py build
-
Inside the container, flash the signed app
esp@c29e740b2630:~/apps/hello_world$ idf.py flash
# Display serial output. crtl+] to exit
esp@c29e740b2630:~/apps/hello_world$ idf.py monitor
In Production
Steps to Enable SBV2 in Production
-
Connect a mew unfused ESP32 development board to development machine through
USB, and run Docker container
mkdir -p shared
chmod 777 -R shared/
# Modify the device node /dev/ttyUSB0 as needed for direct board interactions
# from inside the container
docker run --device=/dev/ttyUSB0 -v "$(pwd)/shared":/home/esp/shared -it esp32fb:dev
-
Copy the void_app
firmware images from the container to host.
Inside the container, under /home/esp/
esp@c29e740b2630:~$ cp apps/void_app/build/partition_table/partition-table.bin shared/
esp@c29e740b2630:~$ cp apps/void_app/build/bootloader/bootloader.bin shared/
esp@c29e740b2630:~$ cp apps/void_app/build/void_app.bin shared/
-
On host machine, open the Thistle Control Center
from a web browser, create a project, and go go the “Signed Firmware” menu.
Click the “+Signed Firmware Bundle” button to add a new signed firmware
bundle: pick a name for it, choose “ESP32” and “ESP-IDF” as the hardware type
and firmware type, respectively, and upload bootloader.bin
and
void_app.bin
that were put in the shared/
folder.
Click the “Create” button to sign the bootloader and application images using
a production signing key managed in a cloud key management system. Download
the production signed images suffixed with .patched_<timestamp>
to the
aforementioned shared/
folder.
-
Inside the container, flash the production signed bootloader and application
images
esp@c29e740b2630:~$ mkdir -p prod_signed/
esp@c29e740b2630:~$ mv shared/void_app.bin.patched_1742873559.3631885 shared/bootloader.bin.patched_1742873561.3099582 shared/partition-table.bin prod_signed/
esp@c29e740b2630:~$ source ${HOME}/esp-idf/export.sh
esp@c29e740b2630:~$ cd prod_signed/
# Flash bootloader
# Adjust device node (-p option) as needed.
# ESP32's bootloader shall be flashed at offset 0x1000
esp@c29e740b2630:~/prod_signed $ esptool.py --chip esp32 \
--port=/dev/ttyUSB0 \
--baud=460800 \
--before=default_reset \
--after=no_reset \
--no-stub \
write_flash \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x1000 bootloader.bin.patched_1742873561.3099582
# Flash partition table and app
# Adjust device node (-p option) as needed
esp@c29e740b2630:~/prod_signed$ esptool.py -c esp32 \
-p /dev/ttyUSB0 \
-b 460800 \
--before=default_reset \
--after=hard_reset \
--no-stub \
write_flash \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x20000 void_app.bin.patched_1742873559.3631885 \
0x10000 partition-table.bin
# Should see "I'm the void app. I do nothing." in serial console output
esp@c29e740b2630:~/prod_signed$ cd ${HOME}/apps/void_app/
esp@c29e740b2630:~/apps/void_app$ idf.py -p /dev/ttyUSB0 monitor
Steps to Sign Custom Application Image in Production
-
Connect the production fused ESP32 development board to development machine
through USB, and run Docker container
mkdir -p shared
chmod 777 -R shared/
# Modify the device node /dev/ttyUSB0 as needed for direct board interactions
# from inside the container
docker run --device=/dev/ttyUSB0 -v "$(pwd)/shared":/home/esp/shared -it esp32fb:dev
-
Inside the container, build the application image by following the steps
described in Section “Steps to Build and Sign Custom Application Image in
Development” above, and copy the development signed application image
apps/hello_world/build/hello_world.bin
and the partition table image
apps/hello_world/build/partition_table/partition-table.bin
to the shared/
folder.
-
On host machine, create a new signed firmware bundle in TCC in the same
project in which the void_app
got signed, and upload the development signed
application image to it (one can ignore the bootloader image if there’s no
change in the bootloader). Pick a meaningful name for the signed firmware
bundle, and choose “ESP32” and “ESP-IDF” as the hardware type and firmware
type, respectively.
Click the “Create” button to sign the application image using the production
signing key managed in a cloud key management system. Download the production
signed images suffixed with .patched_<timestamp>
to the shared/
folder.
-
Inside the container, flash the production signed application image
esp@c29e740b2630:~$ mkdir -p prod_signed/
esp@c29e740b2630:~$ mv shared/hello_world.bin.patched_<timestamp> shared/partition-table.bin prod_signed/
esp@c29e740b2630:~$ source ${HOME}/esp-idf/export.sh
esp@c29e740b2630:~$ cd prod_signed/
# Flash partition table and app
# Adjust device node (-p option) as needed
esp@c29e740b2630:~/prod_signed$ esptool.py -c esp32 \
-p /dev/ttyUSB0 \
-b 460800 \
--before=default_reset \
--after=hard_reset \
--no-stub \
write_flash \
--flash_mode dio \
--flash_freq 80m \
--flash_size keep \
0x20000 hello_world.bin.patched_<timestamp> \
0x10000 partition-table.bin
# Check if the application image boots. crtl+] to exit
esp@c29e740b2630:~/prod_signed$ cd ${HOME}/apps/hello_world
esp@c29e740b2630:~/apps/hello_world$ idf.py -p /dev/ttyUSB0 monitor
On other ESP32 platforms, e.g., ESP32-S2 and ESP32-S3, one can enable secure
boot V2 in a similar manner, in development and in production. Please refer to
our Github repository
thistletech/esp32-devenvs for
more detail.