This is an automated email from the ASF dual-hosted git repository.

archer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit b5b7e8f72e09d527cd474546199878eac4228aeb
Author: Filipe Cavalcanti <[email protected]>
AuthorDate: Fri Mar 27 11:20:54 2026 -0300

    arch/espressif: add flash encryption support on CMake
    
    Add support for burning flash encryption E-Fuses on target.
    
    Signed-off-by: Filipe Cavalcanti <[email protected]>
---
 arch/risc-v/src/common/espressif/CMakeLists.txt |  15 +--
 tools/espressif/burn_flash_enc_key.py           | 124 ++++++++++++++++++++++++
 tools/espressif/espressif_burn_enc_key.cmake    |  95 ++++++++++++++++++
 tools/espressif/espressif_esptool_common.cmake  |  41 ++++++--
 tools/espressif/espressif_mkimage.cmake         |  93 ++++++++++++++++++
 5 files changed, 354 insertions(+), 14 deletions(-)

diff --git a/arch/risc-v/src/common/espressif/CMakeLists.txt 
b/arch/risc-v/src/common/espressif/CMakeLists.txt
index aac5d5c1fbf..ed18f13a1d4 100644
--- a/arch/risc-v/src/common/espressif/CMakeLists.txt
+++ b/arch/risc-v/src/common/espressif/CMakeLists.txt
@@ -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
diff --git a/tools/espressif/burn_flash_enc_key.py 
b/tools/espressif/burn_flash_enc_key.py
new file mode 100644
index 00000000000..00340d125af
--- /dev/null
+++ b/tools/espressif/burn_flash_enc_key.py
@@ -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())
diff --git a/tools/espressif/espressif_burn_enc_key.cmake 
b/tools/espressif/espressif_burn_enc_key.cmake
new file mode 100644
index 00000000000..54864d5d479
--- /dev/null
+++ b/tools/espressif/espressif_burn_enc_key.cmake
@@ -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()
diff --git a/tools/espressif/espressif_esptool_common.cmake 
b/tools/espressif/espressif_esptool_common.cmake
index c16dcba335f..60c3d0a2086 100644
--- a/tools/espressif/espressif_esptool_common.cmake
+++ b/tools/espressif/espressif_esptool_common.cmake
@@ -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()
diff --git a/tools/espressif/espressif_mkimage.cmake 
b/tools/espressif/espressif_mkimage.cmake
index 66797c48363..9befb06aeb4 100644
--- a/tools/espressif/espressif_mkimage.cmake
+++ b/tools/espressif/espressif_mkimage.cmake
@@ -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")

Reply via email to