Host-Based Security Workflows

Introduction

It is recommended to have an uninterrupted power supply while enabling security features on ESP32 SoCs. Power failures during the secure manufacturing process could cause issues that are hard to debug and, in some cases, may cause permanent boot-up failures.

This guide highlights an approach where security features are enabled with the assistance of an external host machine. Security workflows are broken down into various stages and key material is generated on the host machine; thus, allowing greater recovery chances in case of power or other failures. It also offers better timings for secure manufacturing, e.g., in the case of encryption of firmware on the host machine vs. on the device.

Goals

  1. Simplify the traditional workflow with stepwise instructions.

  2. Design a more flexible workflow as compared to the traditional firmware-based workflow.

  3. Improve reliability by dividing the workflow into small operations.

  4. Eliminate dependency on Second Stage Bootloader (firmware bootloader).

Pre-requisite

  • esptool: Please make sure the esptool has been installed. It can be installed by running:

pip install esptool

Scope

Security Workflows

Enable Flash Encryption and Secure Boot V2 Externally

Important

It is recommended to enable both Flash Encryption and Secure Boot V2 for a production use case.

When enabling the Flash Encryption and Secure Boot V2 externally we need to enable them in the following order:

  1. Enable the Flash Encryption feature by following the steps listed in Enable Flash Encryption Externally.

  2. Enable the Secure Boot V2 feature by following the steps listed in Enable Secure Boot V2 Externally.

The reason for this order is as follows:

To enable the Secure Boot (SB) V2, it is necessary to keep the SB V2 key readable. To protect the key's readability, the write protection for RD_DIS (ESP_EFUSE_WR_DIS_RD_DIS) is applied. However, this action poses a challenge when attempting to enable Flash Encryption, as the Flash Encryption (FE) key needs to remain unreadable. This conflict arises because the RD_DIS is already write-protected, making it impossible to read protect the FE key.

Enable Flash Encryption Externally

In this case, all the eFuses related to flash encryption are written with help of the espefuse tool. More details about flash encryption can be found in the Flash Encryption Guide

  1. Ensure that you have an ESP32 device with default flash encryption eFuse settings as shown in Relevant eFuses.

In this case, the flash on the chip must be erased and flash encryption must not be enabled. The chip can be erased by running:

esptool.py --port PORT erase_flash
  1. Generate a flash encryption key.

A random flash encryption key can be generated by running:

espsecure.py generate_flash_encryption_key my_flash_encryption_key.bin
  1. Burn the flash encryption key into eFuse.

This action cannot be reverted. It can be done by running:

espefuse.py --port PORT burn_key flash_encryption my_flash_encryption_key.bin
  1. Burn the FLASH_CRYPT_CNT eFuse.

If you only want to enable flash encryption in Development mode and want to keep the ability to disable it in the future, Update the FLASH_CRYPT_CNT value in the below command from 127 to 0x1. (not recommended for production)

espefuse.py --port PORT --chip esp32 burn_efuse FLASH_CRYPT_CNT 127

In the case of ESP32, you also need to burn the FLASH_CRYPT_CONFIG. It can be done by running:

espefuse.py --port PORT --chip esp32 burn_efuse FLASH_CRYPT_CONFIG 0xF

Note

At this point, the flash encryption on the device has been enabled. You may test the flash encryption process as given in step 5. Please note that the security-related eFuses have not been burned at this point. It is recommended that they should be burned in production use cases as explained in step 6.

  1. Encrypt and flash the binaries.

The bootloader and the application binaries for the project must be built with Flash Encryption Release mode with default configurations.

Flash encryption Release mode can be set in the menuconfig as follows:

The binaries can be encrypted on the host machine by running:

espsecure.py encrypt_flash_data --keyfile my_flash_encryption_key.bin --address 0x1000 --output bootloader-enc.bin build/bootloader/bootloader.bin

espsecure.py encrypt_flash_data --keyfile my_flash_encryption_key.bin --address 0x8000 --output partition-table-enc.bin build/partition_table/partition-table.bin

espsecure.py encrypt_flash_data --keyfile my_flash_encryption_key.bin --address 0x10000 --output my-app-enc.bin build/my-app.bin

The above files can then be flashed to their respective offset using esptool.py. To see all of the command line options recommended for esptool.py, see the output printed when idf.py build succeeds. In the above command the offsets are used for a sample firmware, the actual offset for your firmware can be obtained by checking the partition table entry or by running idf.py partition-table. When the application contains the following partition: otadata, nvs_encryption_keys they need to be encrypted as well. Please refer to Encrypted Partitions for more details about encrypted partitions.

Note

If the flashed ciphertext file is not recognized by the ESP32 when it boots, check that the keys match and that the command line arguments match exactly, including the correct offset. It is important to provide the correct offset as the ciphertext changes when the offset changes.

If your ESP32 uses non-default FLASH_CRYPT_CONFIG value in eFuse then you will need to pass the --flash_crypt_conf argument to espsecure.py to set the matching value. This will not happen if the device configured flash encryption by itself but may happen when burning eFuses manually to enable flash encryption.

The command espsecure.py decrypt_flash_data can be used with the same options (and different input/output files), to decrypt ciphertext flash contents or a previously encrypted file.

  1. Burn flash encryption-related security eFuses as listed below:

  1. Burn security eFuses:

Important

For production use cases, it is highly recommended to burn all the eFuses listed below.

  • DISABLE_DL_ENCRYPT: Disable the UART bootloader encryption access.

  • DISABLE_DL_DECRYPT: Disable the UART bootloader decryption access.

  • DISABLE_DL_CACHE: Disable the UART bootloader flash cache access.

  • JTAG_DISABLE: Disable the JTAG

The respective eFuses can be burned by running:

espefuse.py burn_efuse --port PORT EFUSE_NAME 0x1

Note

Please update the EFUSE_NAME with the eFuse that you need to burn. Multiple eFuses can be burned at the same time by appending them to the above command (e.g., EFUSE_NAME VAL EFUSE_NAME2 VAL2). More documentation about espefuse.py can be found here

  1. Write protect security eFuses:

After burning the respective eFuses we need to write_protect the security configurations

espefuse.py --port PORT write_protect_efuse MAC

Note

The write disable bit for MAC also write disables DIS_CACHE which is required to prevent accidental burning of this bit.

  1. Disable UART ROM DL mode:

Important

  1. Delete flash encryption key on host:

    Once the flash encryption has been enabled for the device, the key must be deleted immediately. This ensures that the host cannot produce encrypted binaries for the same device going forward. This step is important to reduce the vulnerability of the flash encryption key.

Flash Encryption Guidelines

  • It is recommended to generate a unique flash encryption key for each device for production use-cases.

  • It is recommended to ensure that the RNG used by host machine to generate the flash encryption key has good entropy.

  • See Limitations of Flash Encryption for more details.

Enable Secure Boot V2 Externally

In this workflow, we shall use espsecure tool to generate signing keys and use the espefuse tool to burn the relevant eFuses. The details about the Secure Boot V2 process can be found at Secure Boot V2 Guide

  1. Generate Secure Boot V2 Signing Private Key.

The Secure Boot V2 signing key for the RSA3072 scheme can be generated by running:

espsecure.py generate_signing_key --version 2 --scheme rsa3072 secure_boot_signing_key.pem
  1. Generate Public Key Digest.

The public key digest for the private key generated in the previous step can be generated by running:

espsecure.py digest_sbv2_public_key --keyfile secure_boot_signing_key.pem --output digest.bin
  1. Burn the key digest in eFuse.

The public key digest can be burned in the eFuse by running:

espefuse.py --port PORT --chip esp32 burn_key secure_boot_v2 digest.bin
  1. Enable Secure Boot V2.

Secure Boot V2 eFuse can be enabled by running:

espefuse.py --port PORT --chip esp32 burn_efuse ABS_DONE_1

Note

At this stage the secure boot V2 has been enabled for the ESP32. The ROM bootloader shall now verify the authenticity of the Second Stage Bootloader on every bootup. You may test the secure boot process by executing Steps 5 & 6. Please note that security-related eFuses have not been burned at this point. For production use cases, all security-related eFuses must be burned as given in step 7.

  1. Build the binaries.

By default, the ROM bootloader would only verify the Second Stage Bootloader (firmware bootloader). The firmware bootloader would verify the app partition only when the CONFIG_SECURE_BOOT option is enabled (and CONFIG_SECURE_BOOT_VERSION is set to SECURE_BOOT_V2_ENABLED) while building the bootloader.

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

For ESP32, Secure Boot V2 is available only for ESP32 ECO3 onwards. To view the "Secure Boot V2" option the chip revision should be changed to revision v3.0 (ECO3). To change the chip revision, set "Minimum Supported ESP32 Revision" to "Rev 3.0 (ECO3)" in "Component Config" -> "Hardware Settings" -> "Chip Revision".

  1. Disable the option CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES for the project in the Project Configuration Menu. This shall make sure that all the generated binaries are secure padded and unsigned. This step is done to avoid generating signed binaries as we are going to manually sign the binaries using espsecure tool.

After the above configurations, the bootloader and application binaries can be built with idf.py build command.

  1. Sign and Flash the binaries.

The Secure Boot V2 workflow only verifies the bootloader and application binaries, hence only those binaries need to be signed. The other binaries (e.g., partition-table.bin) can be flashed as they are generated in the build stage.

The bootloader.bin and app.bin binaries can be signed by running:

espsecure.py sign_data --version 2 --keyfile secure_boot_signing_key.pem --output bootloader-signed.bin build/bootloader/bootloader.bin

espsecure.py sign_data --version 2 --keyfile secure_boot_signing_key.pem --output my-app-signed.bin build/my-app.bin

The signatures attached to a binary can be checked by running:

espsecure.py signature_info_v2 bootloader-signed.bin

The above files along with other binaries (e.g., partition table) can then be flashed to their respective offset using esptool.py. To see all of the command line options recommended for esptool.py, see the output printed when idf.py build succeeds. The flash offset for your firmware can be obtained by checking the partition table entry or by running idf.py partition-table.

  1. Burn relevant eFuses.

  1. Burn security eFuses:

Important

For production use cases, it is highly recommended to burn all the eFuses listed below.

  • JTAG_DISABLE: Disable the JTAG

The respective eFuses can be burned by running:

espefuse.py burn_efuse --port PORT EFUSE_NAME 0x1

Note

Please update the EFUSE_NAME with the eFuse that you need to burn. Multiple eFuses can be burned at the same time by appending them to the above command (e.g., EFUSE_NAME VAL EFUSE_NAME2 VAL2). More documentation about espefuse.py can be found here

  1. Secure Boot V2-related eFuses:

  1. Disable the ability for read protection:

The secure boot digest burned in the eFuse must be kept readable otherwise secure boot operation would result in a failure. To prevent the accidental enabling of read protection for this key block we need to burn the following eFuse:

espefuse.py -p $ESPPORT write_protect_efuse RD_DIS

Important

After this eFuse has been burned, read protection cannot be enabled for any key. E.g., if flash encryption which requires read protection for its key is not enabled at this point then it cannot be enabled afterwards. Please ensure that no eFuse keys are going to need read protection after this.

  1. Disable UART ROM DL mode:

Secure Boot V2 Guidelines

  • It is recommended to store the secure boot key in a highly secure place. A physical or a cloud HSM may be used for secure storage of the secure boot private key. Please take a look at Remote Signing of Images for more details.