From 955390c35818839c51c485c9b507aa5e6cfbac0d Mon Sep 17 00:00:00 2001 From: Jonathan Berrisch Date: Fri, 17 Apr 2026 18:56:01 +0200 Subject: [PATCH] Without atecc608b --- dependencies.lock | 15 +---- main/CMakeLists.txt | 4 +- main/efuse_ecdsa.c | 125 +++++++++++++++++++++++++++++++++++++++++ main/efuse_ecdsa.h | 48 ++++++++++++++++ main/idf_component.yml | 4 +- main/main.c | 29 ++++++++-- main/ssh_client.c | 49 ++++++++-------- main/ssh_client.h | 7 +-- 8 files changed, 229 insertions(+), 52 deletions(-) create mode 100644 main/efuse_ecdsa.c create mode 100644 main/efuse_ecdsa.h diff --git a/dependencies.lock b/dependencies.lock index 4d0aa9e..2f2495c 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,14 +1,4 @@ dependencies: - esp-cryptoauthlib: - component_hash: 8d24c37df1e906f9bbee9a869ca86f263ab135179d1c2ba6062448269437b192 - dependencies: - - name: idf - version: '>=4.3' - source: - git: https://github.com/espressif/esp-cryptoauthlib.git - path: . - type: git - version: d9792119ebaec0c54839e6605acd3f11dd937205 idf: source: type: idf @@ -22,9 +12,8 @@ dependencies: type: git version: 378f0bd47900bffacbf29cac328c6e9b5391c886 direct_dependencies: -- esp-cryptoauthlib - idf - libssh2_esp -manifest_hash: a6766e71931c845fac37dab1b735cded43d414aa83e5ce0443ba4285e1980180 -target: esp32c6 +manifest_hash: 5bd8c5fcff9e9561e27fb051d1db66506755c66524ba9099cb66598d979940e3 +target: esp32c5 version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 1c39a2b..74dc6f1 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register(SRCS "main.c" "wifi.c" - "atecc608a.c" + "efuse_ecdsa.c" "ssh_client.c" INCLUDE_DIRS "." - REQUIRES mbedtls esp-cryptoauthlib esp_wifi nvs_flash driver) + REQUIRES mbedtls efuse esp_wifi nvs_flash driver esp_hw_support) diff --git a/main/efuse_ecdsa.c b/main/efuse_ecdsa.c new file mode 100644 index 0000000..5e8f7b4 --- /dev/null +++ b/main/efuse_ecdsa.c @@ -0,0 +1,125 @@ +#include "efuse_ecdsa.h" + +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include + +#include "ecdsa/ecdsa_alt.h" + +static const char *TAG = "efuse_ecdsa"; + +/* We use EFUSE_BLK_KEY1 (block 5) for the ECDSA key, leaving KEY0 free + * for secure boot / TEE keys. */ +#define ECDSA_EFUSE_BLOCK EFUSE_BLK_KEY1 + +/* ------------------------------------------------------------------------- + * Public API + * ---------------------------------------------------------------------- */ + +bool efuse_ecdsa_key_provisioned(void) +{ + esp_efuse_block_t blk; + return esp_efuse_find_purpose(ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY, &blk); +} + +bool efuse_ecdsa_provision_key(const uint8_t key[32]) +{ + if (efuse_ecdsa_key_provisioned()) { + ESP_LOGW(TAG, "ECDSA key already provisioned – skipping"); + return true; + } + + if (!esp_efuse_key_block_unused(ECDSA_EFUSE_BLOCK)) { + ESP_LOGE(TAG, "eFuse key block %d is already in use", ECDSA_EFUSE_BLOCK); + return false; + } + + esp_err_t err = esp_efuse_write_key(ECDSA_EFUSE_BLOCK, + ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY, + key, 32); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_efuse_write_key failed: %s", esp_err_to_name(err)); + return false; + } + + ESP_LOGI(TAG, "ECDSA P-256 key burned into eFuse block %d", ECDSA_EFUSE_BLOCK); + return true; +} + +bool efuse_ecdsa_get_pubkey(uint8_t pub_x[32], uint8_t pub_y[32]) +{ + esp_efuse_block_t blk; + if (!esp_efuse_find_purpose(ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY, &blk)) { + ESP_LOGE(TAG, "No eFuse block with ECDSA_KEY purpose found"); + return false; + } + + esp_ecdsa_pk_conf_t conf = { + .grp_id = MBEDTLS_ECP_DP_SECP256R1, + .efuse_block = blk, + .load_pubkey = true, + }; + + mbedtls_pk_context pk; + if (esp_ecdsa_set_pk_context(&pk, &conf) != 0) { + ESP_LOGE(TAG, "esp_ecdsa_set_pk_context failed"); + return false; + } + + mbedtls_ecp_keypair *kp = mbedtls_pk_ec(pk); + int ret = 0; + ret |= mbedtls_mpi_write_binary(&kp->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), pub_x, 32); + ret |= mbedtls_mpi_write_binary(&kp->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), pub_y, 32); + + mbedtls_pk_free(&pk); + + if (ret != 0) { + ESP_LOGE(TAG, "Failed to export public key coordinates"); + return false; + } + return true; +} + +bool efuse_ecdsa_sign(const uint8_t digest[32], + uint8_t r_out[32], uint8_t s_out[32]) +{ + esp_efuse_block_t blk; + if (!esp_efuse_find_purpose(ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY, &blk)) { + ESP_LOGE(TAG, "No eFuse block with ECDSA_KEY purpose found"); + return false; + } + + mbedtls_ecdsa_context ctx; + mbedtls_ecdsa_init(&ctx); + mbedtls_ecp_group_load(&ctx.MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP256R1); + + mbedtls_mpi key_mpi; + esp_ecdsa_privkey_load_mpi(&key_mpi, blk); + + mbedtls_mpi r, s; + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + int ret = mbedtls_ecdsa_sign(&ctx.MBEDTLS_PRIVATE(grp), + &r, &s, &key_mpi, + digest, 32, NULL, NULL); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ecdsa_sign failed: -0x%04X", (unsigned)-ret); + goto out; + } + + ret |= mbedtls_mpi_write_binary(&r, r_out, 32); + ret |= mbedtls_mpi_write_binary(&s, s_out, 32); + +out: + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + mbedtls_mpi_free(&key_mpi); + mbedtls_ecdsa_free(&ctx); + return ret == 0; +} diff --git a/main/efuse_ecdsa.h b/main/efuse_ecdsa.h new file mode 100644 index 0000000..3a69ce9 --- /dev/null +++ b/main/efuse_ecdsa.h @@ -0,0 +1,48 @@ +#ifndef EFUSE_ECDSA_H +#define EFUSE_ECDSA_H + +#include +#include + +/** + * Check whether an ECDSA P-256 key has already been provisioned in eFuse. + */ +bool efuse_ecdsa_key_provisioned(void); + +/** + * Write a 32-byte ECDSA P-256 private key into the eFuse key block. + * The key must be in **little-endian** byte order (as required by the + * ESP32-C5 ECDSA peripheral). + * + * This is a ONE-TIME, IRREVERSIBLE operation. After burning, the key + * block is read-protected so software can never read the private key + * back — only the hardware ECDSA peripheral can use it. + * + * @param key 32 bytes of private-key material (little-endian). + * @return true on success. + */ +bool efuse_ecdsa_provision_key(const uint8_t key[32]); + +/** + * Export the public key that corresponds to the eFuse private key. + * Uses the hardware ECDSA peripheral to derive Q = d·G without ever + * exposing the private key to software. + * + * @param pub_x Output buffer for the X coordinate (32 bytes, big-endian). + * @param pub_y Output buffer for the Y coordinate (32 bytes, big-endian). + * @return true on success. + */ +bool efuse_ecdsa_get_pubkey(uint8_t pub_x[32], uint8_t pub_y[32]); + +/** + * Sign a SHA-256 digest with the eFuse ECDSA key. + * + * @param digest 32-byte SHA-256 hash to sign. + * @param r_out Output: R component of the signature (32 bytes, big-endian). + * @param s_out Output: S component of the signature (32 bytes, big-endian). + * @return true on success. + */ +bool efuse_ecdsa_sign(const uint8_t digest[32], + uint8_t r_out[32], uint8_t s_out[32]); + +#endif /* EFUSE_ECDSA_H */ diff --git a/main/idf_component.yml b/main/idf_component.yml index 5fb942f..3f6f3e0 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -1,8 +1,6 @@ --- dependencies: idf: - version: ">=4.1.0" - esp-cryptoauthlib: - git: https://github.com/espressif/esp-cryptoauthlib.git + version: ">=5.5.0" libssh2_esp: git: https://github.com/skuodi/libssh2_esp.git diff --git a/main/main.c b/main/main.c index 5501103..6bb13d4 100644 --- a/main/main.c +++ b/main/main.c @@ -2,9 +2,15 @@ * keypitecc – door controller * * Hardware: + * ESP32-C5 with ECDSA key stored in eFuse * GPIO 9 – Boot button (active-low, press = GND) * GPIO 15 – User LED (active-high, 1 = on) * + * Provisioning: + * On first boot, if no ECDSA key is present in eFuse, a random P-256 + * private key is generated and burned. The derived public key is + * printed to the console each boot in SSH authorized_keys format. + * * Button state machine (5-second window): * IDLE * └─ press ──► PENDING_OPEN (slow blink, will run SSH open command) @@ -17,13 +23,14 @@ * WiFi disconnected → LED OFF */ -#include "atecc608a.h" +#include "efuse_ecdsa.h" #include "ssh_client.h" #include "wifi.h" #include "sdkconfig.h" #include #include +#include #include #include #include @@ -251,12 +258,22 @@ void app_main(void) } ESP_ERROR_CHECK(ret); - /* ATECC608B */ - if (!atecc608B_init()) { - ESP_LOGW(TAG, "ATECC608B init failed – SSH authentication will not work"); + /* eFuse ECDSA key provisioning / public key export */ + if (!efuse_ecdsa_key_provisioned()) { + ESP_LOGW(TAG, "No ECDSA key in eFuse – generating and burning a new key"); + uint8_t privkey[32]; + esp_fill_random(privkey, sizeof(privkey)); + if (!efuse_ecdsa_provision_key(privkey)) { + ESP_LOGE(TAG, "Key provisioning FAILED – SSH authentication will not work"); + } + /* Wipe the RAM copy immediately */ + memset(privkey, 0, sizeof(privkey)); + } + + if (efuse_ecdsa_key_provisioned()) { + ssh_print_public_key(); } else { - atecc608B_print_config(); - ssh_print_public_key(); /* print key for authorized_keys setup */ + ESP_LOGW(TAG, "ECDSA key not available – SSH authentication will not work"); } /* WiFi */ diff --git a/main/ssh_client.c b/main/ssh_client.c index a86218c..781e07e 100644 --- a/main/ssh_client.c +++ b/main/ssh_client.c @@ -1,6 +1,6 @@ #include "ssh_client.h" -#include "cryptoauthlib.h" +#include "efuse_ecdsa.h" #include "sdkconfig.h" #include #include @@ -67,10 +67,12 @@ static uint32_t write_mpint(uint8_t *buf, const uint8_t *val, uint32_t size) * [uint32 8] "nistp256" * [uint32 65] 0x04 || X(32) || Y(32) * - * @param atecc_pubkey 64-byte raw public key (X||Y) from ATECC608B. - * @param out_blob Must point to a buffer of at least 104 bytes. + * @param pub_x 32-byte X coordinate (big-endian). + * @param pub_y 32-byte Y coordinate (big-endian). + * @param out_blob Must point to a buffer of at least 104 bytes. */ -static void build_pubkey_blob(const uint8_t *atecc_pubkey, uint8_t *out_blob) +static void build_pubkey_blob(const uint8_t *pub_x, const uint8_t *pub_y, + uint8_t *out_blob) { uint32_t off = 0; @@ -87,12 +89,13 @@ static void build_pubkey_blob(const uint8_t *atecc_pubkey, uint8_t *out_blob) /* Uncompressed EC point: 0x04 || X || Y */ write_be32(&out_blob[off], 65); off += 4; out_blob[off++] = 0x04; - memcpy(&out_blob[off], atecc_pubkey, 64); - /* off += 64 — total = 104 */ + memcpy(&out_blob[off], pub_x, 32); off += 32; + memcpy(&out_blob[off], pub_y, 32); + /* off += 32 — total = 104 */ } /* ------------------------------------------------------------------------- - * libssh2 signing callback (ATECC608B signs the hash) + * libssh2 signing callback (eFuse ECDSA signs the hash) * ---------------------------------------------------------------------- */ static int sign_callback(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, @@ -103,14 +106,14 @@ static int sign_callback(LIBSSH2_SESSION *session, (void)abstract; uint8_t digest[32]; - uint8_t raw_sig[64]; + uint8_t r_raw[32], s_raw[32]; /* Hash the challenge data */ mbedtls_sha256(data, data_len, digest, 0); - /* Sign with the ATECC608B hardware key in slot 0 */ - if (atcab_sign(0, digest, raw_sig) != ATCA_SUCCESS) { - ESP_LOGE(TAG, "ATECC608B signing failed!"); + /* Sign with the eFuse hardware ECDSA key */ + if (!efuse_ecdsa_sign(digest, r_raw, s_raw)) { + ESP_LOGE(TAG, "eFuse ECDSA signing failed!"); return -1; } @@ -119,8 +122,8 @@ static int sign_callback(LIBSSH2_SESSION *session, if (!buf) return -1; uint32_t off = 0; - off += write_mpint(&buf[off], &raw_sig[0], 32); /* R */ - off += write_mpint(&buf[off], &raw_sig[32], 32); /* S */ + off += write_mpint(&buf[off], r_raw, 32); /* R */ + off += write_mpint(&buf[off], s_raw, 32); /* S */ *sig = buf; *sig_len = off; @@ -132,15 +135,14 @@ static int sign_callback(LIBSSH2_SESSION *session, * ---------------------------------------------------------------------- */ void ssh_print_public_key(void) { - uint8_t raw_key[64]; - ATCA_STATUS st = atcab_get_pubkey(0, raw_key); - if (st != ATCA_SUCCESS) { - ESP_LOGE(TAG, "atcab_get_pubkey failed: 0x%02X", st); + uint8_t pub_x[32], pub_y[32]; + if (!efuse_ecdsa_get_pubkey(pub_x, pub_y)) { + ESP_LOGE(TAG, "Failed to export public key from eFuse"); return; } uint8_t blob[104]; - build_pubkey_blob(raw_key, blob); + build_pubkey_blob(pub_x, pub_y, blob); size_t b64_len = 0; mbedtls_base64_encode(NULL, 0, &b64_len, blob, sizeof(blob)); @@ -165,14 +167,13 @@ bool ssh_execute_command(const char *cmd) if (!cmd) return false; /* --- Read public key blob ------------------------------------------ */ - uint8_t raw_key[64]; - ATCA_STATUS st = atcab_get_pubkey(0, raw_key); - if (st != ATCA_SUCCESS) { - ESP_LOGE(TAG, "atcab_get_pubkey failed: 0x%02X", st); + uint8_t pub_x[32], pub_y[32]; + if (!efuse_ecdsa_get_pubkey(pub_x, pub_y)) { + ESP_LOGE(TAG, "Failed to export public key from eFuse"); return false; } uint8_t pubkey_blob[104]; - build_pubkey_blob(raw_key, pubkey_blob); + build_pubkey_blob(pub_x, pub_y, pubkey_blob); /* --- TCP connect ------------------------------------------------------- */ int rc; @@ -227,7 +228,7 @@ bool ssh_execute_command(const char *cmd) } ESP_LOGI(TAG, "SSH handshake OK"); - /* --- Authenticate with ATECC608B hardware key ------------------------- */ + /* --- Authenticate with eFuse ECDSA hardware key ---------------------- */ void *abstract = NULL; rc = libssh2_userauth_publickey(session, CONFIG_SSH_USERNAME, pubkey_blob, sizeof(pubkey_blob), diff --git a/main/ssh_client.h b/main/ssh_client.h index 9b1bfbb..2ea14a5 100644 --- a/main/ssh_client.h +++ b/main/ssh_client.h @@ -4,9 +4,8 @@ #include /** - * Read the ATECC608B public key, format it as an SSH authorized_keys entry, - * and print it to the console. Call this once at boot so the key can be - * added to the server's authorized_keys file. + * Derive the public key from the eFuse ECDSA private key, format it as an + * SSH authorized_keys entry, and print it to the console. * * Output format: * ecdsa-sha2-nistp256 keypitecc @@ -15,7 +14,7 @@ void ssh_print_public_key(void); /** * Open a TCP connection to the configured SSH server, authenticate using the - * ATECC608B hardware key, execute @p cmd, log the output, and disconnect. + * eFuse ECDSA hardware key, execute @p cmd, log the output, and disconnect. * * @param cmd Shell command string to run on the remote host. * @return true on success, false on any error.