This is a step-by-step guide for enabling secure boot (Secure Boot V2 - SBV2) on ESP32. We will need a PC running Linux to run commands and communicate with the ESP32 devices.
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 ReferencesHere are some useful references to help understand secure boot and secure boot enablement operations for ESP32 platforms
- Enabling Secure Boot V2 on ESP32 Platforms in Development
- Enabling Secure Boot V2 on ESP32 Platforms in Production
- Espressif DevCon23 - Enabling Secure Boot (V2) on ESP32 Platforms in Development and Production
Two units of unfused ESP32 ECO3 and later: one for development fusing, the other for production fusing. Tested on the following development board
- ESP32-DevKitC-32E (Mouser link;Datasheet)
- 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
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.
In Development: Steps to Build and Sign Custom Application Image- 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 -r 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- 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
- 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
andvoid_app.bin
that were put in theshared/
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 aforementionedshared/
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:~/apps/void_app$ idf.py -p /dev/ttyUSB0 monitor
In Production: Steps to Sign Custom Application Image- 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 imageapps/hello_world/build/partition_table/partition-table.bin
to theshared/
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 theshared/
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:~/apps/void_app$ idf.py -p /dev/ttyUSB0 monitor
Enable SBV2 on Other ESP32 Platforms (ESP32-S2, ESP32-S3)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.
Comments