feat(keymanager): Add Key Manager flasher-stub plugin (ESP32-C5/P4) (ESPTOOL-1324) - #77
feat(keymanager): Add Key Manager flasher-stub plugin (ESP32-C5/P4) (ESPTOOL-1324)#77hrushikesh430 wants to merge 6 commits into
Conversation
Add a flasher-stub plugin that lets esptool drive the Key Manager (KM) peripheral on ESP32-C5 and ESP32-P4 to deploy and recover keys. The plugin exposes 7 wire opcodes (0xDF..0xE5), one per KM operation: key-deploy-random / -aes / -ecdh0 / -ecdh1, key-recovery, huk-deploy and huk-recovery. Each handler takes already-wrapped inputs from the host (k1_encrypted, k2_info, k1_G, k2_G) and drives the KM state machine via the esp-stub-lib KM HAL. There is no chip-side crypto: espsecure on the host does all AES/ECDH preprocessing. Details: - src/keymanager_plugin.c: the 7 opcode handlers + KM/HUK sequencing. If a deploy uses the eFuse init_key path but no key block has KM_INIT_KEY burned, it returns the new RESPONSE_KM_INIT_KEY_NOT_BURNED (0xCC00) so the host can print an actionable message instead of a silent KEY_VLD=0 failure. - src/commands.h: add RESPONSE_KM_INIT_KEY_NOT_BURNED (0xCC00). - src/CMakeLists.txt + src/ld/keymanager_plugin.ld: build and link the plugin. - tools/elf2json.py: export the plugin's dispatch symbol. - Bumps the esp-stub-lib submodule to the KM HAL commit (companion PR). Verified end-to-end on real ESP32-C5 hardware: huk-status/deploy/recovery, key-deploy-random/aes/ecdh0/ecdh1 (single + multi-stage) and key-recovery.
…eanly Three issues prevented a correct ESP32-P4 Key Manager plugin from being built from source: 1. ROM address: the plugin link globbed all ROM scripts into one sorted list, feeding both esp32p4.rom.ld (ECO0-4, esp_rom_km_huk_conf=0x4fc00718) and esp32p4.rom.eco5.ld (ECO>=5, 0x4fc0070c) and letting the ECO0-4 base win. esptool only drives the Key Manager on P4 >= v3.0 (ROM ECO >= 5), so the plugin called the wrong ROM address and crashed huk-deploy / huk-recovery with a Guru Meditation (Load access fault). Feed the base *.rom.ld first and *.rom.*.ld ECO overrides last so the ECO address wins. 2. BASE_SLIP_SEND_FRAME was never resolved: keymanager_plugin.ld forwards slip_send_frame to the base stub via BASE_SLIP_SEND_FRAME, but nothing defined it. compute_plugin_addrs.py now reads the base stub's symbol and emits STUB_BASE_DEFSYMS (--defsym BASE_SLIP_SEND_FRAME=<addr>), which the plugin link consumes. Drop the stale slip_recv_reset / slip_is_frame_complete / slip_get_frame_data forwards — those functions were removed when the transport receive path was refactored and the plugin does not use them (input arrives via the dispatcher's data/len). 3. Handlers were garbage-collected: keymanager_plugin.ld had no EXTERN() list, so --gc-sections dropped every handler except the ENTRY and elf2json.py could not resolve them. Add EXTERN() for all seven KM handlers, mirroring nand_plugin.ld. Also bump the esp-stub-lib submodule to c8dffbd (the committed HAL cleanup). Verified by a clean from-source build on ESP32-P4 v3.1: the plugin links, calls esp_rom_km_huk_conf at 0x4fc0070c, and huk-deploy / huk-recovery / key-deploy-aes / key-recovery all succeed on hardware.
The Key Manager is only supported on ESP32-P4 >= v3.0 (ROM ECO >= 5, the
revision with the 660-byte HUK_INFO). Previously the plugin link fed both the
ECO0-4 base ROM script (esp32p4.rom.ld) and the ECO>=5 one (esp32p4.rom.eco5.ld)
and relied on ordering so the ECO>=5 address won.
Make it explicit instead: when an ECO interface (*.rom.eco*.ld) exists, link
ONLY the ECO scripts (plus the shared *.rom.{api,extra,libc,libgcc}.ld) and
drop the older ECO0-4 base entirely, so the plugin is built exclusively for the
revisions esptool supports and can never pick up a stale ECO0-4 address. Chips
without an ECO variant (e.g. ESP32-C5) keep using their base interface.
Verified by clean builds: P4 links esp_rom_km_huk_conf at 0x4fc0070c with all 7
handlers; C5 still builds with its base interface.
Return RESPONSE_KM_UNSUPPORTED_CHIP (0xCC01) from the deploy, huk-deploy and huk-recovery handlers when stub_target_km_is_supported() is false. On ESP32-P4 the HAL reports support only from v3.0 (ROM ECO >= 5, 660-byte HUK); earlier silicon used a 384-byte HUK and is refused. Defense-in-depth alongside esptool's revision gate (KM_MIN_CHIP_REV) and stub selection.
…rt check The keymanager plugin calls stub_target_km_is_supported(), which lands in esp-stub-lib PR #113; the previously committed submodule pointer predates it and the plugin no longer compiled. Pin the submodule to the PR #113 head (espressif/esp-stub-lib#113) — to be re-bumped to the merged commit once that PR lands.
AdityaHPatwardhan
left a comment
There was a problem hiding this comment.
A few questions from reviewing the Key Manager plugin. One that isn't line-specific: the esp-stub-lib submodule is pinned to a fork-only commit (4ae32cb) — we should repoint it to the upstream merged SHA, so this can't merge before #113.
| { | ||
| /* Phase 1 — IDLE: configure mode + purpose + (XTS only) key length, | ||
| * plus any mode-specific static-register tweaks via pre_start_cb. */ | ||
| stub_target_km_wait_for_state(STUB_KM_STATE_IDLE); |
There was a problem hiding this comment.
Should wait_for_state take a timeout? It's void in the HAL, so if KM never reaches this state the single-threaded stub spins here forever and the host just sees a dead connection.
| stub_target_km_wait_for_state(STUB_KM_STATE_LOAD); | ||
| if (load_cb != NULL) { | ||
| int err = load_cb(purpose, hdr, data, data_len); | ||
| if (err != 0) { |
There was a problem hiding this comment.
On a load/gain/validation error after km_start(), should we drive KM back to IDLE before returning? Otherwise the next operation hangs at the wait_for_state(IDLE) above.
| s_km_state.pending_key_info_slots = 0U; | ||
|
|
||
| if (kind == KM_PENDING_HUK_INFO) { | ||
| slip_send_frame(s_km_state.huk_info, STUB_KM_HUK_INFO_WIRE_SIZE); |
There was a problem hiding this comment.
Should we stream through ctx->transport here instead of calling slip_send_frame directly? The NAND plugin uses the transport abstraction and we already have ctx in post_process.
| return RESPONSE_KM_UNSUPPORTED_CHIP; | ||
| } | ||
|
|
||
| stub_km_key_type_t key_type = (stub_km_key_type_t)hdr->key_type; |
There was a problem hiding this comment.
We cast key_type/key_len straight from the wire and use them before the GAIN-phase validity check — should we gate on the resolved purpose being valid up front?
| /* Key Manager: this chip/revision does not support the 660-byte HUK | ||
| * flow. ESP32-P4 only supports it from v3.0 (ROM ECO >= 5); earlier P4 | ||
| * silicon used a 384-byte HUK and is rejected. */ | ||
| RESPONSE_KM_UNSUPPORTED_CHIP = 0xCC01, |
There was a problem hiding this comment.
Should we mention RESPONSE_KM_UNSUPPORTED_CHIP (0xCC01) in the description too, and mirror it on the esptool side? Right now only 0xCC00 is documented.
What
Adds a flasher-stub plugin that lets esptool drive the Key Manager (KM) peripheral on ESP32-C5 and ESP32-P4 to deploy and recover keys.
Why
esptool is gaining Key Manager support. The flasher stub needs a plugin that exposes KM operations over the wire so esptool can deploy/recover keys at provisioning time. The actual chip register work lives in the
esp-stub-libKM HAL (companion PR below); this plugin is the wire layer on top of it.What's in this PR
src/keymanager_plugin.c— 7 wire opcodes (0xDF..0xE5), one per KM operation:key-deploy-random / -aes / -ecdh0 / -ecdh1,key-recovery,huk-deploy,huk-recovery. Each handler takes already-wrapped inputs from the host (k1_encrypted,k2_info,k1_G,k2_G) and drives the KM state machine via the esp-stub-lib HAL. No chip-side crypto —espsecureon the host does all AES/ECDH preprocessing.src/commands.h— new response codeRESPONSE_KM_INIT_KEY_NOT_BURNED(0xCC00). When a deploy uses the eFuse init-key path but no key block hasKM_INIT_KEYburned, the plugin returns this so the host can print an actionable error instead of a silentKEY_VLD=0failure.src/CMakeLists.txt+src/ld/keymanager_plugin.ld— build and link the plugin.tools/elf2json.py— export the plugin's dispatch symbol.esp-stub-lib→ KM HAL commit.Companion PR / dependency
esp-stub-libsubmodule is pinned to that PR's commit, which currently lives on the fork only. It will resolve fromespressif/esp-stub-libonce #113 is merged (the pointer should be updated to the merged SHA then).Testing
KM_TESTING_REPORT.md
Notes