Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1728,11 +1728,6 @@ msgstr ""
msgid "Failed to connect: timeout"
msgstr ""

#: ports/nordic/common-hal/_bleio/Connection.c
#: ports/zephyr-cp/common-hal/_bleio/Connection.c
msgid "Numeric comparison pairing"
msgstr ""

#: ports/nordic/common-hal/_bleio/UUID.c
msgid "Unexpected nrfx uuid type"
msgstr ""
Expand Down Expand Up @@ -4080,6 +4075,10 @@ msgstr ""
msgid "Unsupported hash algorithm"
msgstr ""

#: shared-bindings/hmac/__init__.c
msgid "hardware key slot is unused"
msgstr ""

#: shared-bindings/i2cioexpander/IOExpander.c
msgid "num_pins must be 8 or 16"
msgstr ""
Expand Down Expand Up @@ -4361,7 +4360,7 @@ msgstr ""
msgid "Failed to parse MP3 file"
msgstr ""

#: shared-module/bitbangio/I2C.c
#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c
msgid "%q too long"
msgstr ""

Expand Down Expand Up @@ -4419,6 +4418,14 @@ msgstr ""
msgid "unsupported colorspace for GifWriter"
msgstr ""

#: shared-module/hmac/HMAC.c
msgid "key does not support this digest"
msgstr ""

#: shared-module/hmac/HMAC.c
msgid "HMAC of an empty message is not supported with a hardware key"
msgstr ""

#: shared-module/i2cdisplaybus/I2CDisplayBus.c
#: shared-module/is31fl3741/IS31FL3741.c
#, c-format
Expand Down
88 changes: 88 additions & 0 deletions ports/espressif/common-hal/hardwarekey/HardwareKey.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey
//
// SPDX-License-Identifier: MIT

// The one port-specific step: turn an eFuse key block into a PSA key id.
// Everything after that -- hmac_sha256(), verify_hmac_sha256() -- lives in
// shared-module/hardwarekey/HardwareKey.c.

#include "common-hal/hardwarekey/__init__.h"
#include "common-hal/hardwarekey/board.h"

#include "shared-module/hardwarekey/HardwareKey.h"

#include "esp_efuse.h"

// board.h hardcodes the slot count (enum values can't be used in #if); make sure
// it still matches this chip's eFuse layout.
_Static_assert(HARDWAREKEY_EFUSE_SLOT_COUNT == EFUSE_BLK_KEY_MAX - EFUSE_BLK_KEY0,
"eFuse key block count changed; update common-hal/hardwarekey/board.h");

// Pulls in MBEDTLS_CONFIG_FILE (esp_config.h), which is what defines
// ESP_HMAC_OPAQUE_DRIVER_ENABLED on HMAC-capable chips. Including only
// <psa/crypto.h> goes through the tf-psa-crypto config path and does NOT
// define it, so the opaque-driver header below would compile to nothing.
#include "mbedtls/build_info.h"
#include "psa/crypto.h"
// Public header of the ESP-IDF mbedtls component's PSA opaque-key driver for
// eFuse HMAC keys (components/mbedtls/port/psa_driver/include/).
#include "psa_crypto_driver_esp_hmac_opaque.h"

#if !defined(ESP_HMAC_OPAQUE_DRIVER_ENABLED)
#error "hardwarekey requires the ESP-IDF PSA opaque HMAC driver (SOC_HMAC_SUPPORTED targets only)"
#endif

// The ESP HMAC peripheral consumes a 256-bit eFuse key.
#define HMAC_KEY_BITS 256

// One PSA key is imported per eFuse block. The imports are volatile references
// (no key material) and survive a CircuitPython soft reset -- ESP-IDF initializes
// PSA once at boot and never frees it -- so this only runs once per block.
static psa_key_id_t import_efuse_hmac_key(mp_int_t slot) {
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC);
psa_set_key_bits(&attr, HMAC_KEY_BITS);
psa_set_key_algorithm(&attr, PSA_ALG_HMAC(PSA_ALG_SHA_256));
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE);
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_ESP_HMAC_VOLATILE);

// Import data is a *reference* to the eFuse block, not key material. The
// driver independently re-checks the HMAC_UP purpose and refuses anything else.
esp_hmac_opaque_key_t keyref = { .efuse_key_id = (uint8_t)slot };

psa_key_id_t key_id = 0;
if (psa_import_key(&attr, (const uint8_t *)&keyref, sizeof(keyref), &key_id) != PSA_SUCCESS) {
return 0;
}
return key_id;
}

bool hardwarekey_efuse_slot_load(mp_int_t slot, hardwarekey_hardwarekey_obj_t *key) {
key->key_slot = slot;
key->key_id = 0;
key->purpose = HARDWAREKEY_PURPOSE_UNUSED;
key->exportable = false;

esp_efuse_block_t block = (esp_efuse_block_t)(EFUSE_BLK_KEY0 + slot);
if (esp_efuse_get_key_purpose(block) != ESP_EFUSE_KEY_PURPOSE_HMAC_UP) {
return false;
}

// PSA is already initialized by ssl / hashlib, but psa_crypto_init() is
// idempotent and keeps hardwarekey working on a build with neither.
if (psa_crypto_init() != PSA_SUCCESS) {
return false;
}

psa_key_id_t key_id = import_efuse_hmac_key(slot);
if (key_id == 0) {
return false;
}

key->key_id = key_id;
key->purpose = HARDWAREKEY_PURPOSE_HMAC;
key->exportable = !esp_efuse_get_key_dis_read(block);
return true;
}
30 changes: 30 additions & 0 deletions ports/espressif/common-hal/hardwarekey/__init__.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey
//
// SPDX-License-Identifier: MIT

#include "common-hal/hardwarekey/__init__.h"
#include "common-hal/hardwarekey/board.h"

#include "shared-bindings/hardwarekey/HardwareKey.h"

// The objects board.EFUSE_KEY0 .. board.EFUSE_KEY<n-1> point at (see board.h).
// Static, not heap: the board globals dict is const and outlives the GC heap
// across a soft reset, so these must too. No GC-traced pointers inside.
hardwarekey_hardwarekey_obj_t hardwarekey_efuse_keys[HARDWAREKEY_EFUSE_SLOT_COUNT];

// board.EFUSE_KEYn, for repr(). Can't assume MP_QSTR_EFUSE_KEY0 + n are contiguous.
static const qstr slot_names[HARDWAREKEY_EFUSE_SLOT_COUNT] = {
MP_QSTR_EFUSE_KEY0, MP_QSTR_EFUSE_KEY1, MP_QSTR_EFUSE_KEY2,
MP_QSTR_EFUSE_KEY3, MP_QSTR_EFUSE_KEY4, MP_QSTR_EFUSE_KEY5,
};

void espressif_hardwarekey_init(void) {
for (mp_int_t slot = 0; slot < HARDWAREKEY_EFUSE_SLOT_COUNT; slot++) {
hardwarekey_hardwarekey_obj_t *key = &hardwarekey_efuse_keys[slot];
key->base.type = &hardwarekey_hardwarekey_type;
key->name = slot_names[slot];
hardwarekey_efuse_slot_load(slot, key);
}
}
20 changes: 20 additions & 0 deletions ports/espressif/common-hal/hardwarekey/__init__.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey
//
// SPDX-License-Identifier: MIT

#pragma once

#include "py/obj.h"

#include "shared-module/hardwarekey/HardwareKey.h"

// Probe every eFuse key block and fill in the board.EFUSE_KEY* HardwareKey
// objects. Call once at startup, before user code. Never raises.
void espressif_hardwarekey_init(void);

// Fill in `key` for eFuse key block `slot`. Sets key->purpose to HMAC when the
// block is burned HMAC_UP (importing its PSA key), else HARDWAREKEY_PURPOSE_UNUSED.
// Never raises. Returns true when the slot ended up usable.
bool hardwarekey_efuse_slot_load(mp_int_t slot, hardwarekey_hardwarekey_obj_t *key);
30 changes: 30 additions & 0 deletions ports/espressif/common-hal/hardwarekey/board.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey
//
// SPDX-License-Identifier: MIT

#pragma once

// board.EFUSE_KEY0 .. board.EFUSE_KEY5: one fixed entry per eFuse key block,
// injected into every board's globals table through CIRCUITPY_BOARD_HARDWARE_KEYS
// (see shared-bindings/board/__init__.h). Each points at a static HardwareKey the
// startup probe (espressif_hardwarekey_init) fills in -- so, like board pins, the
// names exist at compile time and the objects are ready before user code runs.

#include "shared-module/hardwarekey/HardwareKey.h"

// Every HMAC-capable Espressif chip (S2/S3/C3/C5/C6/H2/P4) has 6 eFuse key blocks.
// HardwareKey.c static-asserts this against EFUSE_BLK_KEY_MAX - EFUSE_BLK_KEY0;
// if a future chip differs, update this list (and the assert) to match.
#define HARDWAREKEY_EFUSE_SLOT_COUNT 6

extern hardwarekey_hardwarekey_obj_t hardwarekey_efuse_keys[HARDWAREKEY_EFUSE_SLOT_COUNT];

#define CIRCUITPY_BOARD_HARDWARE_KEYS \
{ MP_ROM_QSTR(MP_QSTR_EFUSE_KEY0), MP_ROM_PTR(&hardwarekey_efuse_keys[0]) }, \
{ MP_ROM_QSTR(MP_QSTR_EFUSE_KEY1), MP_ROM_PTR(&hardwarekey_efuse_keys[1]) }, \
{ MP_ROM_QSTR(MP_QSTR_EFUSE_KEY2), MP_ROM_PTR(&hardwarekey_efuse_keys[2]) }, \
{ MP_ROM_QSTR(MP_QSTR_EFUSE_KEY3), MP_ROM_PTR(&hardwarekey_efuse_keys[3]) }, \
{ MP_ROM_QSTR(MP_QSTR_EFUSE_KEY4), MP_ROM_PTR(&hardwarekey_efuse_keys[4]) }, \
{ MP_ROM_QSTR(MP_QSTR_EFUSE_KEY5), MP_ROM_PTR(&hardwarekey_efuse_keys[5]) },
12 changes: 11 additions & 1 deletion ports/espressif/mpconfigport.mk
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ CIRCUITPY_ESPIDF ?= 1
CIRCUITPY_ESPULP ?= 1
CIRCUITPY_FRAMEBUFFERIO ?= 1
CIRCUITPY_FREQUENCYIO ?= 1
CIRCUITPY_HARDWAREKEY ?= 1
CIRCUITPY_HASHLIB ?= 1
CIRCUITPY_I2CTARGET = 0
CIRCUITPY_MAX3421E ?= 1
Expand All @@ -108,6 +109,9 @@ ifeq ($(IDF_TARGET),esp32)
# Modules
CIRCUITPY_RGBMATRIX = 0

# No HMAC peripheral (introduced starting with ESP32-S2)
CIRCUITPY_HARDWAREKEY = 0

# Has no USB
CIRCUITPY_USB_DEVICE = 0

Expand All @@ -121,6 +125,9 @@ CIRCUITPY_ESPCAMERA = 0
CIRCUITPY_ESPULP = 0
CIRCUITPY_MEMORYMAP = 0

# No HMAC peripheral (SOC_HMAC_SUPPORTED is not defined for this target)
CIRCUITPY_HARDWAREKEY = 0

# No capacitive touch peripheral
CIRCUITPY_ALARM_TOUCH = 0
CIRCUITPY_TOUCHIO_USE_NATIVE = 0
Expand Down Expand Up @@ -254,14 +261,17 @@ CIRCUITPY_SDIOIO = 0
CIRCUITPY_USB_DEVICE = 0
CIRCUITPY_ESP_USB_SERIAL_JTAG ?= 1

#### esp32c6 ##########################################################
#### esp32c61 #########################################################
else ifeq ($(IDF_TARGET),esp32c61)
# Modules
CIRCUITPY_ESPCAMERA = 0
CIRCUITPY_ESPULP = 0
CIRCUITPY_MEMORYMAP = 0
CIRCUITPY_RGBMATRIX = 0

# No HMAC peripheral (SOC_HMAC_SUPPORTED is not defined for this target)
CIRCUITPY_HARDWAREKEY = 0

# No capacitive touch peripheral
CIRCUITPY_ALARM_TOUCH = 0
CIRCUITPY_TOUCHIO_USE_NATIVE = 0
Expand Down
9 changes: 9 additions & 0 deletions ports/espressif/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#include "common-hal/busio/SPI.h"
#include "common-hal/busio/UART.h"
#include "common-hal/dualbank/__init__.h"
#if CIRCUITPY_HARDWAREKEY
#include "common-hal/hardwarekey/__init__.h"
#endif
#include "common-hal/ps2io/Ps2.h"
#include "common-hal/watchdog/WatchDogTimer.h"
#include "common-hal/socketpool/Socket.h"
Expand Down Expand Up @@ -294,6 +297,12 @@ safe_mode_t port_init(void) {

_never_reset_spi_ram_flash();

#if CIRCUITPY_HARDWAREKEY
// Populate board.EFUSE_KEY* from the eFuse key blocks. eFuse reads and the
// PSA key import need no filesystem or VM, so this is safe here.
espressif_hardwarekey_init();
#endif

esp_reset_reason_t reason = esp_reset_reason();
switch (reason) {
case ESP_RST_BROWNOUT:
Expand Down
6 changes: 6 additions & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ endif
ifeq ($(CIRCUITPY_GNSS),1)
SRC_PATTERNS += gnss/%
endif
ifeq ($(CIRCUITPY_HARDWAREKEY),1)
SRC_PATTERNS += hardwarekey/%
endif
ifeq ($(CIRCUITPY_HASHLIB),1)
SRC_PATTERNS += hashlib/%
endif
Expand Down Expand Up @@ -601,6 +604,8 @@ SRC_COMMON_HAL_ALL = \
rtc/__init__.c \
sdioio/SDCard.c \
sdioio/__init__.c \
hardwarekey/HardwareKey.c \
hardwarekey/__init__.c \
socketpool/__init__.c \
socketpool/SocketPool.c \
socketpool/Socket.c \
Expand Down Expand Up @@ -843,6 +848,7 @@ SRC_SHARED_MODULE_ALL = \
rotaryio/IncrementalEncoder.c \
sdcardio/SDCard.c \
sdcardio/__init__.c \
hardwarekey/HardwareKey.c \
sharpdisplay/SharpMemoryFramebuffer.c \
sharpdisplay/__init__.c \
socket/__init__.c \
Expand Down
5 changes: 5 additions & 0 deletions py/circuitpy_mpconfig.mk
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ CFLAGS += -DCIRCUITPY_SDCARDIO=$(CIRCUITPY_SDCARDIO)
CIRCUITPY_SDIOIO ?= 0
CFLAGS += -DCIRCUITPY_SDIOIO=$(CIRCUITPY_SDIOIO)

# hardwarekey: cryptographic operations with hardware-held, non-readable keys.
# Off unless a port provides a common-hal/hardwarekey backend.
CIRCUITPY_HARDWAREKEY ?= 0
CFLAGS += -DCIRCUITPY_HARDWAREKEY=$(CIRCUITPY_HARDWAREKEY)

CIRCUITPY_BLE_SERIAL_SERVICE ?= 0
CFLAGS += -DCIRCUITPY_BLE_SERIAL_SERVICE=$(CIRCUITPY_BLE_SERIAL_SERVICE)

Expand Down
16 changes: 14 additions & 2 deletions shared-bindings/board/__init__.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@

#include "shared-bindings/microcontroller/Pin.h" // for the pin definitions

// A port can inject board.EFUSE_KEY* (or other hardware key) entries into
// every board's globals table by defining CIRCUITPY_BOARD_HARDWARE_KEYS (a
// comma-terminated list of { MP_ROM_QSTR(...), MP_ROM_PTR(...) } pairs).
#if CIRCUITPY_HARDWAREKEY
#include "common-hal/hardwarekey/board.h"
#endif
#ifndef CIRCUITPY_BOARD_HARDWARE_KEYS
#define CIRCUITPY_BOARD_HARDWARE_KEYS
#endif

#if CIRCUITPY_MUTABLE_BOARD
extern mp_obj_dict_t board_module_globals;
#else
Expand Down Expand Up @@ -41,8 +51,10 @@ MP_DECLARE_CONST_FUN_OBJ_0(board_uart_obj);

#define CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS \
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_board) }, \
{ MP_ROM_QSTR(MP_QSTR_board_id), MP_ROM_PTR(&board_module_id_obj) },
{ MP_ROM_QSTR(MP_QSTR_board_id), MP_ROM_PTR(&board_module_id_obj) }, \
CIRCUITPY_BOARD_HARDWARE_KEYS

#define CIRCUITPYTHON_MUTABLE_BOARD_DICT_STANDARD_ITEMS \
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_board) }, \
{ MP_ROM_QSTR(MP_QSTR_board_id), MP_OBJ_FROM_PTR(&board_module_id_obj) },
{ MP_ROM_QSTR(MP_QSTR_board_id), MP_OBJ_FROM_PTR(&board_module_id_obj) }, \
CIRCUITPY_BOARD_HARDWARE_KEYS
Loading
Loading