diff --git a/src/target/base/include/target/key_mgr.h b/src/target/base/include/target/key_mgr.h new file mode 100644 index 00000000..553db0c8 --- /dev/null +++ b/src/target/base/include/target/key_mgr.h @@ -0,0 +1,218 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * Internal target HAL interface for the Key Manager peripheral on + * ESP32-P4 (>= v3.0) and ESP32-C5 (any revision). This is a target-private + * interface, not part of the library's public API under include/esp-stub-lib/. + * + * This header is the contract between the keymanager stub plugin and the + * chip-specific HAL implementation. The plugin uses these accessors to + * drive the Key Manager + HUK Generator state machines and to move bytes + * between host RAM and the KM's internal memory blocks. + * + * The API intentionally mirrors the names used in ESP-IDF's + * `components/esp_security/src/esp_key_mgr.c` so that the plugin can be + * line-by-line compared against the reference driver. Locks, logging, + * eFuse helpers, and OS plumbing from the IDF version are dropped — the + * stub runs single-threaded with no RTOS. + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------- Constants ---------------------------------------------------- */ + +#define STUB_KM_K2_INFO_SIZE 64 +#define STUB_KM_K1_ENCRYPTED_SIZE 32 +#define STUB_KM_ECDH0_INFO_SIZE 64 /* k1*G or k2*G as LE-x || LE-y */ +#define STUB_KM_KEY_RECOVERY_INFO_SIZE 64 /* per slot, before CRC */ +#define STUB_KM_HUK_INFO_SIZE 660 /* Matches IDF HUK_INFO_LEN in rom/km.h */ +#define STUB_KM_SW_INIT_KEY_SIZE 32 +#define STUB_KM_HUK_RISK_ALERT_LEVEL 4 + +/* Magic for the on-flash key_recovery_info partition (matches IDF). */ +#define STUB_KM_KEY_HUK_SECTOR_MAGIC 0xDEA5CE5AU + +/* ---------- Enums (match IDF wire types) -------------------------------- */ + +typedef enum { + STUB_KM_STATE_IDLE = 0, + STUB_KM_STATE_LOAD = 1, + STUB_KM_STATE_GAIN = 2, + STUB_KM_STATE_BUSY = 3, +} stub_km_state_t; + +typedef enum { + STUB_KM_KEYGEN_MODE_RANDOM = 0, + STUB_KM_KEYGEN_MODE_AES = 1, + STUB_KM_KEYGEN_MODE_ECDH0 = 2, + STUB_KM_KEYGEN_MODE_ECDH1 = 3, + STUB_KM_KEYGEN_MODE_RECOVER = 4, + STUB_KM_KEYGEN_MODE_EXPORT = 5, +} stub_km_keygen_mode_t; + +typedef enum { + STUB_KM_KEY_PURPOSE_INVALID = 0, + STUB_KM_KEY_PURPOSE_ECDSA_192 = 1, + STUB_KM_KEY_PURPOSE_ECDSA_256 = 2, + STUB_KM_KEY_PURPOSE_FLASH_256_1 = 3, + STUB_KM_KEY_PURPOSE_FLASH_256_2 = 4, + STUB_KM_KEY_PURPOSE_FLASH_128 = 5, + STUB_KM_KEY_PURPOSE_HMAC = 6, + STUB_KM_KEY_PURPOSE_DS = 7, + STUB_KM_KEY_PURPOSE_PSRAM_256_1 = 8, + STUB_KM_KEY_PURPOSE_PSRAM_256_2 = 9, + STUB_KM_KEY_PURPOSE_PSRAM_128 = 10, + STUB_KM_KEY_PURPOSE_ECDSA_384_L = 11, + STUB_KM_KEY_PURPOSE_ECDSA_384_H = 12, +} stub_km_key_purpose_t; + +typedef enum { + STUB_KM_KEY_TYPE_ECDSA = 0, + STUB_KM_KEY_TYPE_FLASH_XTS_AES = 1, + STUB_KM_KEY_TYPE_HMAC = 2, + STUB_KM_KEY_TYPE_DS = 3, + STUB_KM_KEY_TYPE_PSRAM_XTS_AES = 4, +} stub_km_key_type_t; + +typedef enum { + STUB_KM_KEY_LEN_INVALID = 0, + STUB_KM_KEY_LEN_ECDSA_192, + STUB_KM_KEY_LEN_ECDSA_256, + STUB_KM_KEY_LEN_ECDSA_384, + STUB_KM_KEY_LEN_XTS_AES_128, + STUB_KM_KEY_LEN_XTS_AES_256, +} stub_km_key_len_t; + +typedef enum { + STUB_HUK_MODE_RECOVER = 0, + STUB_HUK_MODE_GENERATE = 1, +} stub_huk_mode_t; + +/* ---------- HUK Generator ----------------------------------------------- */ + +/** + * @brief Read HUK_STATUS_REG. + * + * @param[out] gen_status 0=not generated, 1=valid, 2=invalid, 3=reserved + * @param[out] risk_level 0..7 (higher = more PUF-SRAM error bits; 7 = invalid) + */ +void stub_target_huk_get_status(uint8_t *gen_status, uint8_t *risk_level); + +/** + * @brief Drive the HUK Generator state machine through one cycle. + * + * In GENERATE mode, populates @p huk_info_buf with the freshly generated + * huk_info. In RECOVER mode, @p huk_info_buf is loaded into the HUK and the + * function returns 0 once the HUK is valid. + * + * @p huk_info_buf must be STUB_KM_HUK_INFO_SIZE (660) bytes. The 660-byte + * blob is the caller-facing huk_info (IDF HUK_INFO_LEN); esp_rom_km_huk_conf + * fills/consumes it by iterating the HUK state machine over the 384-byte + * HUK_INFO_MEM MMIO window across multiple passes. (384 = hardware window, + * 660 = persisted blob — not the same thing.) + * + * @return 0 on success; negative if HUK could not be brought to a valid + * state (for RECOVER) or if HUK generation failed (for GENERATE). + */ +int stub_target_huk_configure(stub_huk_mode_t mode, uint8_t *huk_info_buf); + +/* ---------- Capability -------------------------------------------------- */ + +/** + * @brief Whether this chip/revision supports the Key Manager flow this HAL + * implements (the 660-byte HUK_INFO generation/recovery). + * + * ESP32-C5: true on all revisions. ESP32-P4: true only for >= v3.0 (ROM + * ECO >= 5); earlier P4 silicon used a 384-byte HUK and is not supported. + * Callers must check this before driving any other stub_target_km_* / + * stub_target_huk_* operation. + */ +bool stub_target_km_is_supported(void); + +/* ---------- Key Manager bring-up ---------------------------------------- */ + +/** + * @brief Enable the KM bus clock, release reset, and power up KM memory. + * + * Must be called once before any other stub_target_km_* call. Idempotent. + * Without it, KM register writes silently no-op (peripheral held in reset) + * and KEYMNG_*_MEM reads return zero (memory power gate off). + */ +void stub_target_km_bringup(void); + +/* ---------- Key Manager state machine ----------------------------------- */ + +stub_km_state_t stub_target_km_get_state(void); +void stub_target_km_wait_for_state(stub_km_state_t state); + +void stub_target_km_set_keygen_mode(stub_km_keygen_mode_t mode); +void stub_target_km_set_key_purpose(stub_km_key_purpose_t purpose); +void stub_target_km_use_sw_init_key(void); +/* @p use_256: false = XTS-AES-128, true = XTS-AES-256. Only meaningful for + * XTS-AES key types; no-op for ECDSA / HMAC / DS. */ +void stub_target_km_set_xts_aes_key_len(stub_km_key_type_t key_type, bool use_256); + +void stub_target_km_start(void); +void stub_target_km_continue(void); + +/* ---------- KM memory I/O (LOAD / GAIN phases) -------------------------- */ + +/* @p len must not exceed the destination MMIO window: 32 bytes for the + * SW_INIT_KEY block, 64 bytes for ASSIST_INFO / PUBLIC_INFO. The + * implementations clamp @p len to the window size as a safety net so an + * oversized len can never spill into adjacent KM registers. */ +void stub_target_km_write_sw_init_key(const uint8_t *buf, size_t len); +void stub_target_km_write_assist_info(const uint8_t *buf, size_t len); +void stub_target_km_write_public_info(const uint8_t *buf, size_t len); +void stub_target_km_read_assist_info(uint8_t *buf, size_t len); +void stub_target_km_read_public_info(uint8_t *buf, size_t len); + +/* ---------- Result checks ----------------------------------------------- */ + +/* Reads KEYMNG_HUK_VLD_REG. */ +bool stub_target_km_is_huk_valid(void); + +/* Reads KEYMNG_KEY_VLD_REG bit corresponding to the given (key_type, key_len). + * The KEY_VLD encoding is split by ECDSA length — pass the same key_len + * the deploy operation used. */ +bool stub_target_km_is_key_deployment_valid(stub_km_key_type_t key_type, stub_km_key_len_t key_len); + +/** + * @brief Configure KEYMNG_USE_EFUSE_KEY for a given key type. + * + * After a successful deploy/recovery, the static register must direct the + * peripheral to use the KM-deployed key (`use_own_key=true`) rather than + * an eFuse-burned key. This is the very last step in every deploy / recover + * sequence and matches `key_mgr_hal_set_key_usage(...USE_OWN_KEY)` in IDF. + */ +void stub_target_km_set_key_usage(stub_km_key_type_t key_type, bool use_own_key); + +/** + * @brief Check whether any eFuse key block has KM_INIT_KEY (purpose 12) + * burned. + * + * Used by AES / ECDH1 deploy handlers before the chip is told to use the + * eFuse init_key (USE_SW_INIT_KEY=0). If no key block has the right + * purpose, the chip would silently decrypt k2_info with an all-zero key + * and produce an invalid deployment — surfacing this as an explicit error + * up-front saves debugging the resulting KEY_VLD=0 mystery. + * + * @return true if at least one block has purpose == KM_INIT_KEY; false + * otherwise. Implementations iterate over all 6 KEY block purpose + * fields; chip-specific because the field width and packing + * differ between targets (C5 uses 5-bit purposes, P4 4-bit). + */ +bool stub_target_km_is_efuse_init_key_burned(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/target/common/CMakeLists.txt b/src/target/common/CMakeLists.txt index baa234b4..d00e0d38 100644 --- a/src/target/common/CMakeLists.txt +++ b/src/target/common/CMakeLists.txt @@ -7,6 +7,7 @@ set(common_srcs src/flash_4byte.c src/mem_utils.c src/security.c + src/key_mgr.c src/sha256.c src/sdio.c src/uart.c diff --git a/src/target/common/src/key_mgr.c b/src/target/common/src/key_mgr.c new file mode 100644 index 00000000..7d0bee9a --- /dev/null +++ b/src/target/common/src/key_mgr.c @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +#include +#include +#include + +#include + +#include + +/* + * Weak defaults for the Key Manager HAL. Chips that ship a Key Manager + * (ESP32-C5, ESP32-P4 >= v3.0) provide strong overrides under + * src/target//src/key_mgr.c; chips without KM keep these no-op / + * safe-default versions so the library always links. + */ + +void __attribute__((weak)) stub_target_huk_get_status(uint8_t *gen_status, uint8_t *risk_level) +{ + if (gen_status != NULL) { + *gen_status = 0U; + } + if (risk_level != NULL) { + *risk_level = STUB_KM_HUK_RISK_ALERT_LEVEL; + } +} + +int __attribute__((weak)) stub_target_huk_configure(stub_huk_mode_t mode, uint8_t *huk_info_buf) +{ + (void)mode; + (void)huk_info_buf; + return STUB_LIB_FAIL; +} + +bool __attribute__((weak)) stub_target_km_is_supported(void) +{ + return false; /* no Key Manager on this chip/revision by default */ +} + +void __attribute__((weak)) stub_target_km_bringup(void) +{ +} + +stub_km_state_t __attribute__((weak)) stub_target_km_get_state(void) +{ + return STUB_KM_STATE_IDLE; +} + +void __attribute__((weak)) stub_target_km_wait_for_state(stub_km_state_t state) +{ + (void)state; +} + +void __attribute__((weak)) stub_target_km_set_keygen_mode(stub_km_keygen_mode_t mode) +{ + (void)mode; +} + +void __attribute__((weak)) stub_target_km_set_key_purpose(stub_km_key_purpose_t purpose) +{ + (void)purpose; +} + +void __attribute__((weak)) stub_target_km_use_sw_init_key(void) +{ +} + +void __attribute__((weak)) stub_target_km_set_xts_aes_key_len(stub_km_key_type_t key_type, bool use_256) +{ + (void)key_type; + (void)use_256; +} + +void __attribute__((weak)) stub_target_km_start(void) +{ +} + +void __attribute__((weak)) stub_target_km_continue(void) +{ +} + +void __attribute__((weak)) stub_target_km_write_sw_init_key(const uint8_t *buf, size_t len) +{ + (void)buf; + (void)len; +} + +void __attribute__((weak)) stub_target_km_write_assist_info(const uint8_t *buf, size_t len) +{ + (void)buf; + (void)len; +} + +void __attribute__((weak)) stub_target_km_write_public_info(const uint8_t *buf, size_t len) +{ + (void)buf; + (void)len; +} + +void __attribute__((weak)) stub_target_km_read_assist_info(uint8_t *buf, size_t len) +{ + (void)buf; + (void)len; +} + +void __attribute__((weak)) stub_target_km_read_public_info(uint8_t *buf, size_t len) +{ + (void)buf; + (void)len; +} + +bool __attribute__((weak)) stub_target_km_is_huk_valid(void) +{ + return false; +} + +bool __attribute__((weak)) stub_target_km_is_key_deployment_valid(stub_km_key_type_t key_type, + stub_km_key_len_t key_len) +{ + (void)key_type; + (void)key_len; + return false; +} + +void __attribute__((weak)) stub_target_km_set_key_usage(stub_km_key_type_t key_type, bool use_own_key) +{ + (void)key_type; + (void)use_own_key; +} + +bool __attribute__((weak)) stub_target_km_is_efuse_init_key_burned(void) +{ + return false; +} diff --git a/src/target/esp32c5/CMakeLists.txt b/src/target/esp32c5/CMakeLists.txt index 050fca70..4c2524fe 100644 --- a/src/target/esp32c5/CMakeLists.txt +++ b/src/target/esp32c5/CMakeLists.txt @@ -7,6 +7,7 @@ set(srcs src/usb_serial_jtag.c src/clock.c src/sdio.c + src/key_mgr.c ) add_library(${ESP_TARGET_LIB} STATIC ${srcs}) diff --git a/src/target/esp32c5/include/soc/efuse_reg.h b/src/target/esp32c5/include/soc/efuse_reg.h new file mode 100644 index 00000000..a0a826c5 --- /dev/null +++ b/src/target/esp32c5/include/soc/efuse_reg.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + */ + +#pragma once + +#include "reg_base.h" + +#define EFUSE_RD_REPEAT_DATA1_REG (DR_REG_EFUSE_BASE + 0x34) +#define EFUSE_RD_REPEAT_DATA2_REG (DR_REG_EFUSE_BASE + 0x38) + +/* + * Per-block KEY_PURPOSE field positions (5-bit purpose values, one block per + * eFuse KEY[0..5]). Used by the Key Manager HAL to scan for KM_INIT_KEY. + */ +#define EFUSE_C5_KEY_PURPOSE_M 0x1FU +#define EFUSE_C5_KEY0_PURPOSE_IN_DATA1_S 22 +#define EFUSE_C5_KEY1_PURPOSE_IN_DATA1_S 27 +#define EFUSE_C5_KEY2_PURPOSE_IN_DATA2_S 0 +#define EFUSE_C5_KEY3_PURPOSE_IN_DATA2_S 5 +#define EFUSE_C5_KEY4_PURPOSE_IN_DATA2_S 10 +#define EFUSE_C5_KEY5_PURPOSE_IN_DATA2_S 15 diff --git a/src/target/esp32c5/include/soc/huk_reg.h b/src/target/esp32c5/include/soc/huk_reg.h new file mode 100644 index 00000000..5d0f9bf3 --- /dev/null +++ b/src/target/esp32c5/include/soc/huk_reg.h @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-C5 HUK Generator peripheral registers. + * + * Layout matches "ESP32-C5 Technical Reference Manual" Chapter 31 §31.11.1. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "reg_base.h" + +/** LP_AON_MEM_CTRL_REG: LP-AON memory power control. The HUK memory + * defaults to powered-down — both bits must be set/cleared correctly + * before any HUK_INFO_MEM access or it reads back zero. */ +#define LP_AON_MEM_CTRL_REG (DR_REG_LP_AON_BASE + 0x7C) +#define LP_AON_HUK_MEM_FORCE_PD (1U << 2) /* default 1 — powered down */ +#define LP_AON_HUK_MEM_FORCE_PU (1U << 3) /* default 0 — not forced up */ + +/** PUF SRAM power-cycle bits — used to recharge the PUF SRAM on cold boot + * before HUK recovery can succeed. C5 has SOC_HUK_MEM_NEEDS_RECHARGE=1. */ +#define LP_AON_PUF_MEM_SW_REG (DR_REG_LP_AON_BASE + 0x80) +#define LP_AON_PUF_MEM_SW (1U << 0) /* default 1 */ +#define LP_AON_PUF_MEM_DISCHARGE_REG (DR_REG_LP_AON_BASE + 0x88) +#define LP_AON_PUF_MEM_DISCHARGE (1U << 0) /* default 0 */ + +/** HUK_CLK_REG: register and memory clock gates. + * HUK_CLK_EN (bit 0) defaults on — register-side clock. + * HUK_MEM_CG_FORCE_ON (bit 1) defaults OFF — without it HUK_INFO_MEM + * reads return zero even after a successful GENERATE phase. */ +#define HUK_CLK_REG (DR_REG_HUK_BASE + 0x0004) +#define HUK_CLK_EN_M (1U << 0) +#define HUK_MEM_CG_FORCE_ON_M (1U << 1) + +/** HUK_CONF_REG: HUK_MODE (bit 0). 0 = Recovery, 1 = Generation. */ +#define HUK_CONF_REG (DR_REG_HUK_BASE + 0x0020) +#define HUK_MODE_M (1U << 0) + +/** HUK_START_REG: write 1 to advance the state machine. */ +#define HUK_START_REG (DR_REG_HUK_BASE + 0x0024) +#define HUK_START (1U << 0) +#define HUK_CONTINUE (1U << 1) + +/** HUK_STATE_REG: 0=IDLE, 1=LOAD, 2=GAIN, 3=BUSY. */ +#define HUK_STATE_REG (DR_REG_HUK_BASE + 0x0028) +#define HUK_STATE_M 0x3U + +/** HUK_STATUS_REG: HUK_STATUS[1:0] + HUK_RISK_LEVEL[4:2]. */ +#define HUK_STATUS_REG (DR_REG_HUK_BASE + 0x0034) +#define HUK_STATUS_S 0U +#define HUK_STATUS_M 0x3U +#define HUK_RISK_LEVEL_S 2U +#define HUK_RISK_LEVEL_M 0x7U + +/** HUK_INFO_MEM: 384-byte MMIO window — NOT the full huk_info. The ROM + * routine esp_rom_km_huk_conf iterates this window across multiple passes to + * read/write the full STUB_KM_HUK_INFO_SIZE (660) byte huk_info blob during + * generation (read out) and recovery (write in). */ +#define HUK_INFO_MEM (DR_REG_HUK_BASE + 0x0100) +#define HUK_INFO_MEM_SIZE 384 + +#ifdef __cplusplus +} +#endif diff --git a/src/target/esp32c5/include/soc/keymng_reg.h b/src/target/esp32c5/include/soc/keymng_reg.h new file mode 100644 index 00000000..aeb173b7 --- /dev/null +++ b/src/target/esp32c5/include/soc/keymng_reg.h @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-C5 Key Manager peripheral registers. + * + * Layout matches "ESP32-C5 Technical Reference Manual" Chapter 31 §31.11.2. + * Field semantics follow §31.6.2 + §31.7.2; see comments inline. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "reg_base.h" + +/* ---------- PCR registers used to bring the KM peripheral up ------------ */ + +/** PCR_SEC_CONF_REG: master crypto-subsystem clock select. C5 ROM/IDF + * default is XTAL on cold boot; IDF's esp_crypto_clk_init() switches + * it to 480 MHz SPLL (PCR_SEC_CLK_SEL=2) before any KM/HUK operation. + * Without that, the HUK Generator's PUF readout is unstable. */ +#define PCR_SEC_CONF_REG (DR_REG_PCR_BASE + 0x13C) +#define PCR_SEC_CLK_SEL_S 0U +#define PCR_SEC_CLK_SEL_M 0x3U +#define PCR_SEC_CLK_SEL_SPLL 0x2U /* 480 MHz */ + +/** PCR_KM_CONF_REG: KM clock + reset + ready bits. + * After the reset pulse (set RST=1, then 0) the caller MUST poll for + * PCR_KM_READY before issuing any KM register access — otherwise the + * KM peripheral can latch register writes silently. The ROM's + * pcr_ll_km_en() does this; we now match it. */ +#define PCR_KM_CONF_REG (DR_REG_PCR_BASE + 0x164) +#define PCR_KM_CLK_EN_M (1U << 0) +#define PCR_KM_RST_EN_M (1U << 1) +#define PCR_KM_READY_M (1U << 2) + +/** PCR_KM_PD_CTRL_REG: KM memory power-down/up bits. + * Defaults to powered-down — must clear PD and set PU before any KM + * memory access (KEYMNG_*_MEM region) or reads return zero. */ +#define PCR_KM_PD_CTRL_REG (DR_REG_PCR_BASE + 0x168) +#define PCR_KM_MEM_FORCE_PU_M (1U << 1) +#define PCR_KM_MEM_FORCE_PD_M (1U << 2) + +/** PCR_ECC_CONF_REG: ECC peripheral clock + reset. The KM uses the + * external ECC block to compute k1*G and k2*G during ECDH0 / ECDH1 + * deploys — if the ECC clock isn't enabled the chip silently returns + * zero for k2*G even though KEYMNG_KEY_VLD ends up set. */ +#define PCR_ECC_CONF_REG (DR_REG_PCR_BASE + 0xDC) +#define PCR_ECC_CLK_EN_M (1U << 0) +#define PCR_ECC_RST_EN_M (1U << 1) + +/** PCR_ECC_PD_CTRL_REG: ECC memory power-down/up bits. Defaults are + * powered-down; clear both bits so the ECC peripheral's parameter memory + * is reachable when the KM drives it for ECDH0 / ECDH1. */ +#define PCR_ECC_PD_CTRL_REG (DR_REG_PCR_BASE + 0xE0) +#define PCR_ECC_MEM_PD_M (1U << 0) +#define PCR_ECC_MEM_FORCE_PD_M (1U << 2) + +/** PCR_ECDSA_CONF_REG: ECDSA peripheral reset. The ECDSA block sits in + * series with the ECC multiplier; when its RST_EN bit is asserted (the + * ROM default) the ECC stays held in reset and KM-driven ECDH0 silently + * returns zero for k2*G. Clearing this bit lets ECC come out of reset. */ +#define PCR_ECDSA_CONF_REG (DR_REG_PCR_BASE + 0xEC) +#define PCR_ECDSA_RST_EN_M (1U << 1) + +/* ---------- Configuration / control ------------------------------------- */ + +/** KEYMNG_STATIC_REG: KEY_LEN, USE_SW_INIT_KEY, USE_EFUSE_KEY etc. */ +#define KEYMNG_STATIC_REG (DR_REG_KEYMNG_BASE + 0x0018) +#define KEYMNG_USE_EFUSE_KEY_S 0U /* bits[4:0]: 1 bit per key type */ +#define KEYMNG_USE_EFUSE_KEY_M 0x1FU +#define KEYMNG_RND_SWITCH_CYCLE_S 5U /* bits[9:5] */ +#define KEYMNG_USE_SW_INIT_KEY_S 10U /* bit10: 1=use sw_init_key, 0=use efuse */ +#define KEYMNG_FLASH_KEY_LEN_S 11U /* bit11: 0=XTS-AES-128, 1=XTS-AES-256 */ +#define KEYMNG_PSRAM_KEY_LEN_S 12U + +/** KEYMNG_LOCK_REG: write-1 lock for fields in STATIC_REG (one-shot). */ +#define KEYMNG_LOCK_REG (DR_REG_KEYMNG_BASE + 0x001C) + +/** KEYMNG_CONF_REG: KGEN_MODE + KEY_PURPOSE. Configured in IDLE phase. */ +#define KEYMNG_CONF_REG (DR_REG_KEYMNG_BASE + 0x0020) +#define KEYMNG_KGEN_MODE_S 0U /* bits[2:0]: deploy mode */ +#define KEYMNG_KGEN_MODE_M 0x7U +#define KEYMNG_KEY_PURPOSE_S 3U /* bits[6:3]: target key purpose 1..12 */ +#define KEYMNG_KEY_PURPOSE_M 0xFU + +/** KEYMNG_START_REG: write 1 to advance state machine. */ +#define KEYMNG_START_REG (DR_REG_KEYMNG_BASE + 0x0024) +#define KEYMNG_START (1U << 0) +#define KEYMNG_CONTINUE (1U << 1) + +/** KEYMNG_STATE_REG: current FSM phase. */ +#define KEYMNG_STATE_REG (DR_REG_KEYMNG_BASE + 0x0028) +#define KEYMNG_STATE_M 0x3U +/* Values: 0=IDLE, 1=LOAD, 2=GAIN, 3=BUSY */ + +/** KEYMNG_RESULT_REG: per-bit per-key-type "deploy operation succeeded". */ +#define KEYMNG_RESULT_REG (DR_REG_KEYMNG_BASE + 0x002C) + +/** KEYMNG_KEY_VLD_REG: per-bit "this (key_type, key_len) has been deployed + * and is ready to use". Note these bits are split by ECDSA *length* — not + * the same encoding as KEYMNG_USE_EFUSE_KEY (which is by type only). */ +#define KEYMNG_KEY_VLD_REG (DR_REG_KEYMNG_BASE + 0x0030) +#define KEYMNG_KEY_VLD_ECDSA_192_M (1U << 0) +#define KEYMNG_KEY_VLD_ECDSA_256_M (1U << 1) +#define KEYMNG_KEY_VLD_FLASH_M (1U << 2) +#define KEYMNG_KEY_VLD_HMAC_M (1U << 3) +#define KEYMNG_KEY_VLD_DS_M (1U << 4) +#define KEYMNG_KEY_VLD_PSRAM_M (1U << 5) +#define KEYMNG_KEY_VLD_ECDSA_384_M (1U << 6) + +/** KEYMNG_HUK_VLD_REG: 1 if a HUK is currently loaded into the KM. */ +#define KEYMNG_HUK_VLD_REG (DR_REG_KEYMNG_BASE + 0x0034) +#define KEYMNG_HUK_VLD_M (1U << 0) + +/* ---------- Memory blocks ----------------------------------------------- */ + +/** Assist info: write k2_info / read k2*G (ECDH0) or write key_info (recovery). */ +#define KEYMNG_ASSIST_INFO_MEM (DR_REG_KEYMNG_BASE + 0x0100) /* 64 bytes */ +#define KEYMNG_ASSIST_INFO_MEM_SIZE 64 + +/** Public info: write k1_encrypted (AES) or k1*G (ECDH0/1) or read key_info. */ +#define KEYMNG_PUBLIC_INFO_MEM (DR_REG_KEYMNG_BASE + 0x0140) /* 64 bytes */ +#define KEYMNG_PUBLIC_INFO_MEM_SIZE 64 + +/** sw_init_key (32 bytes), used when KEYMNG_USE_SW_INIT_KEY=1. */ +#define KEYMNG_SW_INIT_KEY_MEM (DR_REG_KEYMNG_BASE + 0x0180) +#define KEYMNG_SW_INIT_KEY_MEM_SIZE 32 + +#ifdef __cplusplus +} +#endif diff --git a/src/target/esp32c5/include/soc/soc_caps.h b/src/target/esp32c5/include/soc/soc_caps.h index 2d7ed52d..095259e5 100644 --- a/src/target/esp32c5/include/soc/soc_caps.h +++ b/src/target/esp32c5/include/soc/soc_caps.h @@ -9,6 +9,7 @@ /*-------------------------- COMMON CAPS ---------------------------------------*/ #define SOC_UART_HP_NUM (2) /*!< HP UART number */ #define SOC_UART_HAS_SYNC_REG_UPDATE 1 +#define SOC_KEY_MANAGER_SUPPORTED 1 // Memory Caps #define SOC_RTC_FAST_MEM_SUPPORTED 1 diff --git a/src/target/esp32c5/src/key_mgr.c b/src/target/esp32c5/src/key_mgr.c new file mode 100644 index 00000000..4d044d84 --- /dev/null +++ b/src/target/esp32c5/src/key_mgr.c @@ -0,0 +1,487 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-C5 Key Manager + HUK Generator HAL. + * + * Single-file port of the operations the stub keymanager plugin needs out + * of ESP-IDF's `components/esp_security/src/esp_key_mgr.c`. The HAL/LL + * split from IDF is collapsed (no portability layer needed in the stub). + * OS locks, logging, eFuse helpers, and chip-clock plumbing from the IDF + * version are dropped — the stub runs single-threaded and arrives with + * the necessary clocks already enabled by the ROM. + * + * Reference: ESP32-C5 Technical Reference Manual chapter 31. + */ + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +extern void *memcpy(void *dest, const void *src, size_t n); + +/* ---------- Small utilities --------------------------------------------- */ + +/* Write @p len bytes to a 32-bit-aligned memory-mapped peripheral region. + * Both ASSIST_INFO_MEM and PUBLIC_INFO_MEM only accept 4-byte stores; @p len + * is rounded up to a 4-byte boundary internally. */ +static void km_write_mem(uint32_t reg, const uint8_t *src, size_t len) +{ + /* Pad src to a 4-byte multiple so we never read past the caller's buffer + * and can use plain word stores. The KM ignores extra zero bytes per + * the IDF reference impl, but we must not read OOB on src. */ + uint32_t i = 0; + while (i + 4 <= len) { + uint32_t word = ((uint32_t)src[i]) | ((uint32_t)src[i + 1] << 8) | ((uint32_t)src[i + 2] << 16) | + ((uint32_t)src[i + 3] << 24); + REG_WRITE(reg + i, word); + i += 4; + } + if (i < len) { + /* Tail: assemble a partial word with zero-padded high bytes. */ + uint32_t word = 0; + for (size_t j = 0; j + i < len; j++) { + word |= (uint32_t)src[i + j] << (8U * j); + } + REG_WRITE(reg + i, word); + } +} + +static void km_read_mem(uint32_t reg, uint8_t *dst, size_t len) +{ + uint32_t i = 0; + while (i + 4 <= len) { + uint32_t word = REG_READ(reg + i); + dst[i + 0] = (uint8_t)(word & 0xFF); + dst[i + 1] = (uint8_t)((word >> 8) & 0xFF); + dst[i + 2] = (uint8_t)((word >> 16) & 0xFF); + dst[i + 3] = (uint8_t)((word >> 24) & 0xFF); + i += 4; + } + if (i < len) { + uint32_t word = REG_READ(reg + i); + for (size_t j = 0; j + i < len; j++) { + dst[i + j] = (uint8_t)((word >> (8U * j)) & 0xFFU); + } + } +} + +/* ---------- HUK Generator ----------------------------------------------- */ + +void stub_target_huk_get_status(uint8_t *gen_status, uint8_t *risk_level) +{ + uint32_t reg = REG_READ(HUK_STATUS_REG); + if (gen_status) { + *gen_status = (uint8_t)((reg >> HUK_STATUS_S) & HUK_STATUS_M); + } + if (risk_level) { + *risk_level = (uint8_t)((reg >> HUK_RISK_LEVEL_S) & HUK_RISK_LEVEL_M); + } +} + +/* HUK_MODE values matching the ROM signature (huk_mode_t in rom/km.h). */ +#define HUK_ROM_MODE_RECOVER 0 +#define HUK_ROM_MODE_GEN 1 + +/* esp_rom_km_huk_conf — full HUK state-machine driver, including power-up + * sequencing, state polling, and (in GENERATE mode) the read-out into + * huk_info_buf. Returns ETS_OK (=0) on success. + * + * Defined in C5 ROM at 0x40000894 — provided by esp32c5.rom.ld. We only + * need the prototype here; we don't pull in the IDF rom/km.h header + * because it transitively requires soc/soc.h and ets_sys.h. */ +extern int esp_rom_km_huk_conf(int mode, uint8_t *huk_info); + +/* ROM delay helper, provided by esp32c5.rom.api.ld as esp_rom_delay_us. */ +extern void esp_rom_delay_us(uint32_t us); + +/* Power-cycle the PUF SRAM. C5 has SOC_HUK_MEM_NEEDS_RECHARGE=1: on cold + * boot the PUF SRAM holds stale state, and a fresh huk_configure() will + * report HUK valid in HUK_STATUS_REG but the KM will not pick it up + * (KEYMNG_HUK_VLD stays 0). The recharge pulse re-initialises the SRAM + * cells so a follow-up configure succeeds. */ +static void huk_recharge_puf_memory(void) +{ + REG_CLR_BIT(LP_AON_MEM_CTRL_REG, LP_AON_HUK_MEM_FORCE_PD); + REG_CLR_BIT(LP_AON_PUF_MEM_SW_REG, LP_AON_PUF_MEM_SW); + REG_SET_BIT(LP_AON_PUF_MEM_DISCHARGE_REG, LP_AON_PUF_MEM_DISCHARGE); + esp_rom_delay_us(100000U); + REG_CLR_BIT(LP_AON_PUF_MEM_DISCHARGE_REG, LP_AON_PUF_MEM_DISCHARGE); + REG_SET_BIT(LP_AON_PUF_MEM_SW_REG, LP_AON_PUF_MEM_SW); + esp_rom_delay_us(100000U); +} + +int stub_target_huk_configure(stub_huk_mode_t mode, uint8_t *huk_info_buf) +{ + if (huk_info_buf == NULL) { + STUB_LOGE("huk_info_buf is NULL\n"); + return STUB_LIB_ERR_INVALID_ARG; + } + /* Clear FORCE_PD and assert FORCE_PU. Clearing PD alone is enough for + * the HUK Generator's own readout, but the KM peripheral's view of HUK + * (KEYMNG_HUK_VLD latched as a side-effect of the next key-deploy) needs + * the PUF SRAM forced on. Matches A1's huk_power_up sequence which was + * verified end-to-end on this C5 v1.2 board. */ + { + uint32_t v = REG_READ(LP_AON_MEM_CTRL_REG); + v &= ~LP_AON_HUK_MEM_FORCE_PD; + v |= LP_AON_HUK_MEM_FORCE_PU; + REG_WRITE(LP_AON_MEM_CTRL_REG, v); + } + + int rom_mode = (mode == STUB_HUK_MODE_GENERATE) ? HUK_ROM_MODE_GEN : HUK_ROM_MODE_RECOVER; + + /* IDF reference path (configure_huk in esp_security/src/esp_key_mgr.c): + * configure once; if the KM doesn't see HUK valid, recharge the PUF + * SRAM and configure again. esp_rom_km_huk_conf always returns 0 + * per the ROM source (rom/key_mgr/key_mgr.c) — its return value + * carries no information, so we ignore it. + * + * For same-session deploy → recover paths the success signal we use is + * HUK_STATUS_REG.gen_status==1: that's what the HUK Generator reports + * after GENERATE, and what same-session RECOVER preserves. IDF's + * configure_huk asserts KEYMNG_HUK_VLD instead, but on this C5 v1.2 + * board the KM doesn't latch HUK from a standalone huk_configure — + * it only sets KEYMNG_HUK_VLD as a side-effect of a subsequent + * key-deploy. Cross-boot RECOVER on a cold chip currently fails + * regardless of which signal we check (HUK_STATUS goes to gen=2/risk=7 + * even after recharge + retry); see the commit message for the test + * matrix. */ + (void)esp_rom_km_huk_conf(rom_mode, huk_info_buf); + + uint8_t gen = 0; + stub_target_huk_get_status(&gen, NULL); + if (gen != 1U) { + huk_recharge_puf_memory(); + (void)esp_rom_km_huk_conf(rom_mode, huk_info_buf); + stub_target_huk_get_status(&gen, NULL); + } + if (gen != 1U) { + STUB_LOGE("HUK Generator did not report gen_status=1 (got %u)\n", (unsigned)gen); + return STUB_LIB_FAIL; + } + return STUB_LIB_OK; +} + +/* ---------- Capability -------------------------------------------------- */ + +bool stub_target_km_is_supported(void) +{ + return true; /* ESP32-C5: Key Manager (660-byte HUK) on all revisions */ +} + +/* ---------- Key Manager bring-up ---------------------------------------- */ + +void stub_target_km_bringup(void) +{ + /* Step 0: master crypto clock select. IDF's esp_crypto_clk_init runs + * this at startup (esp_security/src/init.c, priority 103) before any + * KM/HUK operation. The default after reset is XTAL — operations + * sometimes succeed on XTAL but the HUK Generator's PUF readout is + * unstable, manifesting as cross-boot RECOVER returning gen=2/risk=7. */ + { + uint32_t v = REG_READ(PCR_SEC_CONF_REG); + v &= ~(PCR_SEC_CLK_SEL_M << PCR_SEC_CLK_SEL_S); + v |= (PCR_SEC_CLK_SEL_SPLL & PCR_SEC_CLK_SEL_M) << PCR_SEC_CLK_SEL_S; + REG_WRITE(PCR_SEC_CONF_REG, v); + } + + /* Mirror C5 ROM `pcr_ll_km_en()` exactly. Order matters: the ROM + * source (rom/key_mgr/key_mgr.c → pcr_ll_km_en) does: + * 1. Clear KM memory FORCE_PD + * 2. Clear LP_AON HUK memory FORCE_PD (yes, KM bring-up touches HUK power) + * 3. KM bus clock on + * 4. Reset pulse (RST=1, then 0) + * 5. Poll PCR_KM_READY */ + REG_CLR_BIT(PCR_KM_PD_CTRL_REG, PCR_KM_MEM_FORCE_PD_M); + REG_CLR_BIT(LP_AON_MEM_CTRL_REG, LP_AON_HUK_MEM_FORCE_PD); + + REG_SET_BIT(PCR_KM_CONF_REG, PCR_KM_CLK_EN_M); + REG_SET_BIT(PCR_KM_CONF_REG, PCR_KM_RST_EN_M); + REG_CLR_BIT(PCR_KM_CONF_REG, PCR_KM_RST_EN_M); + + while ((REG_READ(PCR_KM_CONF_REG) & PCR_KM_READY_M) == 0U) { + ; + } + + /* Wait for the KM state machine to fall through into IDLE before any + * subsequent register access — matches IDF's esp_key_mgr_init. + * Reading state before this point can return non-IDLE depending on + * what the previous boot left in the FSM. */ + while ((REG_READ(KEYMNG_STATE_REG) & KEYMNG_STATE_M) != 0U) { + ; + } + + /* The crucial cold-boot step IDF's esp_key_mgr_init() does for + * non-flash-encrypted boots: set USE_EFUSE_KEY for the XTS-AES Flash + * key type. Without this the KM is in a default state where it + * expects a deployed key for flash decryption, which is never going + * to materialise on a chip without flash encryption — and that + * unresolved expectation interferes with subsequent HUK operations, + * showing up empirically as cross-boot HUK recovery returning + * gen=2/risk=7. USE_EFUSE_KEY is keyed by key type, so derive the bit + * from the enum rather than a literal to track any enum reordering. */ + REG_SET_BIT(KEYMNG_STATIC_REG, BIT(STUB_KM_KEY_TYPE_FLASH_XTS_AES)); + + REG_SET_BIT(PCR_KM_PD_CTRL_REG, PCR_KM_MEM_FORCE_PU_M); + + /* Bring up the external ECC peripheral. The KM internally drives this + * block for ECDH0 / ECDH1 scalar multiplications (k2*G generation). + * Two register-bit fixes are needed; without either the chip silently + * returns zero for k2*G even though KEYMNG_KEY_VLD ends up set: + * 1. PCR_ECC_CONF.CLK_EN — bus clock on, plus a reset pulse. + * 2. PCR_ECDSA_CONF.RST_EN — clear it. The ECDSA block sits in series + * with ECC and stays in reset by default; while it's held the ECC + * multiplier doesn't run. Mirrors A1's ecc_reset() sequence. */ + /* ECC memory must be powered before any ECC register access. */ + REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_PD_M); + REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD_M); + REG_SET_BIT(PCR_ECC_CONF_REG, PCR_ECC_CLK_EN_M); + REG_SET_BIT(PCR_ECC_CONF_REG, PCR_ECC_RST_EN_M); + REG_CLR_BIT(PCR_ECC_CONF_REG, PCR_ECC_RST_EN_M); + REG_CLR_BIT(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN_M); +} + +/* ---------- Key Manager state machine ----------------------------------- */ + +stub_km_state_t stub_target_km_get_state(void) +{ + return (stub_km_state_t)(REG_READ(KEYMNG_STATE_REG) & KEYMNG_STATE_M); +} + +void stub_target_km_wait_for_state(stub_km_state_t state) +{ + while (stub_target_km_get_state() != state) { + ; + } +} + +void stub_target_km_set_keygen_mode(stub_km_keygen_mode_t mode) +{ + uint32_t reg = REG_READ(KEYMNG_CONF_REG); + reg &= ~(KEYMNG_KGEN_MODE_M << KEYMNG_KGEN_MODE_S); + reg |= ((uint32_t)mode & KEYMNG_KGEN_MODE_M) << KEYMNG_KGEN_MODE_S; + REG_WRITE(KEYMNG_CONF_REG, reg); +} + +void stub_target_km_set_key_purpose(stub_km_key_purpose_t purpose) +{ + uint32_t reg = REG_READ(KEYMNG_CONF_REG); + reg &= ~(KEYMNG_KEY_PURPOSE_M << KEYMNG_KEY_PURPOSE_S); + reg |= ((uint32_t)purpose & KEYMNG_KEY_PURPOSE_M) << KEYMNG_KEY_PURPOSE_S; + REG_WRITE(KEYMNG_CONF_REG, reg); +} + +void stub_target_km_use_sw_init_key(void) +{ + REG_SET_BIT(KEYMNG_STATIC_REG, BIT(KEYMNG_USE_SW_INIT_KEY_S)); +} + +void stub_target_km_set_xts_aes_key_len(stub_km_key_type_t key_type, bool use_256) +{ + uint32_t shift; + if (key_type == STUB_KM_KEY_TYPE_FLASH_XTS_AES) { + shift = KEYMNG_FLASH_KEY_LEN_S; + } else if (key_type == STUB_KM_KEY_TYPE_PSRAM_XTS_AES) { + shift = KEYMNG_PSRAM_KEY_LEN_S; + } else { + return; /* not an XTS-AES key type — KM ignores the len bit */ + } + if (use_256) { + REG_SET_BIT(KEYMNG_STATIC_REG, BIT(shift)); + } else { + REG_CLR_BIT(KEYMNG_STATIC_REG, BIT(shift)); + } +} + +void stub_target_km_start(void) +{ + REG_WRITE(KEYMNG_START_REG, KEYMNG_START); +} + +void stub_target_km_continue(void) +{ + REG_WRITE(KEYMNG_START_REG, KEYMNG_CONTINUE); +} + +/* ---------- KM memory I/O ----------------------------------------------- */ + +void stub_target_km_write_sw_init_key(const uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("write_sw_init_key: NULL buf\n"); + return; + } + if (len > KEYMNG_SW_INIT_KEY_MEM_SIZE) { + len = KEYMNG_SW_INIT_KEY_MEM_SIZE; /* never spill past the MMIO window */ + } + km_write_mem(KEYMNG_SW_INIT_KEY_MEM, buf, len); +} + +void stub_target_km_write_assist_info(const uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("write_assist_info: NULL buf\n"); + return; + } + if (len > KEYMNG_ASSIST_INFO_MEM_SIZE) { + len = KEYMNG_ASSIST_INFO_MEM_SIZE; + } + km_write_mem(KEYMNG_ASSIST_INFO_MEM, buf, len); +} + +void stub_target_km_write_public_info(const uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("write_public_info: NULL buf\n"); + return; + } + if (len > KEYMNG_PUBLIC_INFO_MEM_SIZE) { + len = KEYMNG_PUBLIC_INFO_MEM_SIZE; + } + km_write_mem(KEYMNG_PUBLIC_INFO_MEM, buf, len); +} + +void stub_target_km_read_assist_info(uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("read_assist_info: NULL buf\n"); + return; + } + if (len > KEYMNG_ASSIST_INFO_MEM_SIZE) { + len = KEYMNG_ASSIST_INFO_MEM_SIZE; + } + km_read_mem(KEYMNG_ASSIST_INFO_MEM, buf, len); +} + +void stub_target_km_read_public_info(uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("read_public_info: NULL buf\n"); + return; + } + if (len > KEYMNG_PUBLIC_INFO_MEM_SIZE) { + len = KEYMNG_PUBLIC_INFO_MEM_SIZE; + } + km_read_mem(KEYMNG_PUBLIC_INFO_MEM, buf, len); +} + +/* ---------- Result checks ----------------------------------------------- */ + +bool stub_target_km_is_huk_valid(void) +{ + return (REG_READ(KEYMNG_HUK_VLD_REG) & KEYMNG_HUK_VLD_M) != 0U; +} + +/* USE_EFUSE_KEY is keyed by stub_km_key_type_t enum (0..4). KEY_VLD has a + * different encoding split by ECDSA length — see key_vld_mask(). + * The shift form avoids producing a .rodata jump table. */ +static uint32_t key_type_bit(stub_km_key_type_t key_type) +{ + if ((unsigned)key_type > 4U) { + return 0U; + } + return BIT((unsigned)key_type); +} + +/* Per-bit lookup for KEYMNG_KEY_VLD — explicitly enumerated because the + * length-aware split (ECDSA-192/256/384 occupy bits 0/1/6) doesn't map to + * a single shift formula. */ +static uint32_t key_vld_mask(stub_km_key_type_t kt, stub_km_key_len_t kl) +{ + if (kt == STUB_KM_KEY_TYPE_ECDSA) { + if (kl == STUB_KM_KEY_LEN_ECDSA_192) { + return KEYMNG_KEY_VLD_ECDSA_192_M; + } + if (kl == STUB_KM_KEY_LEN_ECDSA_256) { + return KEYMNG_KEY_VLD_ECDSA_256_M; + } + if (kl == STUB_KM_KEY_LEN_ECDSA_384) { + return KEYMNG_KEY_VLD_ECDSA_384_M; + } + } + if (kt == STUB_KM_KEY_TYPE_FLASH_XTS_AES) { + return KEYMNG_KEY_VLD_FLASH_M; + } + if (kt == STUB_KM_KEY_TYPE_HMAC) { + return KEYMNG_KEY_VLD_HMAC_M; + } + if (kt == STUB_KM_KEY_TYPE_DS) { + return KEYMNG_KEY_VLD_DS_M; + } + if (kt == STUB_KM_KEY_TYPE_PSRAM_XTS_AES) { + return KEYMNG_KEY_VLD_PSRAM_M; + } + return 0U; +} + +bool stub_target_km_is_key_deployment_valid(stub_km_key_type_t key_type, stub_km_key_len_t key_len) +{ + uint32_t mask = key_vld_mask(key_type, key_len); + if (mask == 0U) { + return false; + } + return (REG_READ(KEYMNG_KEY_VLD_REG) & mask) != 0U; +} + +void stub_target_km_set_key_usage(stub_km_key_type_t key_type, bool use_own_key) +{ + /* KEYMNG_USE_EFUSE_KEY[bit_for_type] = 1 → use eFuse, 0 → use KM-deployed. + * Each key type maps to a single bit, in the same order as KEYMNG_KEY_VLD_REG. */ + uint32_t mask = key_type_bit(key_type); + if (mask == 0U) { + return; + } + if (use_own_key) { + REG_CLR_BIT(KEYMNG_STATIC_REG, mask); + } else { + REG_SET_BIT(KEYMNG_STATIC_REG, mask); + } +} + +/* ---------- eFuse: KM_INIT_KEY presence ---------------------------------- */ + +/* Inline-unrolled to keep the slot table out of .rodata (plugin linker + * script forbids initialized .data). */ +#define EFUSE_C5_KM_INIT_KEY_PURPOSE_VAL 12U + +bool stub_target_km_is_efuse_init_key_burned(void) +{ + uint32_t data1 = REG_READ(EFUSE_RD_REPEAT_DATA1_REG); + uint32_t data2 = REG_READ(EFUSE_RD_REPEAT_DATA2_REG); + const uint32_t mask = EFUSE_C5_KEY_PURPOSE_M; + const uint32_t target = EFUSE_C5_KM_INIT_KEY_PURPOSE_VAL; + + if (((data1 >> EFUSE_C5_KEY0_PURPOSE_IN_DATA1_S) & mask) == target) { + return true; + } + if (((data1 >> EFUSE_C5_KEY1_PURPOSE_IN_DATA1_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_C5_KEY2_PURPOSE_IN_DATA2_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_C5_KEY3_PURPOSE_IN_DATA2_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_C5_KEY4_PURPOSE_IN_DATA2_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_C5_KEY5_PURPOSE_IN_DATA2_S) & mask) == target) { + return true; + } + return false; +} diff --git a/src/target/esp32p4/CMakeLists.txt b/src/target/esp32p4/CMakeLists.txt index d684f130..0dc7027a 100644 --- a/src/target/esp32p4/CMakeLists.txt +++ b/src/target/esp32p4/CMakeLists.txt @@ -7,6 +7,7 @@ set(srcs src/usb_otg.c src/clock.c src/aes_xts.c + src/key_mgr.c ) add_library(${ESP_TARGET_LIB} STATIC ${srcs}) diff --git a/src/target/esp32p4/include/soc/hp_sys_clkrst_reg.h b/src/target/esp32p4/include/soc/hp_sys_clkrst_reg.h new file mode 100644 index 00000000..c9e93801 --- /dev/null +++ b/src/target/esp32p4/include/soc/hp_sys_clkrst_reg.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-P4 HP_SYS_CLKRST registers — only the subset the Key Manager HAL + * needs (KM bus clock + peripheral clock + reset, parent crypto reset). + * + * Layout matches the ESP32-P4 register reference; field numbering follows + * IDF's `soc/hp_sys_clkrst_reg.h`. ESP32-P4 uses these registers in place + * of the PCR registers that ESP32-C5 has for the equivalent functions. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "reg_base.h" + +/** HP_SYS_CLKRST_SOC_CLK_CTRL1_REG: SoC-level system clock gates. + * Bit 27 = KEY_MANAGER_SYS_CLK_EN — the KM bus clock. Defaults to enabled + * on cold boot but explicit re-assertion is the cheapest insurance against + * a previous boot leaving it gated off. */ +#define HP_SYS_CLKRST_SOC_CLK_CTRL1_REG (DR_REG_HP_SYS_CLKRST_BASE + 0x18) +#define HP_SYS_CLKRST_KEY_MANAGER_SYS_CLK_EN_M (1U << 27) + +/** HP_SYS_CLKRST_PERI_CLK_CTRL25_REG: peripheral clock gates for the + * crypto sub-block (ECC, ECDSA, KM all live here). The KM peripheral + * clock is bit 22 — independent of the bus clock above. */ +#define HP_SYS_CLKRST_PERI_CLK_CTRL25_REG (DR_REG_HP_SYS_CLKRST_BASE + 0xA8) +#define HP_SYS_CLKRST_CRYPTO_ECC_CLK_EN_M (1U << 16) +#define HP_SYS_CLKRST_CRYPTO_ECDSA_CLK_EN_M (1U << 21) +#define HP_SYS_CLKRST_CRYPTO_KM_CLK_EN_M (1U << 22) + +/** HP_SYS_CLKRST_HP_RST_EN2_REG: reset bits for HP-domain crypto blocks. + * RST_EN_CRYPTO (bit 14) is the parent crypto reset — when asserted it + * holds the KM in reset regardless of RST_EN_KM. Both bits must be 0 for + * the KM to operate. */ +#define HP_SYS_CLKRST_HP_RST_EN2_REG (DR_REG_HP_SYS_CLKRST_BASE + 0xC8) +#define HP_SYS_CLKRST_RST_EN_CRYPTO_M (1U << 14) +#define HP_SYS_CLKRST_RST_EN_ECDSA_M (1U << 20) +#define HP_SYS_CLKRST_RST_EN_ECC_M (1U << 22) +#define HP_SYS_CLKRST_RST_EN_KM_M (1U << 23) + +#ifdef __cplusplus +} +#endif diff --git a/src/target/esp32p4/include/soc/huk_reg.h b/src/target/esp32p4/include/soc/huk_reg.h new file mode 100644 index 00000000..2b831321 --- /dev/null +++ b/src/target/esp32p4/include/soc/huk_reg.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-P4 HUK Generator peripheral registers (hw_ver3). + * + * Layout matches the ESP32-P4 register reference (hw_ver3). Compared with + * ESP32-C5 this file omits the LP_AON PUF SRAM power/discharge bits — the + * P4 HUK Generator's MMIO is in the LP-AON peripheral region but doesn't + * require the cold-boot PUF SRAM recharge dance that C5 needs + * (SOC_HUK_MEM_NEEDS_RECHARGE=0 in IDF's P4 soc_caps). + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "reg_base.h" + +/** HUK_CLK_REG: register and memory clock gates. + * HUK_CLK_EN (bit 0) defaults on — register-side clock. + * HUK_MEM_CG_FORCE_ON (bit 1) defaults OFF — without it HUK_INFO_MEM + * reads return zero even after a successful GENERATE phase. */ +#define HUK_CLK_REG (DR_REG_HUK_BASE + 0x0004) +#define HUK_CLK_EN_M (1U << 0) +#define HUK_MEM_CG_FORCE_ON_M (1U << 1) + +/** HUK_CONF_REG: HUK_MODE (bit 0). 0 = Recovery, 1 = Generation. */ +#define HUK_CONF_REG (DR_REG_HUK_BASE + 0x0020) +#define HUK_MODE_M (1U << 0) + +/** HUK_START_REG: write 1 to advance the state machine. */ +#define HUK_START_REG (DR_REG_HUK_BASE + 0x0024) +#define HUK_START (1U << 0) +#define HUK_CONTINUE (1U << 1) + +/** HUK_STATE_REG: 0=IDLE, 1=LOAD, 2=GAIN, 3=BUSY. */ +#define HUK_STATE_REG (DR_REG_HUK_BASE + 0x0028) +#define HUK_STATE_M 0x3U + +/** HUK_STATUS_REG: HUK_STATUS[1:0] + HUK_RISK_LEVEL[4:2]. */ +#define HUK_STATUS_REG (DR_REG_HUK_BASE + 0x0034) +#define HUK_STATUS_S 0U +#define HUK_STATUS_M 0x3U +#define HUK_RISK_LEVEL_S 2U +#define HUK_RISK_LEVEL_M 0x7U + +/** HUK_INFO_MEM: 384-byte MMIO window — NOT the full huk_info. The ROM + * routine esp_rom_km_huk_conf iterates this window across multiple passes to + * read/write the full STUB_KM_HUK_INFO_SIZE (660) byte huk_info blob during + * generation (read out) and recovery (write in). */ +#define HUK_INFO_MEM (DR_REG_HUK_BASE + 0x0100) +#define HUK_INFO_MEM_SIZE 384 + +#ifdef __cplusplus +} +#endif diff --git a/src/target/esp32p4/include/soc/keymng_reg.h b/src/target/esp32p4/include/soc/keymng_reg.h new file mode 100644 index 00000000..86d1ae69 --- /dev/null +++ b/src/target/esp32p4/include/soc/keymng_reg.h @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-P4 Key Manager peripheral registers (hw_ver3). + * + * Layout matches the ESP32-P4 register reference (hw_ver3) — the v3.0+ + * Key Manager has full parity with ESP32-C5: same offsets, same field + * semantics. The first-generation KM on ESP32-P4 v1.x is disabled in + * software (see SUPPORTS_KEY_MANAGER gating in esptool's esp32p4.py). + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "reg_base.h" + +/* ---------- Configuration / control ------------------------------------- */ + +/** KEYMNG_STATIC_REG: KEY_LEN, USE_SW_INIT_KEY, USE_EFUSE_KEY etc. */ +#define KEYMNG_STATIC_REG (DR_REG_KEYMNG_BASE + 0x0018) +#define KEYMNG_USE_EFUSE_KEY_S 0U /* bits[4:0]: 1 bit per key type */ +#define KEYMNG_USE_EFUSE_KEY_M 0x1FU +#define KEYMNG_RND_SWITCH_CYCLE_S 5U /* bits[9:5] */ +#define KEYMNG_USE_SW_INIT_KEY_S 10U /* bit10: 1=use sw_init_key, 0=use efuse */ +#define KEYMNG_FLASH_KEY_LEN_S 11U /* bit11: 0=XTS-AES-128, 1=XTS-AES-256 */ +#define KEYMNG_PSRAM_KEY_LEN_S 12U + +/** KEYMNG_LOCK_REG: write-1 lock for fields in STATIC_REG (one-shot). */ +#define KEYMNG_LOCK_REG (DR_REG_KEYMNG_BASE + 0x001C) + +/** KEYMNG_CONF_REG: KGEN_MODE + KEY_PURPOSE. Configured in IDLE phase. */ +#define KEYMNG_CONF_REG (DR_REG_KEYMNG_BASE + 0x0020) +#define KEYMNG_KGEN_MODE_S 0U /* bits[2:0]: deploy mode */ +#define KEYMNG_KGEN_MODE_M 0x7U +#define KEYMNG_KEY_PURPOSE_S 3U /* bits[6:3]: target key purpose 1..12 */ +#define KEYMNG_KEY_PURPOSE_M 0xFU + +/** KEYMNG_START_REG: write 1 to advance state machine. */ +#define KEYMNG_START_REG (DR_REG_KEYMNG_BASE + 0x0024) +#define KEYMNG_START (1U << 0) +#define KEYMNG_CONTINUE (1U << 1) + +/** KEYMNG_STATE_REG: current FSM phase. */ +#define KEYMNG_STATE_REG (DR_REG_KEYMNG_BASE + 0x0028) +#define KEYMNG_STATE_M 0x3U +/* Values: 0=IDLE, 1=LOAD, 2=GAIN, 3=BUSY */ + +/** KEYMNG_RESULT_REG: per-bit per-key-type "deploy operation succeeded". */ +#define KEYMNG_RESULT_REG (DR_REG_KEYMNG_BASE + 0x002C) + +/** KEYMNG_KEY_VLD_REG: per-bit "this (key_type, key_len) has been deployed + * and is ready to use". Note these bits are split by ECDSA *length* — not + * the same encoding as KEYMNG_USE_EFUSE_KEY (which is by type only). + * Layout matches ESP32-C5 register reference. */ +#define KEYMNG_KEY_VLD_REG (DR_REG_KEYMNG_BASE + 0x0030) +#define KEYMNG_KEY_VLD_ECDSA_192_M (1U << 0) +#define KEYMNG_KEY_VLD_ECDSA_256_M (1U << 1) +#define KEYMNG_KEY_VLD_FLASH_M (1U << 2) +#define KEYMNG_KEY_VLD_HMAC_M (1U << 3) +#define KEYMNG_KEY_VLD_DS_M (1U << 4) +#define KEYMNG_KEY_VLD_PSRAM_M (1U << 5) +#define KEYMNG_KEY_VLD_ECDSA_384_M (1U << 6) + +/** KEYMNG_HUK_VLD_REG: 1 if a HUK is currently loaded into the KM. */ +#define KEYMNG_HUK_VLD_REG (DR_REG_KEYMNG_BASE + 0x0034) +#define KEYMNG_HUK_VLD_M (1U << 0) + +/* ---------- Memory blocks ----------------------------------------------- */ + +/** Assist info: write k2_info / read k2*G (ECDH0) or write key_info (recovery). */ +#define KEYMNG_ASSIST_INFO_MEM (DR_REG_KEYMNG_BASE + 0x0100) /* 64 bytes */ +#define KEYMNG_ASSIST_INFO_MEM_SIZE 64 + +/** Public info: write k1_encrypted (AES) or k1*G (ECDH0/1) or read key_info. */ +#define KEYMNG_PUBLIC_INFO_MEM (DR_REG_KEYMNG_BASE + 0x0140) /* 64 bytes */ +#define KEYMNG_PUBLIC_INFO_MEM_SIZE 64 + +/** sw_init_key (32 bytes), used when KEYMNG_USE_SW_INIT_KEY=1. */ +#define KEYMNG_SW_INIT_KEY_MEM (DR_REG_KEYMNG_BASE + 0x0180) +#define KEYMNG_SW_INIT_KEY_MEM_SIZE 32 + +#ifdef __cplusplus +} +#endif diff --git a/src/target/esp32p4/include/soc/soc_caps.h b/src/target/esp32p4/include/soc/soc_caps.h index e2ecf8bd..794a9063 100644 --- a/src/target/esp32p4/include/soc/soc_caps.h +++ b/src/target/esp32p4/include/soc/soc_caps.h @@ -9,6 +9,7 @@ /*-------------------------- COMMON CAPS ---------------------------------------*/ #define SOC_UART_HP_NUM (5) /*!< HP UART number */ #define SOC_UART_HAS_SYNC_REG_UPDATE 1 +#define SOC_KEY_MANAGER_SUPPORTED 1 // Memory Caps #define SOC_RTC_FAST_MEM_SUPPORTED 1 diff --git a/src/target/esp32p4/src/key_mgr.c b/src/target/esp32p4/src/key_mgr.c new file mode 100644 index 00000000..4ee97dc0 --- /dev/null +++ b/src/target/esp32p4/src/key_mgr.c @@ -0,0 +1,434 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + * ESP32-P4 Key Manager + HUK Generator HAL. + * + * Single-file port of the operations the stub keymanager plugin needs out + * of ESP-IDF's `components/esp_security/src/esp_key_mgr.c`. The HAL/LL + * split from IDF is collapsed (no portability layer needed in the stub). + * OS locks, logging, eFuse helpers, and chip-clock plumbing from the IDF + * version are dropped — the stub runs single-threaded. + * + * Compared with the ESP32-C5 sibling, ESP32-P4 uses HP_SYS_CLKRST instead + * of PCR for clock/reset control and does not need the LP_AON PUF SRAM + * power dance (SOC_HUK_MEM_NEEDS_RECHARGE=0). Field semantics and KEY_VLD + * bit layout are identical to C5 on v3.0+ silicon. + * + * Reference: ESP-IDF `components/esp_hal_security/esp32p4/include/hal/` + * - key_mgr_ll.h (bus clock + reset sequencing) + * - huk_ll.h (HUK generator state machine) + * and the ESP32-P4 register reference (hw_ver3). + */ + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +extern void *memcpy(void *dest, const void *src, size_t n); + +/* ---------- Small utilities --------------------------------------------- */ + +/* Write @p len bytes to a 32-bit-aligned memory-mapped peripheral region. + * Both ASSIST_INFO_MEM and PUBLIC_INFO_MEM only accept 4-byte stores; @p len + * is rounded up to a 4-byte boundary internally. */ +static void km_write_mem(uint32_t reg, const uint8_t *src, size_t len) +{ + uint32_t i = 0; + while (i + 4 <= len) { + uint32_t word = ((uint32_t)src[i]) | ((uint32_t)src[i + 1] << 8) | ((uint32_t)src[i + 2] << 16) | + ((uint32_t)src[i + 3] << 24); + REG_WRITE(reg + i, word); + i += 4; + } + if (i < len) { + uint32_t word = 0; + for (size_t j = 0; j + i < len; j++) { + word |= (uint32_t)src[i + j] << (8U * j); + } + REG_WRITE(reg + i, word); + } +} + +static void km_read_mem(uint32_t reg, uint8_t *dst, size_t len) +{ + uint32_t i = 0; + while (i + 4 <= len) { + uint32_t word = REG_READ(reg + i); + dst[i + 0] = (uint8_t)(word & 0xFF); + dst[i + 1] = (uint8_t)((word >> 8) & 0xFF); + dst[i + 2] = (uint8_t)((word >> 16) & 0xFF); + dst[i + 3] = (uint8_t)((word >> 24) & 0xFF); + i += 4; + } + if (i < len) { + uint32_t word = REG_READ(reg + i); + for (size_t j = 0; j + i < len; j++) { + dst[i + j] = (uint8_t)((word >> (8U * j)) & 0xFFU); + } + } +} + +/* ---------- HUK Generator ----------------------------------------------- */ + +void stub_target_huk_get_status(uint8_t *gen_status, uint8_t *risk_level) +{ + uint32_t reg = REG_READ(HUK_STATUS_REG); + if (gen_status) { + *gen_status = (uint8_t)((reg >> HUK_STATUS_S) & HUK_STATUS_M); + } + if (risk_level) { + *risk_level = (uint8_t)((reg >> HUK_RISK_LEVEL_S) & HUK_RISK_LEVEL_M); + } +} + +/* HUK_MODE values matching the ROM signature (huk_mode_t in rom/km.h). */ +#define HUK_ROM_MODE_RECOVER 0 +#define HUK_ROM_MODE_GEN 1 + +/* esp_rom_km_huk_conf — full HUK state-machine driver, including state + * polling and (in GENERATE mode) the read-out into huk_info_buf. Returns + * ETS_OK (=0) on success. + * + * Defined in P4 ROM (different address on rev1 vs eco5) — provided by + * esp32p4.rom.ld / esp32p4.rom.eco5.ld. */ +extern int esp_rom_km_huk_conf(int mode, uint8_t *huk_info); + +int stub_target_huk_configure(stub_huk_mode_t mode, uint8_t *huk_info_buf) +{ + if (huk_info_buf == NULL) { + STUB_LOGE("huk_info_buf is NULL\n"); + return STUB_LIB_ERR_INVALID_ARG; + } + + int rom_mode = (mode == STUB_HUK_MODE_GENERATE) ? HUK_ROM_MODE_GEN : HUK_ROM_MODE_RECOVER; + + /* P4 doesn't need the LP_AON PUF SRAM recharge that C5 requires on cold + * boot — SOC_HUK_MEM_NEEDS_RECHARGE=0 in IDF's P4 soc_caps. The ROM + * HUK configure routine sequences power-up and state polling on its own. + * + * The return value is intentionally ignored: esp_rom_km_huk_conf always + * returns 0 (its result carries no information per the ROM source), so + * success is judged solely by HUK_STATUS gen_status == 1 below. */ + (void)esp_rom_km_huk_conf(rom_mode, huk_info_buf); + + uint8_t gen = 0; + stub_target_huk_get_status(&gen, NULL); + if (gen != 1U) { + STUB_LOGE("HUK Generator did not report gen_status=1 (got %u)\n", (unsigned)gen); + return STUB_LIB_FAIL; + } + return STUB_LIB_OK; +} + +/* ---------- Capability -------------------------------------------------- */ + +bool stub_target_km_is_supported(void) +{ + /* P4 KM uses the 660-byte HUK_INFO only on >= v3.0 (ROM ECO >= 5); earlier + * silicon had a 384-byte HUK and is unsupported. Wafer version lives in + * EFUSE_RD_MAC_SYS_2: major = (bit23 << 2) | major_lo[5:4] (matches esptool). */ + uint32_t w = REG_READ(EFUSE_RD_MAC_SYS_2_REG); + uint32_t major = + (((w >> 23) & 0x1U) << 2) | ((w >> EFUSE_WAFER_VERSION_MAJOR_LO_S) & EFUSE_WAFER_VERSION_MAJOR_LO_V); + return major >= 3U; +} + +/* ---------- Key Manager bring-up ---------------------------------------- */ + +void stub_target_km_bringup(void) +{ + /* Mirror IDF's esp_key_mgr_init (esp_security/src/init.c): + * 1. Power up KM (no-op on P4 — empty key_mgr_ll_power_up) + * 2. Enable bus clock (HP_SYS_CLKRST.soc_clk_ctrl1.reg_key_manager_sys_clk_en) + * 3. Enable peripheral clock (HP_SYS_CLKRST.peri_clk_ctrl25.reg_crypto_km_clk_en) + * 4. Reset KM (pulse RST_EN_KM) + * 5. Clear parent crypto reset (RST_EN_CRYPTO) — without this the KM + * stays held in reset regardless of RST_EN_KM + * 6. Wait for the KM state machine to fall through into IDLE + * + * Also enable ECC + ECDSA peripheral clocks and clear their resets so + * ECDH0 / ECDH1 deploys can drive the external ECC multiplier without + * silently producing zero for k2*G. The C5 sibling does an equivalent + * sequence via PCR registers. */ + + /* Bus clock + crypto peripheral clocks. */ + REG_SET_BIT(HP_SYS_CLKRST_SOC_CLK_CTRL1_REG, HP_SYS_CLKRST_KEY_MANAGER_SYS_CLK_EN_M); + + REG_SET_BIT(HP_SYS_CLKRST_PERI_CLK_CTRL25_REG, + HP_SYS_CLKRST_CRYPTO_KM_CLK_EN_M | HP_SYS_CLKRST_CRYPTO_ECC_CLK_EN_M | + HP_SYS_CLKRST_CRYPTO_ECDSA_CLK_EN_M); + + /* Reset pulse for KM, then leave reset clear. */ + REG_SET_BIT(HP_SYS_CLKRST_HP_RST_EN2_REG, HP_SYS_CLKRST_RST_EN_KM_M); + REG_CLR_BIT(HP_SYS_CLKRST_HP_RST_EN2_REG, HP_SYS_CLKRST_RST_EN_KM_M); + + /* Clear parent crypto reset + ECC / ECDSA resets so the KM can drive + * the external ECC block during ECDH0 / ECDH1 deploys. */ + REG_CLR_BIT(HP_SYS_CLKRST_HP_RST_EN2_REG, + HP_SYS_CLKRST_RST_EN_CRYPTO_M | HP_SYS_CLKRST_RST_EN_ECC_M | HP_SYS_CLKRST_RST_EN_ECDSA_M); + + /* Wait for KM to settle into IDLE before any subsequent register + * access. Reading state before this point can return non-IDLE + * depending on what the previous boot left in the FSM. */ + while ((REG_READ(KEYMNG_STATE_REG) & KEYMNG_STATE_M) != 0U) { + ; + } + + /* Mirror IDF's esp_key_mgr_init final step: force USE_EFUSE_KEY for the + * XTS-AES Flash key type on non-flash-encrypted boots. Without this the + * KM is in a default state where it expects a deployed key for flash + * decryption, which can interfere with subsequent HUK operations. The + * HUK peripheral's own register/memory clock gates are configured by + * the ROM huk_conf routine on demand — no separate setup needed here, + * unlike C5 which has LP_AON power control to drive. + * + * USE_EFUSE_KEY is keyed by key type, so derive the bit from the enum + * rather than a literal to track any enum reordering. */ + REG_SET_BIT(KEYMNG_STATIC_REG, BIT(STUB_KM_KEY_TYPE_FLASH_XTS_AES)); +} + +/* ---------- Key Manager state machine ----------------------------------- */ + +stub_km_state_t stub_target_km_get_state(void) +{ + return (stub_km_state_t)(REG_READ(KEYMNG_STATE_REG) & KEYMNG_STATE_M); +} + +void stub_target_km_wait_for_state(stub_km_state_t state) +{ + while (stub_target_km_get_state() != state) { + ; + } +} + +void stub_target_km_set_keygen_mode(stub_km_keygen_mode_t mode) +{ + uint32_t reg = REG_READ(KEYMNG_CONF_REG); + reg &= ~(KEYMNG_KGEN_MODE_M << KEYMNG_KGEN_MODE_S); + reg |= ((uint32_t)mode & KEYMNG_KGEN_MODE_M) << KEYMNG_KGEN_MODE_S; + REG_WRITE(KEYMNG_CONF_REG, reg); +} + +void stub_target_km_set_key_purpose(stub_km_key_purpose_t purpose) +{ + uint32_t reg = REG_READ(KEYMNG_CONF_REG); + reg &= ~(KEYMNG_KEY_PURPOSE_M << KEYMNG_KEY_PURPOSE_S); + reg |= ((uint32_t)purpose & KEYMNG_KEY_PURPOSE_M) << KEYMNG_KEY_PURPOSE_S; + REG_WRITE(KEYMNG_CONF_REG, reg); +} + +void stub_target_km_use_sw_init_key(void) +{ + REG_SET_BIT(KEYMNG_STATIC_REG, BIT(KEYMNG_USE_SW_INIT_KEY_S)); +} + +void stub_target_km_set_xts_aes_key_len(stub_km_key_type_t key_type, bool use_256) +{ + uint32_t shift; + if (key_type == STUB_KM_KEY_TYPE_FLASH_XTS_AES) { + shift = KEYMNG_FLASH_KEY_LEN_S; + } else if (key_type == STUB_KM_KEY_TYPE_PSRAM_XTS_AES) { + shift = KEYMNG_PSRAM_KEY_LEN_S; + } else { + return; /* not an XTS-AES key type — KM ignores the len bit */ + } + if (use_256) { + REG_SET_BIT(KEYMNG_STATIC_REG, BIT(shift)); + } else { + REG_CLR_BIT(KEYMNG_STATIC_REG, BIT(shift)); + } +} + +void stub_target_km_start(void) +{ + REG_WRITE(KEYMNG_START_REG, KEYMNG_START); +} + +void stub_target_km_continue(void) +{ + REG_WRITE(KEYMNG_START_REG, KEYMNG_CONTINUE); +} + +/* ---------- KM memory I/O ----------------------------------------------- */ + +void stub_target_km_write_sw_init_key(const uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("write_sw_init_key: NULL buf\n"); + return; + } + if (len > KEYMNG_SW_INIT_KEY_MEM_SIZE) { + len = KEYMNG_SW_INIT_KEY_MEM_SIZE; /* never spill past the MMIO window */ + } + km_write_mem(KEYMNG_SW_INIT_KEY_MEM, buf, len); +} + +void stub_target_km_write_assist_info(const uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("write_assist_info: NULL buf\n"); + return; + } + if (len > KEYMNG_ASSIST_INFO_MEM_SIZE) { + len = KEYMNG_ASSIST_INFO_MEM_SIZE; + } + km_write_mem(KEYMNG_ASSIST_INFO_MEM, buf, len); +} + +void stub_target_km_write_public_info(const uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("write_public_info: NULL buf\n"); + return; + } + if (len > KEYMNG_PUBLIC_INFO_MEM_SIZE) { + len = KEYMNG_PUBLIC_INFO_MEM_SIZE; + } + km_write_mem(KEYMNG_PUBLIC_INFO_MEM, buf, len); +} + +void stub_target_km_read_assist_info(uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("read_assist_info: NULL buf\n"); + return; + } + if (len > KEYMNG_ASSIST_INFO_MEM_SIZE) { + len = KEYMNG_ASSIST_INFO_MEM_SIZE; + } + km_read_mem(KEYMNG_ASSIST_INFO_MEM, buf, len); +} + +void stub_target_km_read_public_info(uint8_t *buf, size_t len) +{ + if (buf == NULL) { + STUB_LOGE("read_public_info: NULL buf\n"); + return; + } + if (len > KEYMNG_PUBLIC_INFO_MEM_SIZE) { + len = KEYMNG_PUBLIC_INFO_MEM_SIZE; + } + km_read_mem(KEYMNG_PUBLIC_INFO_MEM, buf, len); +} + +/* ---------- Result checks ----------------------------------------------- */ + +bool stub_target_km_is_huk_valid(void) +{ + return (REG_READ(KEYMNG_HUK_VLD_REG) & KEYMNG_HUK_VLD_M) != 0U; +} + +/* USE_EFUSE_KEY is keyed by stub_km_key_type_t enum (0..4). KEY_VLD has a + * different encoding split by ECDSA length — see key_vld_mask(). + * The shift form avoids producing a .rodata jump table. */ +static uint32_t key_type_bit(stub_km_key_type_t key_type) +{ + if ((unsigned)key_type > 4U) { + return 0U; + } + return BIT((unsigned)key_type); +} + +/* Per-bit lookup for KEYMNG_KEY_VLD — explicitly enumerated because the + * length-aware split (ECDSA-192/256/384 occupy bits 0/1/6) doesn't map to + * a single shift formula. */ +static uint32_t key_vld_mask(stub_km_key_type_t kt, stub_km_key_len_t kl) +{ + if (kt == STUB_KM_KEY_TYPE_ECDSA) { + if (kl == STUB_KM_KEY_LEN_ECDSA_192) { + return KEYMNG_KEY_VLD_ECDSA_192_M; + } + if (kl == STUB_KM_KEY_LEN_ECDSA_256) { + return KEYMNG_KEY_VLD_ECDSA_256_M; + } + if (kl == STUB_KM_KEY_LEN_ECDSA_384) { + return KEYMNG_KEY_VLD_ECDSA_384_M; + } + } + if (kt == STUB_KM_KEY_TYPE_FLASH_XTS_AES) { + return KEYMNG_KEY_VLD_FLASH_M; + } + if (kt == STUB_KM_KEY_TYPE_HMAC) { + return KEYMNG_KEY_VLD_HMAC_M; + } + if (kt == STUB_KM_KEY_TYPE_DS) { + return KEYMNG_KEY_VLD_DS_M; + } + if (kt == STUB_KM_KEY_TYPE_PSRAM_XTS_AES) { + return KEYMNG_KEY_VLD_PSRAM_M; + } + return 0U; +} + +bool stub_target_km_is_key_deployment_valid(stub_km_key_type_t key_type, stub_km_key_len_t key_len) +{ + uint32_t mask = key_vld_mask(key_type, key_len); + if (mask == 0U) { + return false; + } + return (REG_READ(KEYMNG_KEY_VLD_REG) & mask) != 0U; +} + +void stub_target_km_set_key_usage(stub_km_key_type_t key_type, bool use_own_key) +{ + /* KEYMNG_USE_EFUSE_KEY[bit_for_type] = 1 → use eFuse, 0 → use KM-deployed. + * Each key type maps to a single bit, in the same order as KEYMNG_KEY_VLD_REG. */ + uint32_t mask = key_type_bit(key_type); + if (mask == 0U) { + return; + } + if (use_own_key) { + REG_CLR_BIT(KEYMNG_STATIC_REG, mask); + } else { + REG_SET_BIT(KEYMNG_STATIC_REG, mask); + } +} + +/* ---------- eFuse: KM_INIT_KEY presence ---------------------------------- */ + +/* Inline-unrolled to keep the slot table out of .rodata (plugin linker + * script forbids initialized .data). */ +#define EFUSE_P4_KM_INIT_KEY_PURPOSE_VAL 12U + +bool stub_target_km_is_efuse_init_key_burned(void) +{ + uint32_t data1 = REG_READ(EFUSE_RD_REPEAT_DATA1_REG); + uint32_t data2 = REG_READ(EFUSE_RD_REPEAT_DATA2_REG); + const uint32_t mask = EFUSE_KEY_PURPOSE_0_V; + const uint32_t target = EFUSE_P4_KM_INIT_KEY_PURPOSE_VAL; + + if (((data1 >> EFUSE_KEY_PURPOSE_0_S) & mask) == target) { + return true; + } + if (((data1 >> EFUSE_KEY_PURPOSE_1_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_KEY_PURPOSE_2_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_KEY_PURPOSE_3_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_KEY_PURPOSE_4_S) & mask) == target) { + return true; + } + if (((data2 >> EFUSE_KEY_PURPOSE_5_S) & mask) == target) { + return true; + } + return false; +}