Skip to content

Secure Boot V2

Important

This document is about Secure Boot V2, supported on ESP32 (ECO 3 onwards)

For ESP32 before ECO3, refer to Secure Boot. It is recommended that users use Secure Boot V2 if they have a chip version that supports it. Secure Boot V2 is safer and more flexible than Secure Boot V1.

Secure Boot V2 uses RSA-PSS based app and bootloader (Second Stage Bootloader) verification. This document can also be used as a reference for signing apps using the RSA-PSS scheme without signing the bootloader.

Secure Boot V2 and RSA scheme (App Signing Scheme) options are available for ESP32 from ECO3 onwards. To use these options in menuconfig, set CONFIG_ESP32_REV_MIN greater than or equal to Rev 3.

Background

Secure Boot protects a device from running any unauthorized (i.e., unsigned) code by checking that each piece of software that is being booted is signed. On an ESP32, these pieces of software include the second stage bootloader and each application binary. Note that the first stage bootloader does not require signing as it is ROM code thus cannot be changed.

A RSA based Secure Boot verification scheme (Secure Boot V2) is implemented on ESP32 (ECO 3 onwards).

The Secure Boot process on the ESP32 involves the following steps:

  1. When the first stage bootloader loads the second stage bootloader, the second stage bootloader's RSA-PSS signature is verified. If the verification is successful, the second stage bootloader is executed.

  2. When the second stage bootloader loads a particular application image, the application's RSA-PSS signature is verified. If the verification is successful, the application image is executed.

Advantages

  • The RSA-PSS public key is stored on the device. The corresponding RSA-PSS private key is kept at a secret place and is never accessed by the device.

  • Only one public key can be generated and stored in the chip during manufacturing.

  • Same image format and signature verification method is applied for applications and software bootloader.

  • No secrets are stored on the device. Therefore, it is immune to passive side-channel attacks (timing or power analysis, etc.)

Secure Boot V2 Process

This is an overview of the Secure Boot V2 Process. Instructions how to enable Secure Boot are supplied in section How To Enable Secure Boot V2.

Secure Boot V2 verifies the bootloader image and application binary images using a dedicated signature block. Each image has a separately generated signature block which is appended to the end of the image.

Only one signature block can be appended to the bootloader or application image in ESP32 ECO3.

Each signature block contains a signature of the preceding image as well as the corresponding RSA-3072 public key. For more details about the format, refer to Signature Block Format. A digest of the RSA-3072 public key is stored in the eFuse.

The application image is not only verified on every boot but also on each over the air (OTA) update. If the currently selected OTA app image cannot be verified, the bootloader will fall back and look for another correctly signed application image.

The Secure Boot V2 process follows these steps:

  1. On startup, the ROM code checks the Secure Boot V2 bit in the eFuse. If Secure Boot is disabled, a normal boot will be executed. If Secure Boot is enabled, the boot will proceed according to the following steps.

  2. The ROM code verifies the bootloader's signature block (Verifying a Signature Block). If this fails, the boot process will be aborted.

  3. The ROM code verifies the bootloader image using the raw image data, its corresponding signature block(s), and the eFuse (Verifying an Image). If this fails, the boot process will be aborted.

  4. The ROM code executes the bootloader.

  5. The bootloader verifies the application image's signature block (Verifying a Signature Block). If this fails, the boot process will be aborted.

  6. The bootloader verifies the application image using the raw image data, its corresponding signature blocks and the eFuse (Verifying an Image). If this fails, the boot process will be aborted. If the verification fails but another application image is found, the bootloader will then try to verify that other image using steps 5 to 7. This repeats until a valid image is found or no other images are found.

  7. The bootloader executes the verified application image.

Signature Block Format

The signature block starts on a 4 KB aligned boundary and has a flash sector of its own. The signature is calculated over all bytes in the image including the padding bytes (Secure Padding).

The content of each signature block is shown in the following table:

Content of a RSA Signature Block OffsetSize (bytes)Description
01Magic byte
11Version number byte (currently 0x02), 0x01 is for Secure Boot V1.
22Padding bytes, Reserved. Should be zero.
432SHA-256 hash of only the image content, not including the signature block.
36384RSA Public Modulus used for signature verification. (value ‘n’ in RFC8017).
4204RSA Public Exponent used for signature verification (value ‘e’ in RFC8017).
424384Pre-calculated R, derived from ‘n’.
8084Pre-calculated M’, derived from ‘n’
812384RSA-PSS Signature result (section 8.1.1 of RFC8017) of image content, computed using following PSS parameters: SHA256 hash, MGF1 function, salt length 32 bytes, default trailer field (0xBC).
11964CRC32 of the preceding 1196 bytes.
120016Zero padding to length 1216 bytes.

Note

R and M' are used for hardware-assisted Montgomery Multiplication.

The remainder of the signature sector is erased flash (0xFF) which allows writing other signature blocks after previous signature block.

Secure Padding

In Secure Boot V2 scheme, the application image is padded to the flash MMU page size boundary to ensure that only verified contents are mapped in the internal address space. This is known as secure padding. Signature of the image is calculated after padding and then signature block (4KB) gets appended to the image.

  • Default flash MMU page size is 64KB

  • Secure padding is applied through the option --secure-pad-v2 in the elf2image conversion using esptool.py

Following table explains the Secure Boot V2 signed image with secure padding and signature block appended:

Contents of a signed application OffsetSize (KB)Description
0580Unsigned application size (as an example)
58060Secure padding (aligned to next 64KB boundary)
6404Signature block

Note

Please note that the application image always starts on the next flash MMU page size boundary (default 64KB) and hence the space left over after the signature block shown above can be utilized to store any other data partitions (e.g., nvs).

Verifying a Signature Block

A signature block is "valid" if the first byte is 0xe7 and a valid CRC32 is stored at offset 1196. Otherwise it is invalid.

Verifying an Image

An image is "verified" if the public key stored in any signature block is valid for this device, and if the stored signature is valid for the image data read from flash.

  1. Compare the SHA-256 hash digest of the public key embedded in the bootloader's signature block with the digest(s) saved in the eFuses. If public key's hash does not match any of the hashes from the eFuses, the verification fails.

  2. Generate the application image digest and match it with the image digest in the signature block. If the digests do not match, the verification fails.

  3. Use the public key to verify the signature of the bootloader image, using RSA-PSS (section 8.1.2 of RFC8017) with the image digest calculated in step (2) for comparison.

Bootloader Size

Enabling Secure boot and/or flash encryption will increase the size of bootloader, which might require updating partition table offset. See Bootloader Size.

In the case when CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES is disabled, the bootloader is sector padded (4KB) using the --pad-to-size option in elf2image command of esptool.

eFuse Usage

ESP32-ECO3:

  • ABS_DONE_1 - Enables Secure Boot protection on boot.

  • BLK2 - Stores the SHA-256 digest of the public key. SHA-256 hash of public key modulus, exponent, pre-calculated R & M' values (represented as 776 bytes – offsets 36 to 812 - as per the Signature Block Format) is written to an eFuse key block. The write-protection bit must be set, but the read-protection bit must not.

The key(s) must be readable in order to give software access to it. If the key(s) is read-protected then the software reads the key(s) as all zeros and the signature verification process will fail, and the boot process will be aborted.

How To Enable Secure Boot V2

  1. Open the Project Configuration Menu, in "Security features" set "Enable hardware Secure Boot in bootloader" to enable Secure Boot.

  2. For ESP32, Secure Boot V2 is available only ESP32 ECO3 onwards. To view the "Secure Boot V2" option the chip revision should be changed to revision 3 (ESP32- ECO3). To change the chip revision, set "Minimum Supported ESP32 Revision" to Rev 3 in "Component Config" -> "ESP32- Specific".

  3. Specify the path to Secure Boot signing key, relative to the project directory.

  4. Select the desired UART ROM download mode in "UART ROM download mode". By default the UART ROM download mode has been kept enabled in order to prevent permanently disabling it in the development phase, this option is a potentially insecure option. It is recommended to disable the UART download mode for better security.

  5. Set other menuconfig options (as desired). Then exit menuconfig and save your configuration.

  6. The first time you run idf.py build, if the signing key is not found then an error message will be printed with a command to generate a signing key via espsecure.py generate_signing_key.

Important

A signing key generated this way will use the best random number source available to the OS and its Python installation (/dev/urandom on OSX/Linux and CryptGenRandom() on Windows). If this random number source is weak, then the private key will be weak.

Important

For production environments, we recommend generating the key pair using openssl or another industry standard encryption program. See Generating Secure Boot Signing Key for more details.

  1. Run idf.py bootloader to build a Secure Boot enabled bootloader. The build output will include a prompt for a flashing command, using esptool.py write_flash.

  2. When you are ready to flash the bootloader, run the specified command (you have to enter it yourself, this step is not performed by the build system) and then wait for flashing to complete.

  3. Run idf.py flash to build and flash the partition table and the just-built app image. The app image will be signed using the signing key you generated in step 6.

Note

idf.py flash does not flash the bootloader if Secure Boot is enabled.

  1. Reset the ESP32 and it will boot the software bootloader you flashed. The software bootloader will enable Secure Boot on the chip, and then it verifies the app image signature and boots the app. You should watch the serial console output from the ESP32 to verify that Secure Boot is enabled and no errors have occurred due to the build configuration.

Note

Secure boot will not be enabled until after a valid partition table and app image have been flashed. This is to prevent accidents before the system is fully configured.

Note

If the ESP32 is reset or powered down during the first boot, it will start the process again on the next boot.

  1. On subsequent boots, the Secure Boot hardware will verify the software bootloader has not changed and the software bootloader will verify the signed app image (using the validated public key portion of its appended signature block).

Restrictions After Secure Boot Is Enabled

  • Any updated bootloader or app will need to be signed with a key matching the digest already stored in eFuse.

  • After Secure Boot is enabled, no further eFuses can be read protected. (If Flash Encryption is enabled then the bootloader will ensure that any flash encryption key generated on first boot will already be read protected.) If CONFIG_SECURE_BOOT_INSECURE is enabled then this behavior can be disabled, but this is not recommended.

  • Please note that enabling Secure Boot or flash encryption disables the USB-OTG USB stack in the ROM, disallowing updates via the serial emulation or Device Firmware Update (DFU) on that port.

Generating Secure Boot Signing Key

The build system will prompt you with a command to generate a new signing key via espsecure.py generate_signing_key.

The --version 2 parameter will generate the RSA 3072 private key for Secure Boot V2. Additionally --scheme rsa3072 can be passed as well to generate RSA 3072 private key

The strength of the signing key is proportional to (a) the random number source of the system, and (b) the correctness of the algorithm used. For production devices, we recommend generating signing keys from a system with a quality entropy source, and using the best available RSA-PSS key generation utilities.

For example, to generate a signing key using the openssl command line:

For RSA 3072

openssl genrsa -out my_secure_boot_signing_key.pem 3072

Remember that the strength of the Secure Boot system depends on keeping the signing key private.

Remote Signing of Images

Signing Using espsecure.py

For production builds, it can be good practice to use a remote signing server rather than have the signing key on the build machine (which is the default esp-idf Secure Boot configuration). The espsecure.py command line program can be used to sign app images & partition table data for Secure Boot, on a remote system.

To use remote signing, disable the option CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES and build the firmware. The private signing key does not need to be present on the build system.

After the app image and partition table are built, the build system will print signing steps using espsecure.py:

espsecure.py sign_data BINARY_FILE --version 2 --keyfile PRIVATE_SIGNING_KEY

The above command appends the image signature to the existing binary. You can use the --output argument to write the signed binary to a separate file:

espsecure.py sign_data --version 2 --keyfile PRIVATE_SIGNING_KEY --output SIGNED_BINARY_FILE BINARY_FILE

Signing Using Pre-calculated Signatures

If you have valid pre-calculated signatures generated for an image and their corresponding public keys, you can use these signatures to generate a signature sector and append it to the image. Note that the pre-calculated signature should be calculated over all bytes in the image including the secure-padding bytes.

In such cases, the firmware image should be built by disabling the option CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES. This image will be secure-padded and to generate a signed binary use the following command:

espsecure.py sign_data --version 2 --pub-key PUBLIC_SIGNING_KEY --signature SIGNATURE_FILE --output SIGNED_BINARY_FILE BINARY_FILE

The above command verifies the signature, generates a signature block (refer to Signature Block Format) and appends it to the binary file.

Signing Using an External Hardware Security Module (HSM)

For security reasons, you might also use an external Hardware Security Module (HSM) to store your private signing key, which cannot be accessed directly but has an interface to generate the signature of a binary file and its corresponding public key.

In such cases, disable the option CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES and build the firmware. This secure-padded image then can be used to supply the external HSM for generating a signature. Refer to Signing using an External HSM to generate a signed image.

Note

For all the above three remote signing workflows, the signed binary is written to the filename provided to the --output argument.

Secure Boot Best Practices

  • Generate the signing key on a system with a quality source of entropy.

  • Keep the signing key private at all times. A leak of this key will compromise the Secure Boot system.

  • Do not allow any third party to observe any aspects of the key generation or signing process using espsecure.py. Both processes are vulnerable to timing or other side-channel attacks.

  • Enable all Secure Boot options in the Secure Boot Configuration. These include flash encryption, disabling of JTAG, disabling BASIC ROM interpreter, and disabling the UART bootloader encrypted flash access.

  • Use Secure Boot in combination with Flash Encryption to prevent local readout of the flash contents.

Technical Details

The following sections contain low-level reference descriptions of various Secure Boot elements:

Manual Commands

Secure boot is integrated into the esp-idf build system, so idf.py build will sign an app image and idf.py bootloader will produce a signed bootloader if secure signed binaries on build is enabled.

However, it is possible to use the espsecure.py tool to make standalone signatures and digests.

To sign a binary image:

espsecure.py sign_data --version 2 --keyfile ./my_signing_key.pem --output ./image_signed.bin image-unsigned.bin

Keyfile is the PEM file containing an RSA-3072 private signing key.

Secure Boot & Flash Encryption

If Secure Boot is used without Flash Encryption, it is possible to launch "time-of-check to time-of-use" attack, where flash contents are swapped after the image is verified and running. Therefore, it is recommended to use both the features together.

Signed App Verification Without Hardware Secure Boot

The Secure Boot V2 signature of apps can be checked on OTA update, without enabling the hardware Secure Boot option. This option uses the same app signature scheme as Secure Boot V2, but unlike hardware Secure Boot it does not prevent an attacker who can write to flash from bypassing the signature protection.

This may be desirable in cases where the delay of Secure Boot verification on startup is unacceptable, and/or where the threat model does not include physical access or attackers writing to bootloader or app partitions in flash.

In this mode, the public key which is present in the signature block of the currently running app will be used to verify the signature of a newly updated app. (The signature on the running app is not verified during the update process, it is assumed to be valid.) In this way the system creates a chain of trust from the running app to the newly updated app.

For this reason, it is essential that the initial app flashed to the device is also signed. A check is run on app startup and the app will abort if no signatures are found. This is to try and prevent a situation where no update is possible. The app should have only one valid signature block in the first position. Note again that, unlike hardware Secure Boot V2, the signature of the running app is not verified on boot. The system only verifies a signature block in the first position and ignores any other appended signatures.

Note

In general, it is recommended to use full hardware Secure Boot unless certain that this option is sufficient for application security needs.

How To Enable Signed App Verification

  1. Open Project Configuration Menu -> Security features

  2. Ensure App Signing Scheme is RSA. For ESP32 ECO3 chip, select CONFIG_ESP32_REV_MIN to Rev 3 to get RSA option available

  3. Enable CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT

  4. By default, "Sign binaries during build" will be enabled on selecting "Require signed app images" option, which will sign binary files as a part of build process. The file named in "Secure boot private signing key" will be used to sign the image.

  5. If you disable "Sign binaries during build" option then all app binaries must be manually signed by following instructions in Remote Signing of Images.

Warning

It is very important that all apps flashed have been signed, either during the build or after the build.

Advanced Features

JTAG Debugging

By default, when Secure Boot is enabled then JTAG debugging is disabled via eFuse. The bootloader does this on first boot, at the same time it enables Secure Boot.

See JTAG with Flash Encryption or Secure Boot for more information about using JTAG Debugging with either Secure Boot or signed app verification enabled.