> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thistle.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# ESP32 Secure Boot V2

>  Enabling ESP32 Secure Boot V2 (SBV2): A Step-by-step Guide

<img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/esp32-wroom-32e.png?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=bb5ef51860cd82330989c7d8a7982015" alt="ESP32" width="600" height="302" data-path="images/esp32-wroom-32e.png" />

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](https://github.com/thistletech/esp32-devenvs/blob/main/README.md).

## Useful References

Here 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](https://thistle.tech/blog/esp32-secure-boot-v2-enablement-1)
* [Enabling Secure Boot V2 on ESP32 Platforms in
  Production](https://thistle.tech/blog/esp32-secure-boot-v2-enablement-2)
* [Espressif DevCon23 - Enabling Secure Boot (V2) on ESP32 Platforms in
  Development and Production](https://www.youtube.com/watch?v=a8u-iJT_z0U)

## 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

* ESP32-DevKitC-32E ([Mouser
  link](https://www.mouser.com/ProductDetail/356-ESP32-DEVKITC32E);
  [Datasheet](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html))

## In Development

### Steps to Enable SBV2 in Development

1. On a development machine running Linux, [install the Docker
   engine](https://docs.docker.com/engine/install/).

2. Build Docker container images for secure boot fuse blowing and firmware
   signing on development machine.

   ```bash theme={"dark"}
   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 \
     .
   ```

3. Connect an unfused ESP32 development board to development machine through
   USB, and run Docker container

   ```bash theme={"dark"}
   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
   ```

4. Inside the container, blow eFuses by flashing the prebuilt (and signed) "void
   app" firmware images

   ```bash theme={"dark"}
   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

   ```text theme={"dark"}
   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.

   ```bash theme={"dark"}
   # 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

   ```text theme={"dark"}
   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

1. 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

   ```bash theme={"dark"}
   # 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
   ```

2. Inside the container, flash the signed app

   ```bash theme={"dark"}
   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

1. Connect a mew unfused ESP32 development board to development machine through
   USB, and run Docker container

   ```bash theme={"dark"}
   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
   ```

2. Copy the `void_app` firmware images from the container to host.

   Inside the container, under `/home/esp/`

   ```bash theme={"dark"}
   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/
   ```

3. On host machine, open the [Thistle Control Center](https://app.thistle.tech)
   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.

   <img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/tcc_sign_void_app.png?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=71dac2fd6f82bbbdf1b15db9ad975e2d" alt="Signed Firmware Bundle View" width="1260" height="857" data-path="images/tcc_sign_void_app.png" />

   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.

   <img src="https://mintcdn.com/thistletechnologies/MrBm0BC7xpW_ySdM/images/tcc_signed_firmware.png?fit=max&auto=format&n=MrBm0BC7xpW_ySdM&q=85&s=d298d706a9f78a74a4bd840271a1cfb0" alt="&#x22;Signed Firmware&#x22;" width="1267" height="650" data-path="images/tcc_signed_firmware.png" />

4. Inside the container, flash the production signed bootloader and application
   images

   ```bash theme={"dark"}
   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

1. Connect the production fused ESP32 development board to development machine
   through USB, and run Docker container

   ```bash theme={"dark"}
   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
   ```

2. 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.

3. 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.

4. Inside the container, flash the production signed application image

   ```bash theme={"dark"}
   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
   ```

## 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](https://github.com/thistletech/esp32-devenvs) for
more detail.
