arch/espressif: add flash encryption support on CMake

Add support for burning flash encryption E-Fuses on target.

Signed-off-by: Filipe Cavalcanti <filipe.cavalcanti@espressif.com>
This commit is contained in:
Filipe Cavalcanti
2026-03-27 11:20:54 -03:00
committed by archer
parent e59604bbe6
commit b5b7e8f72e
5 changed files with 354 additions and 14 deletions
@@ -65,12 +65,6 @@ if(CONFIG_ESPRESSIF_HR_TIMER)
endif()
if(CONFIG_ESPRESSIF_EFUSE)
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_ENABLED)
message(
WARNING
"Flash Encryption is not supported on CMake. Use Make to build the image instead."
)
endif()
list(APPEND SRCS esp_efuse.c)
endif()
@@ -424,6 +418,15 @@ add_custom_command(
COMMENT "Generating ESP-compatible binary"
VERBATIM)
# Flash encryption: burn key to eFuses (similar to tools/espressif/Config.mk
# BURN_EFUSES). Build: ESPTOOL_PORT=/dev/ttyUSB0 NOCHECK=1 cmake --build
# <builddir> -t burn_enc_key (NOCHECK is required by burn_flash_enc_key.py; same
# idea as make NOCHECK.)
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_ENABLED)
include(${NUTTX_DIR}/tools/espressif/espressif_burn_enc_key.cmake)
endif()
# ##############################################################################
# Flash operation Usage: ESPTOOL_PORT=/dev/ttyUSBx cmake --build <build_dir>
# --target flash
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
# tools/espressif/burn_flash_enc_key.py
#
# SPDX-License-Identifier: Apache-2.0
#
# Burn the flash encryption key into hardware eFuses (same role as
# tools/espressif/Config.mk BURN_EFUSES + espefuse.py burn_key).
#
# Invocation
# ----------
# Normally you do not run this script by hand. The CMake target ``burn_enc_key``
# (arch/risc-v/src/common/espressif/CMakeLists.txt) runs:
#
# cmake -E env NUTTX_ESPEFUSE=<path> NUTTX_KEY=<path> -- python3 "$0"
#
# The serial port is not passed by CMake: set ESPTOOL_PORT in the environment
# when you run the build, e.g.:
#
# ESPTOOL_PORT=/dev/ttyUSB0 NOCHECK=1 cmake --build <builddir> -t burn_enc_key
#
# Environment variables
# ---------------------
# Required (set by CMake via -E env):
# NUTTX_ESPEFUSE Absolute path to espefuse.py (from find_program at configure time).
# NUTTX_KEY Absolute path to the XTS_AES_128 flash encryption key (.bin).
#
# Required (your shell / build environment):
# ESPTOOL_PORT Serial device for espefuse.py -p (e.g. /dev/ttyUSB0).
#
# Required (safety gate; same idea as ``make NOCHECK`` in Config.mk):
# NOCHECK Must be set (any value) so non-interactive burn is explicit.
# If unset, the script exits with an error before calling espefuse.
#
# Behaviour
# ---------
# 1. Runs ``espefuse.py --port $ESPTOOL_PORT summary``; fails if that command fails.
# 2. If the summary shows ``?? ??`` in the BLOCK1 region, the key is treated as
# already programmed and the script exits 0 without burning again.
# 3. Otherwise runs ``burn_key BLOCK_KEY0 <key> XTS_AES_128_KEY``, always with
# ``--do-not-confirm`` when NOCHECK is set (as required above).
#
# Exit status: 0 on skip (already burned) or successful burn; non-zero on error.
from __future__ import annotations
import os
import subprocess
import sys
def _efuse_summary_lines(summary_out: str) -> str:
"""Text from espefuse summary corresponding to ``grep -A1 BLOCK1``."""
chunks: list[str] = []
lines = summary_out.splitlines()
for i, line in enumerate(lines):
if "BLOCK1" in line:
chunks.append(line)
if i + 1 < len(lines):
chunks.append(lines[i + 1])
return "\n".join(chunks)
def main() -> int:
for var in ("NUTTX_ESPEFUSE", "ESPTOOL_PORT", "NUTTX_KEY"):
if var not in os.environ or not os.environ[var]:
print(
f"Error: {var} is not set or empty.",
file=sys.stderr,
)
return 1
if "NOCHECK" not in os.environ:
print(
"Error: NOCHECK is not set; refusing to run without explicit confirmation bypass.",
file=sys.stderr,
)
print(
"Set NOCHECK=1 (or NOCHECK with any value) to proceed non-interactively.",
file=sys.stderr,
)
return 1
espefuse = os.environ["NUTTX_ESPEFUSE"]
port = os.environ["ESPTOOL_PORT"]
key_path = os.environ["NUTTX_KEY"]
proc = subprocess.run(
[espefuse, "--port", port, "summary"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
summary_out = proc.stdout or ""
if proc.returncode != 0:
print(
f"espefuse.py summary failed for {port}: {summary_out}",
file=sys.stderr,
)
return proc.returncode
efuse_summary = _efuse_summary_lines(summary_out)
if "?? ??" in efuse_summary:
print("Encryption key already burned. Skipping...")
return 0
print("Burning flash encryption key...")
burn = subprocess.run(
[
espefuse,
"--do-not-confirm",
"--port",
port,
"burn_key",
"BLOCK_KEY0",
key_path,
"XTS_AES_128_KEY",
],
)
return burn.returncode
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,95 @@
# ##############################################################################
# tools/espressif/espressif_burn_enc_key.cmake
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
# license agreements. See the NOTICE file distributed with this work for
# additional information regarding copyright ownership. The ASF licenses this
# file to you under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
# ##############################################################################
#
# Include() from the main NuttX CMake configure when
# CONFIG_ESPRESSIF_SECURE_FLASH_ENC_ENABLED. Defines the ``burn_enc_key`` target
# (no cmake -P). Expects NUTTX_DIR, CMAKE_BINARY_DIR, CMAKE_SOURCE_DIR.
#
# Serial port: ESPTOOL_PORT in the environment at build time (e.g.
# ESPTOOL_PORT=/dev/ttyUSB0). NOCHECK must also be set so the burn is explicit
# (see burn_flash_enc_key.py).
#
# ##############################################################################
find_program(ESPEFUSE espefuse espefuse.py)
if(NOT ESPEFUSE)
message(FATAL_ERROR "espefuse.py not found (required for burn_enc_key)")
endif()
find_program(PYTHON3 python3)
if(NOT PYTHON3)
message(FATAL_ERROR "python3 not found (required for burn_enc_key)")
endif()
set(BINARY_DIR "${CMAKE_BINARY_DIR}")
set(SOURCE_DIR "${CMAKE_SOURCE_DIR}")
include(${NUTTX_DIR}/tools/espressif/espressif_esptool_common.cmake)
if(NOT EXISTS "${FLASH_ENC_KEY_PATH}")
message(
FATAL_ERROR
"burn_enc_key: flash encryption key file missing. "
"Generate the encryption key using: espsecure.py generate_flash_encryption_key <key_name.bin>"
)
endif()
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_FLASH_DEVICE_ENCRYPTED)
add_custom_target(
burn_enc_key
COMMAND
${CMAKE_COMMAND} -E echo
"burn_enc_key: device already encrypted (Kconfig); skipping E-Fuse burn."
VERBATIM)
elseif(CONFIG_ESPRESSIF_EFUSE_VIRTUAL)
add_custom_target(
burn_enc_key
COMMAND
${CMAKE_COMMAND} -E echo
"burn_enc_key: virtual E-Fuses enabled (Kconfig); skipping E-Fuse burn."
VERBATIM)
else()
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_USE_HOST_KEY)
set(_nuttx_burn_key_msg
"Using host key: ${CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME}")
else()
set(_nuttx_burn_key_msg
"Using randomly generated key flow (see Kconfig / documentation).")
endif()
add_custom_target(
burn_enc_key
COMMAND
bash -c
"if [ -z \"$$ESPTOOL_PORT\" ]; then echo 'burn_enc_key: ESPTOOL_PORT is not set. Example: ESPTOOL_PORT=/dev/ttyUSB0 cmake --build . -t burn_enc_key'; exit 1; fi"
COMMAND ${CMAKE_COMMAND} -E echo "${_nuttx_burn_key_msg}"
COMMAND
${CMAKE_COMMAND} -E echo
"This operation is NOT REVERSIBLE. See flash encryption documentation."
COMMAND
${CMAKE_COMMAND} -E env "NUTTX_ESPEFUSE=${ESPEFUSE}"
"NUTTX_KEY=${FLASH_ENC_KEY_PATH}" -- ${PYTHON3}
"${NUTTX_DIR}/tools/espressif/burn_flash_enc_key.py"
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Burn flash encryption key to eFuses (espefuse.py)"
VERBATIM)
unset(_nuttx_burn_key_msg)
endif()
+33 -8
View File
@@ -25,7 +25,8 @@
# Shared Espressif + esptool layout and flash parameters for CMake scripts.
#
# Prerequisites (callers must do this first): - include(nuttx_kconfig.cmake) -
# nuttx_export_kconfig(${BINARY_DIR}/.config)
# nuttx_export_kconfig(${DOTCONFIG}) - BINARY_DIR and SOURCE_DIR set (for
# FLASH_ENC_KEY_PATH)
#
# Defines set by including this file:
# CHIP_SERIES - Chip string for esptool -c (from CONFIG_ESPRESSIF_CHIP_SERIES)
@@ -36,6 +37,8 @@
# FLASH_SIZE - Flash size (e.g. 2MB, 4MB), for merge_bin/elf2image -fs
# FLASH_MODE - Flash mode (dio, dout, qio, qout), for elf2image -fm only
# FLASH_FREQ - Flash speed (e.g. 40m), for elf2image -ff/write_flash -ff
# FLASH_ENC_KEY_PATH - Resolved path to CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME
# (empty if unset; relative paths checked vs BINARY_DIR then SOURCE_DIR)
#
# ##############################################################################
# cmake-format: on
@@ -66,13 +69,15 @@ else()
endif()
set(EFUSE_OFFSET ${EFUSE_FLASH_OFFSET})
# MCUboot application slot (Config.mk APP_OFFSET)
if(CONFIG_ESPRESSIF_ESPTOOL_TARGET_PRIMARY)
set(MCUBOOT_APP_OFFSET ${CONFIG_ESPRESSIF_OTA_PRIMARY_SLOT_OFFSET})
elseif(CONFIG_ESPRESSIF_ESPTOOL_TARGET_SECONDARY)
set(MCUBOOT_APP_OFFSET ${CONFIG_ESPRESSIF_OTA_SECONDARY_SLOT_OFFSET})
else()
message(FATAL_ERROR "Missing MCUBoot slot target: PRIMARY or SECONDARY")
# MCUboot application slot (Config.mk APP_OFFSET); default when not MCUboot boot
if(CONFIG_ESPRESSIF_BOOTLOADER_MCUBOOT)
if(CONFIG_ESPRESSIF_ESPTOOL_TARGET_PRIMARY)
set(MCUBOOT_APP_OFFSET ${CONFIG_ESPRESSIF_OTA_PRIMARY_SLOT_OFFSET})
elseif(CONFIG_ESPRESSIF_ESPTOOL_TARGET_SECONDARY)
set(MCUBOOT_APP_OFFSET ${CONFIG_ESPRESSIF_OTA_SECONDARY_SLOT_OFFSET})
else()
message(FATAL_ERROR "Missing MCUBoot slot target: PRIMARY or SECONDARY")
endif()
endif()
# Flash capacity (merge_bin --fill-flash-size, elf2image -fs)
@@ -109,3 +114,23 @@ if(DEFINED CONFIG_ESPRESSIF_FLASH_FREQ)
else()
set(FLASH_FREQ "40m")
endif()
# Host flash encryption key file (Config.mk / espressif_mkimage FLASH_ENC)
if((NOT DEFINED BINARY_DIR) OR (NOT DEFINED SOURCE_DIR))
message(
FATAL_ERROR
"espressif_esptool_common.cmake: BINARY_DIR and SOURCE_DIR must be set before include()"
)
endif()
set(FLASH_ENC_KEY_PATH "")
if(DEFINED CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME)
set(FLASH_ENC_KEY_PATH "${CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME}")
if(NOT IS_ABSOLUTE "${FLASH_ENC_KEY_PATH}")
if(EXISTS "${BINARY_DIR}/${FLASH_ENC_KEY_PATH}")
set(FLASH_ENC_KEY_PATH "${BINARY_DIR}/${FLASH_ENC_KEY_PATH}")
elseif(EXISTS "${SOURCE_DIR}/${FLASH_ENC_KEY_PATH}")
set(FLASH_ENC_KEY_PATH "${SOURCE_DIR}/${FLASH_ENC_KEY_PATH}")
endif()
endif()
endif()
+93
View File
@@ -187,6 +187,90 @@ endif()
file(APPEND "${BINARY_DIR}/nuttx.manifest" "nuttx.bin\n")
# ##############################################################################
# Flash encryption (matches tools/espressif/Config.mk FLASH_ENC + ENC_APP)
# ##############################################################################
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_ENABLED)
message(STATUS "Flash Encryption is enabled!")
find_program(ESPSECURE espsecure espsecure.py)
if(CONFIG_ESPRESSIF_EFUSE_VIRTUAL)
message(WARNING "Virtual E-Fuses are enabled! E-Fuses will not be burned.")
endif()
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_USE_HOST_KEY)
if(NOT EXISTS "${FLASH_ENC_KEY_PATH}")
message(
FATAL_ERROR
"FLASH ENCRYPTION error: Key file '${CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME}' not found.\n"
"Generate the encryption key using: espsecure.py generate_flash_encryption_key <key_name.bin>\n"
"Refer to the documentation on flash encryption before proceeding.")
endif()
endif()
if(CONFIG_ESPRESSIF_SPIFLASH)
message(STATUS "Applying encryption to user MTD partition on flash.")
if(NOT EXISTS "${FLASH_ENC_KEY_PATH}")
message(
FATAL_ERROR
"Flash encryption key is required for user MTD partition encryption. Key file: '${CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME}'\n"
"Make sure CONFIG_ESPRESSIF_SECURE_FLASH_ENC_HOST_KEY_NAME is set or disable SPI Flash."
)
endif()
if(NOT ESPSECURE)
message(
FATAL_ERROR "espsecure.py not found - cannot encrypt MTD partition")
endif()
if(NOT DEFINED CONFIG_ESPRESSIF_STORAGE_MTD_SIZE)
message(
FATAL_ERROR
"CONFIG_ESPRESSIF_STORAGE_MTD_SIZE is required for SPI Flash + flash encryption"
)
endif()
if(NOT DEFINED CONFIG_ESPRESSIF_STORAGE_MTD_OFFSET)
message(
FATAL_ERROR
"CONFIG_ESPRESSIF_STORAGE_MTD_OFFSET is required for SPI Flash + flash encryption"
)
endif()
math(EXPR MTD_SIZE_INT "${CONFIG_ESPRESSIF_STORAGE_MTD_SIZE}")
message(
STATUS
"Encrypting user MTD partition offset: ${CONFIG_ESPRESSIF_STORAGE_MTD_OFFSET}, size: ${CONFIG_ESPRESSIF_STORAGE_MTD_SIZE} (${MTD_SIZE_INT})"
)
execute_process(
COMMAND
${CMAKE_COMMAND} -E env LC_ALL=C sh -c
"dd if=/dev/zero ibs=1 count=${MTD_SIZE_INT} 2>/dev/null | tr '\\000' '\\377' > blank_mtd.bin"
RESULT_VARIABLE BLANK_MTD_RESULT
WORKING_DIRECTORY ${BINARY_DIR})
if(NOT BLANK_MTD_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to create blank_mtd.bin for MTD encryption")
endif()
execute_process(
COMMAND
${ESPSECURE} encrypt_flash_data --aes_xts --keyfile
${FLASH_ENC_KEY_PATH} --address 0 --output ${BINARY_DIR}/enc_mtd.bin
${BINARY_DIR}/blank_mtd.bin
RESULT_VARIABLE ENC_MTD_RESULT
WORKING_DIRECTORY ${BINARY_DIR})
if(NOT ENC_MTD_RESULT EQUAL 0)
file(REMOVE "${BINARY_DIR}/blank_mtd.bin")
message(FATAL_ERROR "espsecure.py encrypt_flash_data failed")
endif()
file(REMOVE "${BINARY_DIR}/blank_mtd.bin")
message(STATUS "Generated: enc_mtd.bin (encrypted user MTD placeholder)")
endif()
endif()
# ##############################################################################
# Merge binaries (optional)
# ##############################################################################
@@ -226,6 +310,15 @@ if(CONFIG_ESPRESSIF_MERGE_BINS)
message(
STATUS "Merge bin: ${MCUBOOT_APP_OFFSET} -> ${BINARY_DIR}/nuttx.bin")
if(CONFIG_ESPRESSIF_SECURE_FLASH_ENC_ENABLED AND CONFIG_ESPRESSIF_SPIFLASH)
list(APPEND ESPTOOL_BINS ${CONFIG_ESPRESSIF_STORAGE_MTD_OFFSET}
"${BINARY_DIR}/enc_mtd.bin")
message(
STATUS
"Merge bin: ${CONFIG_ESPRESSIF_STORAGE_MTD_OFFSET} -> ${BINARY_DIR}/enc_mtd.bin"
)
endif()
elseif(CONFIG_ESPRESSIF_SIMPLE_BOOT)
# Simple boot: same base offset as BL_OFFSET (0x2000 on ESP32-P4, else 0x0)
list(APPEND ESPTOOL_BINS ${BL_OFFSET} "${BINARY_DIR}/nuttx.bin")