diff --git a/main/TangServer.h b/main/TangServer.h index 5a31378..4b5660e 100644 --- a/main/TangServer.h +++ b/main/TangServer.h @@ -17,12 +17,6 @@ static const char *TAG = "TangServer"; // Include core components #include "atecc608a.h" -#include "crypto.h" -#include "encoding.h" -#include "provision.h" -#include "provision_handlers.h" -#include "tang_handlers.h" -#include "tang_storage.h" #include "zk_auth.h" #include "zk_handlers.h" @@ -33,7 +27,6 @@ const char *wifi_password = CONFIG_WIFI_PASSWORD; // --- Global State --- bool unlocked = false; // Start inactive until provisioned and authenticated httpd_handle_t server_http = NULL; -TangKeyStore keystore; ZKAuth zk_auth; // Zero-Knowledge Authentication // WiFi event group @@ -103,19 +96,6 @@ void setup_wifi() { // --- Initial Setup --- bool perform_initial_setup() { - if (!P256::generate_keypair(keystore.exc_pub, keystore.exc_priv)) { - ESP_LOGE(TAG, "ERROR: Failed to generate exchange key"); - return false; - } - - // Save Tang keys directly (no encryption in prototype) - if (!keystore.save_tang_keys()) { - ESP_LOGE(TAG, "ERROR: Failed to save Tang keys"); - return false; - } - - ESP_LOGI(TAG, "Configuration saved to NVS"); - ESP_LOGI(TAG, "======================================================="); ESP_LOGI(TAG, "Setup complete! Device is ready to use"); ESP_LOGI(TAG, "NOTE: Exchange key stored unencrypted for prototyping"); @@ -134,44 +114,7 @@ httpd_handle_t setup_http_server() { httpd_handle_t server = NULL; if (httpd_start(&server, &config) == ESP_OK) { - register_provision_handlers(server); - register_zk_handlers(server); - - // Register Tang protocol handlers - httpd_uri_t adv_uri = {.uri = "/adv", - .method = HTTP_GET, - .handler = handle_adv, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &adv_uri); - - httpd_uri_t adv_uri_slash = {.uri = "/adv/", - .method = HTTP_GET, - .handler = handle_adv, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &adv_uri_slash); - - httpd_uri_t rec_uri = {.uri = "/rec", - .method = HTTP_POST, - .handler = handle_rec, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &rec_uri); - - httpd_uri_t config_uri = {.uri = "/config", - .method = HTTP_GET, - .handler = handle_config, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &config_uri); - - httpd_uri_t reboot_uri = {.uri = "/reboot", - .method = HTTP_GET, - .handler = handle_reboot, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &reboot_uri); - - // Register custom error handler for 404 - httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, handle_not_found); - ESP_LOGI(TAG, "HTTP server listening on port 80"); } else { ESP_LOGE(TAG, "Failed to start HTTP server"); @@ -182,7 +125,6 @@ httpd_handle_t setup_http_server() { // --- Main Setup --- void setup() { - ESP_LOGI(TAG, "\n\nESP32 Tang Server Starting..."); // Initialize NVS (required before any storage operations) esp_err_t ret = nvs_flash_init(); @@ -194,26 +136,12 @@ void setup() { ESP_ERROR_CHECK(ret); ESP_LOGI(TAG, "NVS initialized"); - // Initialize ATECC608A if (atecc608B_init()) { atecc608B_print_config(); } else { ESP_LOGW(TAG, "WARNING: ATECC608A initialization failed"); } - // Load or initialize configuration - if (keystore.is_configured()) { - ESP_LOGI(TAG, "Found existing configuration"); - // Auto-load Tang keys on startup (no activation needed in prototype) - if (keystore.load_tang_keys()) { - ESP_LOGI(TAG, "Loaded Tang keys - server ready"); - } else { - ESP_LOGW(TAG, "Failed to load Tang keys"); - } - } else { - perform_initial_setup(); - } - // Initialize Zero-Knowledge Authentication ESP_LOGI(TAG, "Initializing Zero-Knowledge Authentication..."); if (zk_auth.init()) { diff --git a/main/crypto.h b/main/crypto.h deleted file mode 100644 index 9c576c1..0000000 --- a/main/crypto.h +++ /dev/null @@ -1,306 +0,0 @@ -#ifndef CRYPTO_H -#define CRYPTO_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *TAG_CRYPTO = "crypto"; - -// --- Constants --- - -// P-256 uses 256 bits = 32 bytes per coordinate -const int P256_PRIVATE_KEY_SIZE = 32; // Scalar value -const int P256_PUBLIC_KEY_SIZE = 64; // Uncompressed point (x + y) -const int P256_COORDINATE_SIZE = 32; // Single coordinate (x or y) -const int GCM_TAG_SIZE = 16; -const int SALT_SIZE = 16; -const int PBKDF2_ITERATIONS = 1000; - -// --- RNG Management --- -class RNG { -private: - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - bool initialized; - -public: - RNG() : initialized(false) {} - - ~RNG() { cleanup(); } - - int init() { - if (initialized) - return 0; - - mbedtls_entropy_init(&entropy); - mbedtls_ctr_drbg_init(&ctr_drbg); - - const char *pers = "esp32_tang_server"; - int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - (const unsigned char *)pers, strlen(pers)); - if (ret != 0) - return ret; - - initialized = true; - return 0; - } - - void cleanup() { - if (initialized) { - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); - initialized = false; - } - } - - mbedtls_ctr_drbg_context *context() { return &ctr_drbg; } -}; - -// Global RNG instance -static RNG global_rng; - -// --- P-256 EC Operations --- -class P256 { -public: - static bool generate_keypair(uint8_t *pub_key, uint8_t *priv_key) { - int ret = global_rng.init(); - if (ret != 0) { - ESP_LOGE(TAG_CRYPTO, "RNG init failed: -0x%04x", -ret); - return false; - } - - mbedtls_ecp_group grp; - mbedtls_ecp_point Q; - mbedtls_mpi d; - - mbedtls_ecp_group_init(&grp); - mbedtls_ecp_point_init(&Q); - mbedtls_mpi_init(&d); - - ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); - if (ret != 0) { - ESP_LOGE(TAG_CRYPTO, "ECP group load failed: -0x%04x", -ret); - } else { - ret = mbedtls_ecp_gen_keypair(&grp, &d, &Q, mbedtls_ctr_drbg_random, - global_rng.context()); - if (ret != 0) { - ESP_LOGE(TAG_CRYPTO, "ECP keypair gen failed: -0x%04x", -ret); - } - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&d, priv_key, P256_COORDINATE_SIZE); - if (ret != 0) { - ESP_LOGE(TAG_CRYPTO, "Write private key failed: -0x%04x", -ret); - } - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(X), pub_key, - P256_COORDINATE_SIZE); - if (ret != 0) { - ESP_LOGE(TAG_CRYPTO, "Write pub key X failed: -0x%04x", -ret); - } - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(Y), - pub_key + P256_COORDINATE_SIZE, - P256_COORDINATE_SIZE); - if (ret != 0) { - ESP_LOGE(TAG_CRYPTO, "Write pub key Y failed: -0x%04x", -ret); - } - } - - mbedtls_ecp_group_free(&grp); - mbedtls_ecp_point_free(&Q); - mbedtls_mpi_free(&d); - - return (ret == 0); - } - - static bool compute_public_key(const uint8_t *priv_key, uint8_t *pub_key) { - if (global_rng.init() != 0) - return false; - - mbedtls_ecp_group grp; - mbedtls_ecp_point Q; - mbedtls_mpi d; - - mbedtls_ecp_group_init(&grp); - mbedtls_ecp_point_init(&Q); - mbedtls_mpi_init(&d); - - int ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); - if (ret == 0) { - ret = mbedtls_mpi_read_binary(&d, priv_key, P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_ecp_mul(&grp, &Q, &d, &grp.G, mbedtls_ctr_drbg_random, - global_rng.context()); - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(X), pub_key, - P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(Y), - pub_key + P256_COORDINATE_SIZE, - P256_COORDINATE_SIZE); - } - - mbedtls_ecp_group_free(&grp); - mbedtls_ecp_point_free(&Q); - mbedtls_mpi_free(&d); - - return (ret == 0); - } - - static bool ecdh_compute_shared_point(const uint8_t *peer_pub_key, - const uint8_t *priv_key, - uint8_t *shared_point, - bool full_point = true) { - if (global_rng.init() != 0) - return false; - - mbedtls_ecp_group grp; - mbedtls_ecp_point Q; - mbedtls_mpi d; - - mbedtls_ecp_group_init(&grp); - mbedtls_ecp_point_init(&Q); - mbedtls_mpi_init(&d); - - int ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); - if (ret == 0) { - ret = mbedtls_mpi_read_binary(&d, priv_key, P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(X), peer_pub_key, - P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(Y), - peer_pub_key + P256_COORDINATE_SIZE, - P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_mpi_lset(&Q.MBEDTLS_PRIVATE(Z), 1); - } - if (ret == 0) { - ret = mbedtls_ecp_check_pubkey(&grp, &Q); - } - if (ret == 0) { - ret = mbedtls_ecp_mul(&grp, &Q, &d, &Q, mbedtls_ctr_drbg_random, - global_rng.context()); - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(X), shared_point, - P256_COORDINATE_SIZE); - } - if (ret == 0 && full_point) { - ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(Y), - shared_point + P256_COORDINATE_SIZE, - P256_COORDINATE_SIZE); - } - - mbedtls_ecp_group_free(&grp); - mbedtls_ecp_point_free(&Q); - mbedtls_mpi_free(&d); - - return (ret == 0); - } - - static bool sign(const uint8_t *hash, size_t hash_len, - const uint8_t *priv_key, uint8_t *signature) { - if (global_rng.init() != 0) - return false; - - mbedtls_ecp_group grp; - mbedtls_mpi d, r, s; - - mbedtls_ecp_group_init(&grp); - mbedtls_mpi_init(&d); - mbedtls_mpi_init(&r); - mbedtls_mpi_init(&s); - - int ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); - if (ret == 0) { - ret = mbedtls_mpi_read_binary(&d, priv_key, P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_ecdsa_sign(&grp, &r, &s, &d, hash, hash_len, - mbedtls_ctr_drbg_random, global_rng.context()); - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&r, signature, P256_COORDINATE_SIZE); - } - if (ret == 0) { - ret = mbedtls_mpi_write_binary(&s, signature + P256_COORDINATE_SIZE, - P256_COORDINATE_SIZE); - } - - mbedtls_ecp_group_free(&grp); - mbedtls_mpi_free(&d); - mbedtls_mpi_free(&r); - mbedtls_mpi_free(&s); - - return (ret == 0); - } -}; - -// --- AES-GCM Operations --- -class AESGCM { -public: - static bool encrypt(uint8_t *plaintext, size_t len, const uint8_t *key, - size_t key_len, const uint8_t *iv, size_t iv_len, - const uint8_t *aad, size_t aad_len, uint8_t *tag) { - mbedtls_gcm_context ctx; - mbedtls_gcm_init(&ctx); - - int ret = mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, key_len * 8); - if (ret == 0) { - ret = mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_GCM_ENCRYPT, len, iv, - iv_len, aad, aad_len, plaintext, - plaintext, GCM_TAG_SIZE, tag); - } - - mbedtls_gcm_free(&ctx); - return (ret == 0); - } - - static bool decrypt(uint8_t *ciphertext, size_t len, const uint8_t *key, - size_t key_len, const uint8_t *iv, size_t iv_len, - const uint8_t *aad, size_t aad_len, const uint8_t *tag) { - mbedtls_gcm_context ctx; - mbedtls_gcm_init(&ctx); - - int ret = mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, key_len * 8); - if (ret == 0) { - ret = mbedtls_gcm_auth_decrypt(&ctx, len, iv, iv_len, aad, aad_len, tag, - GCM_TAG_SIZE, ciphertext, ciphertext); - } - - mbedtls_gcm_free(&ctx); - return (ret == 0); - } -}; - -// --- PBKDF2 --- -class PBKDF2 { -public: - static int derive_key(uint8_t *output, size_t key_len, const char *password, - const uint8_t *salt, size_t salt_len, int iterations) { - return mbedtls_pkcs5_pbkdf2_hmac_ext( - MBEDTLS_MD_SHA256, (const uint8_t *)password, strlen(password), salt, - salt_len, iterations, key_len, output); - } -}; - -#endif // CRYPTO_H diff --git a/main/encoding.h b/main/encoding.h deleted file mode 100644 index e08c491..0000000 --- a/main/encoding.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef ENCODING_H -#define ENCODING_H - -#include -#include -#include -#include - -static bool b64url_encode_buf(const uint8_t *data, size_t data_len, - char *out_buf, size_t out_max_len) { - size_t b64_len = out_max_len; - ATCA_STATUS status = atcab_base64encode_(data, data_len, out_buf, &b64_len, - atcab_b64rules_urlsafe()); - - return (status == ATCA_SUCCESS && b64_len < out_max_len); -} - -static bool b64url_decode_buf(const char *in_str, uint8_t *out_buf, - size_t expected_len) { - if (!in_str || !out_buf) - return false; - - size_t out_len = expected_len; - ATCA_STATUS status = atcab_base64decode_(in_str, strlen(in_str), out_buf, - &out_len, atcab_b64rules_urlsafe()); - - return (status == ATCA_SUCCESS && out_len == expected_len); -} - -#endif // ENCODING_H \ No newline at end of file diff --git a/main/provision.h b/main/provision.h deleted file mode 100644 index 19229a7..0000000 --- a/main/provision.h +++ /dev/null @@ -1,357 +0,0 @@ -#ifndef PROVISION_H -#define PROVISION_H - -#include "cryptoauthlib.h" -#include -#include -#include - -static const char *TAG_PROVISION = "provision"; - -// Hardcoded HMAC key for prototyping (32 bytes) -// WARNING: This is for prototyping only! In production, this should be securely -// generated -static const uint8_t IO_KEY[32] = { - 0x2c, 0x43, 0x34, 0x9c, 0x4c, 0xe6, 0x70, 0xf3, 0xcb, 0x10, 0xef, - 0xcc, 0x56, 0xf0, 0xd0, 0xc4, 0x03, 0x2c, 0x45, 0x9f, 0xf3, 0xcb, - 0x29, 0x27, 0x22, 0x8e, 0x93, 0x3c, 0xfe, 0x6e, 0x87, 0xed}; - -static const uint8_t HMAC_KEY[32] = { - 0x24, 0x01, 0x2a, 0xf7, 0x3e, 0x62, 0x7a, 0x5e, 0x5e, 0xdc, 0xf0, - 0xce, 0xd6, 0xe5, 0x32, 0x20, 0x56, 0xca, 0x29, 0xd1, 0x52, 0xf8, - 0x17, 0x23, 0x06, 0x75, 0x4f, 0x1d, 0xb9, 0x85, 0x51, 0x5e}; - -// ATECC608B Configuration values -// SlotConfig (Bytes 20-51): 32 bytes for 16 slots (2 bytes each) -static const uint8_t ATECC_SLOT_CONFIG[32] = { - 0x81, 0x00, 0x81, 0x20, 0x81, 0x20, 0x81, 0x20, 0x84, 0x20, 0x84, - 0x20, 0xc6, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x9a, 0x4a, - 0x84, 0x20, 0x84, 0x20, 0x9a, 0x4a, 0x00, 0x00, 0x00, 0x00}; - -// KeyConfig (Bytes 96-127): 32 bytes for 16 slots (2 bytes each) -static const uint8_t ATECC_KEY_CONFIG[32] = { - 0x33, 0x00, 0x13, 0x00, 0xd3, 0x09, 0x93, 0x0a, 0x53, 0x00, 0x53, - 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x5c, 0x00, - 0x53, 0x00, 0x53, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// Byte 69: Persistent Latch enabled with Keyslot 10 -static const uint8_t ATECC_LAST_KEY_USE_10 = 0x8a; - -// Bytes 90-91: IO Protection optional and Key is Slot 6 -static const uint8_t ATECC_CHIP_OPTIONS[2] = {0x02, 0x60}; - -/** - * Check if ATECC608B config zone is locked - * Returns: true if locked, false if unlocked - */ -bool is_atecc608b_config_locked() { - extern uint8_t g_atecc_config_data[128]; - extern bool g_atecc_config_valid; - - // Read config if not already read - if (!g_atecc_config_valid) { - extern bool atecc608B_read_config(); - if (!atecc608B_read_config()) { - ESP_LOGE(TAG_PROVISION, "Failed to read ATECC608B config"); - return false; // Assume unlocked if we can't read - } - } - - // Byte 87: LockConfig (Config Zone Lock) - // 0x00 = LOCKED, any other value = UNLOCKED - bool locked = (g_atecc_config_data[87] == 0x00); - ESP_LOGI(TAG_PROVISION, "ATECC608B Config Zone: %s (0x%02X)", - locked ? "LOCKED" : "UNLOCKED", g_atecc_config_data[87]); - return locked; -} - -/** - * Check if ATECC608B data zone is locked - * Returns: true if locked, false if unlocked - */ -bool is_atecc608b_data_locked() { - extern uint8_t g_atecc_config_data[128]; - extern bool g_atecc_config_valid; - - // Read config if not already read - if (!g_atecc_config_valid) { - extern bool atecc608B_read_config(); - if (!atecc608B_read_config()) { - ESP_LOGE(TAG_PROVISION, "Failed to read ATECC608B config"); - return false; // Assume unlocked if we can't read - } - } - - // Byte 86: LockValue (Data/OTP Zone Lock) - // 0x00 = LOCKED, any other value = UNLOCKED - bool locked = (g_atecc_config_data[86] == 0x00); - ESP_LOGI(TAG_PROVISION, "ATECC608B Data Zone: %s (0x%02X)", - locked ? "LOCKED" : "UNLOCKED", g_atecc_config_data[86]); - return locked; -} - -/** - * Check if ESP32-C6 efuse BLOCK_KEY5 is already used - * Returns: true if used, false if not used - */ -bool is_efuse_key5_used() { - // Read the key purpose for BLOCK_KEY5 - esp_efuse_purpose_t purpose; - esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_KEY_PURPOSE_5, &purpose, - sizeof(purpose) * 8); - - if (err != ESP_OK) { - ESP_LOGE(TAG_PROVISION, "Failed to read KEY_PURPOSE_5: %s", - esp_err_to_name(err)); - return false; - } - - ESP_LOGI(TAG_PROVISION, "EFUSE KEY_PURPOSE_5: %d (0=NONE/unused)", purpose); - - // Purpose 0 (ESP_EFUSE_KEY_PURPOSE_USER) typically means unused/default - // Any non-zero value means it's configured for something - return (purpose != ESP_EFUSE_KEY_PURPOSE_USER); -} - -/** - * Provision the ATECC608B configuration and lock the config zone - * Returns: true on success, false on failure - */ -bool provision_atecc608b_config() { - ESP_LOGI(TAG_PROVISION, - "=== Starting ATECC608B Configuration Provisioning ==="); - - // Check if config zone is already locked - if (is_atecc608b_config_locked()) { - ESP_LOGW(TAG_PROVISION, "Config zone is already locked!"); - return false; - } - - // Wake the device - ATCA_STATUS status = atcab_wakeup(); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed to wake ATECC608B: 0x%02X", status); - return false; - } - - // 1. Ensure we have the current 128-byte config read into memory - extern uint8_t g_atecc_config_data[128]; - extern bool g_atecc_config_valid; - - if (!g_atecc_config_valid) { - ESP_LOGI(TAG_PROVISION, "Reading full config zone into memory..."); - status = atcab_read_config_zone(g_atecc_config_data); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed to read config zone: 0x%02X", status); - return false; - } - g_atecc_config_valid = true; - } - - // 2. Modify the in-memory array with our desired settings - ESP_LOGI(TAG_PROVISION, "Applying configuration changes to memory..."); - memcpy(&g_atecc_config_data[20], ATECC_SLOT_CONFIG, 32); // SlotConfig - g_atecc_config_data[69] = ATECC_LAST_KEY_USE_10; // Persistent Latch - memcpy(&g_atecc_config_data[90], ATECC_CHIP_OPTIONS, 2); // ChipOptions - memcpy(&g_atecc_config_data[96], ATECC_KEY_CONFIG, 32); // KeyConfig - - // 3. Write the whole modified config zone back to the device - // atcab_write_config_zone safely skips the read-only bytes (0-15) - ESP_LOGI(TAG_PROVISION, "Writing modified config zone back to ATECC608B..."); - status = atcab_write_config_zone(g_atecc_config_data); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed to write config zone: 0x%02X", status); - return false; - } - ESP_LOGI(TAG_PROVISION, "Configuration written successfully"); - - // 4. Lock the config zone - ESP_LOGI(TAG_PROVISION, "Locking config zone..."); - status = atcab_lock_config_zone(); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed to lock config zone: 0x%02X", status); - return false; - } - - ESP_LOGI(TAG_PROVISION, - "ATECC608B configuration provisioned and locked successfully!"); - - // Invalidate cached config so it gets accurately re-read on next use - g_atecc_config_valid = false; - - // Verify it was locked - if (is_atecc608b_config_locked()) { - ESP_LOGI(TAG_PROVISION, "Verification: Config zone is now locked"); - return true; - } else { - ESP_LOGE(TAG_PROVISION, - "Verification failed: Config zone still appears unlocked"); - return false; - } -} - -/** - * Provision the ATECC608B Data Zone with required keys - * Returns: true on success, false on failure - */ -bool provision_atecc608b_data_zone() { - ESP_LOGI(TAG_PROVISION, "=== Starting ATECC608B Data Zone Provisioning ==="); - - // 1. Prerequisites check - if (is_atecc608b_data_locked()) { - ESP_LOGW(TAG_PROVISION, - "Data zone is already locked! Cannot provision keys."); - return false; - } - - if (!is_atecc608b_config_locked()) { - ESP_LOGE(TAG_PROVISION, - "Config zone MUST be locked before generating keys (GenKey)."); - return false; - } - - ATCA_STATUS status; - - // 2. Write Symmetric Keys (Slots 6, 10, 13) - ESP_LOGI(TAG_PROVISION, "Writing IO_KEY to Slot 6..."); - status = atcab_write_bytes_zone(ATCA_ZONE_DATA, 6, 0, IO_KEY, 32); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed Slot 6: 0x%02X", status); - return false; - } - - ESP_LOGI(TAG_PROVISION, "Writing HMAC_KEY to Slot 10..."); - status = atcab_write_bytes_zone(ATCA_ZONE_DATA, 10, 0, HMAC_KEY, 32); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed Slot 10: 0x%02X", status); - return false; - } - - ESP_LOGI(TAG_PROVISION, "Writing HMAC_KEY to Slot 13..."); - status = atcab_write_bytes_zone(ATCA_ZONE_DATA, 13, 0, HMAC_KEY, 32); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed Slot 13: 0x%02X", status); - return false; - } - - // 3. Write ECC Public Key to Slot 9 - // Note: The 0x04 prefix byte is stripped. The ATECC608B expects exactly 64 - // bytes. - static const uint8_t SLOT9_PUB_KEY[64] = { - 0x76, 0xc1, 0xa2, 0xe9, 0x63, 0xda, 0x58, 0x41, 0x12, 0x4e, 0xe7, - 0xc5, 0x3b, 0xeb, 0x2d, 0xad, 0x72, 0xf4, 0xc4, 0x61, 0xb8, 0x4a, - 0x65, 0xb7, 0xc7, 0x91, 0xdd, 0x59, 0xf9, 0x0a, 0xad, 0xf0, 0x6f, - 0x13, 0xf2, 0xb6, 0x29, 0x05, 0x4f, 0xab, 0x98, 0xdc, 0xfb, 0x93, - 0xab, 0xbd, 0x90, 0xd1, 0xea, 0x92, 0x91, 0x0a, 0xfe, 0x95, 0x7c, - 0xf6, 0xc7, 0x97, 0x41, 0x8e, 0x96, 0x6c, 0xaa, 0x15}; - - ESP_LOGI(TAG_PROVISION, "Writing ECC Public Key to Slot 9..."); - status = atcab_write_pubkey(9, SLOT9_PUB_KEY); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed Slot 9: 0x%02X", status); - return false; - } - - // 4. Generate Random ECC Private Keys (Slots 0-5, 11, 12) - uint8_t pubkey[64]; // Buffer to catch the generated public keys - const uint8_t genkey_slots[] = {0, 1, 2, 3, 4, 5, 11, 12}; - - for (int i = 0; i < sizeof(genkey_slots) / sizeof(genkey_slots[0]); i++) { - uint8_t slot = genkey_slots[i]; - ESP_LOGI(TAG_PROVISION, "Generating ECC Private Key in Slot %d...", slot); - - status = atcab_genkey(slot, pubkey); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed GenKey Slot %d: 0x%02X", slot, status); - return false; - } - - ESP_LOGI(TAG_PROVISION, "Successfully generated Private Key in Slot %d.", - slot); - ESP_LOGI(TAG_PROVISION, "Corresponding Public Key (64 bytes):"); - - // Standard ESP-IDF hex dump (good for reading) - ESP_LOG_BUFFER_HEX(TAG_PROVISION, pubkey, sizeof(pubkey)); - - // Continuous hex string (good for copy-pasting to host/scripts) - printf("--> Copy this public key for Slot %d: 04", - slot); // Prepending the 0x04 uncompressed prefix - for (int j = 0; j < 64; j++) { - printf("%02x", pubkey[j]); - } - printf("\n\n"); - } - - // 5. Lock the Data Zone - ESP_LOGI(TAG_PROVISION, "Locking Data Zone..."); - status = atcab_lock_data_zone(); - if (status != ATCA_SUCCESS) { - ESP_LOGE(TAG_PROVISION, "Failed to lock Data Zone: 0x%02X", status); - return false; - } - - ESP_LOGI(TAG_PROVISION, - "ATECC608B Data Zone provisioned and locked successfully!"); - return true; -} - -/** - * Check if provisioning is needed - * Returns: true if any condition requires provisioning - */ -bool needs_provisioning() { - bool config_unlocked = !is_atecc608b_config_locked(); - bool data_unlocked = !is_atecc608b_data_locked(); - bool key5_unused = !is_efuse_key5_used(); - - ESP_LOGI(TAG_PROVISION, "Provisioning check:"); - ESP_LOGI(TAG_PROVISION, " - Config unlocked: %s", - config_unlocked ? "YES" : "NO"); - ESP_LOGI(TAG_PROVISION, " - Data unlocked: %s", - data_unlocked ? "YES" : "NO"); - ESP_LOGI(TAG_PROVISION, " - KEY5 unused: %s", key5_unused ? "YES" : "NO"); - ESP_LOGI(TAG_PROVISION, " - Needs provisioning: %s", - (config_unlocked || data_unlocked || key5_unused) ? "YES" : "NO"); - - return (config_unlocked || data_unlocked || key5_unused); -} - -/** - * Provision the ESP32-C6 efuse BLOCK_KEY5 with hardcoded HMAC key - * Returns: true on success, false on failure - */ -bool provision_efuse_key5() { - ESP_LOGI(TAG_PROVISION, "=== Starting EFUSE KEY5 Provisioning ==="); - - // Check if already used - if (is_efuse_key5_used()) { - ESP_LOGW(TAG_PROVISION, "EFUSE KEY5 is already used!"); - return false; - } - - // Write the HMAC key to BLOCK_KEY5 - ESP_LOGI(TAG_PROVISION, "Writing HMAC key to BLOCK_KEY5..."); - esp_err_t err = - esp_efuse_write_key(EFUSE_BLK_KEY5, ESP_EFUSE_KEY_PURPOSE_HMAC_UP, - HMAC_KEY, sizeof(HMAC_KEY)); - - if (err != ESP_OK) { - ESP_LOGE(TAG_PROVISION, "Failed to write EFUSE KEY5: %s", - esp_err_to_name(err)); - return false; - } - - ESP_LOGI(TAG_PROVISION, "EFUSE KEY5 provisioned successfully!"); - ESP_LOGI(TAG_PROVISION, "Purpose set to: HMAC_UP"); - - // Verify it was written - if (is_efuse_key5_used()) { - ESP_LOGI(TAG_PROVISION, "Verification: KEY5 is now marked as used"); - return true; - } else { - ESP_LOGE(TAG_PROVISION, "Verification failed: KEY5 still appears unused"); - return false; - } -} - -#endif // PROVISION_H diff --git a/main/provision_handlers.h b/main/provision_handlers.h deleted file mode 100644 index 6e56d52..0000000 --- a/main/provision_handlers.h +++ /dev/null @@ -1,187 +0,0 @@ -#ifndef PROVISION_HANDLERS_H -#define PROVISION_HANDLERS_H - -#include "provision.h" -#include "provision_web_page.h" -#include -#include -#include - -static const char *TAG_PROVISION_HANDLERS = "provision_handlers"; - -// Forward declarations -extern httpd_handle_t server_http; - -/** - * Serve the provisioning web page - */ -static esp_err_t handle_provision_page(httpd_req_t *req) { - httpd_resp_set_type(req, "text/html"); - httpd_resp_sendstr(req, PROVISION_WEB_PAGE); - return ESP_OK; -} - -/** - * API endpoint: Get provisioning status - */ -static esp_err_t handle_provision_status(httpd_req_t *req) { - cJSON *response = cJSON_CreateObject(); - - bool config_unlocked = !is_atecc608b_config_locked(); - bool data_unlocked = !is_atecc608b_data_locked(); - bool key5_unused = !is_efuse_key5_used(); - bool needs_prov = needs_provisioning(); - - cJSON_AddBoolToObject(response, "needs_provisioning", needs_prov); - cJSON_AddBoolToObject(response, "config_unlocked", config_unlocked); - cJSON_AddBoolToObject(response, "data_unlocked", data_unlocked); - cJSON_AddBoolToObject(response, "key5_unused", key5_unused); - - char *json_str = cJSON_PrintUnformatted(response); - cJSON_Delete(response); - - httpd_resp_set_type(req, "application/json"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_sendstr(req, json_str); - free(json_str); - - return ESP_OK; -} - -/** - * API endpoint: Provision the device EFUSE - */ -static esp_err_t handle_provision_api(httpd_req_t *req) { - ESP_LOGI(TAG_PROVISION_HANDLERS, "Provisioning API called"); - - cJSON *response = cJSON_CreateObject(); - bool success = false; - const char *message = "Unknown error"; - - // Check if provisioning is actually needed - if (!needs_provisioning()) { - message = "Device is already provisioned"; - success = true; - ESP_LOGI(TAG_PROVISION_HANDLERS, "%s", message); - } else { - bool atecc_done = true; - bool efuse_done = true; - - // Step 1: Provision ATECC608B config if needed - if (!is_atecc608b_config_locked()) { - ESP_LOGI(TAG_PROVISION_HANDLERS, - "Provisioning ATECC608B configuration..."); - atecc_done = provision_atecc608b_config(); - if (!atecc_done) { - message = "Failed to provision ATECC608B configuration"; - success = false; - ESP_LOGE(TAG_PROVISION_HANDLERS, "%s", message); - } - } - - // Step 2: Provision EFUSE KEY5 if needed and previous step succeeded - if (atecc_done && !is_efuse_key5_used()) { - ESP_LOGI(TAG_PROVISION_HANDLERS, "Provisioning EFUSE KEY5..."); - efuse_done = provision_efuse_key5(); - if (!efuse_done) { - message = "Failed to provision EFUSE KEY5"; - success = false; - ESP_LOGE(TAG_PROVISION_HANDLERS, "%s", message); - } - } - - if (!is_atecc608b_data_locked()) { - ESP_LOGI(TAG_PROVISION_HANDLERS, "Provisioning ATECC608B data zone..."); - bool data_done = provision_atecc608b_data_zone(); - if (!data_done) { - message = "Failed to provision ATECC608B data zone"; - success = false; - ESP_LOGE(TAG_PROVISION_HANDLERS, "%s", message); - } - } - - // Build success message - if (atecc_done && efuse_done) { - if (!is_atecc608b_config_locked() && !is_efuse_key5_used()) { - message = - "Provisioned ATECC608B configuration and EFUSE KEY5 successfully"; - } else if (!is_atecc608b_config_locked()) { - message = "ATECC608B configuration provisioned and locked successfully"; - } else { - message = "EFUSE KEY5 provisioned successfully with HMAC key"; - } - success = true; - ESP_LOGI(TAG_PROVISION_HANDLERS, "%s", message); - } - } - - // Build JSON response - cJSON_AddBoolToObject(response, "success", success); - cJSON_AddStringToObject(response, "message", message); - - char *json_str = cJSON_PrintUnformatted(response); - cJSON_Delete(response); - - // Send response - httpd_resp_set_type(req, "application/json"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_set_status(req, success ? "200 OK" : "500 Internal Server Error"); - httpd_resp_sendstr(req, json_str); - free(json_str); - - return ESP_OK; -} - -/** - * Handle CORS preflight for provision API - */ -static esp_err_t handle_provision_options(httpd_req_t *req) { - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST, GET, OPTIONS"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type"); - httpd_resp_set_status(req, "204 No Content"); - httpd_resp_send(req, NULL, 0); - return ESP_OK; -} - -/** - * Register provisioning handlers - */ -void register_provision_handlers(httpd_handle_t server) { - // Provision page - httpd_uri_t provision_page_uri = {.uri = "/provision", - .method = HTTP_GET, - .handler = handle_provision_page, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &provision_page_uri); - - // API endpoint for getting status - httpd_uri_t provision_status_uri = {.uri = "/api/provision/status", - .method = HTTP_GET, - .handler = handle_provision_status, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &provision_status_uri); - - // API endpoint for provisioning - httpd_uri_t provision_api_uri = {.uri = "/api/provision", - .method = HTTP_POST, - .handler = handle_provision_api, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &provision_api_uri); - - // CORS preflight - httpd_uri_t provision_options_uri = {.uri = "/api/provision", - .method = HTTP_OPTIONS, - .handler = handle_provision_options, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &provision_options_uri); - - ESP_LOGI(TAG_PROVISION_HANDLERS, "Provision routes registered:"); - ESP_LOGI(TAG_PROVISION_HANDLERS, - " GET /provision - Provision page"); - ESP_LOGI(TAG_PROVISION_HANDLERS, - " GET /api/provision/status - Provision status"); - ESP_LOGI(TAG_PROVISION_HANDLERS, " POST /api/provision - Provision API"); -} - -#endif // PROVISION_HANDLERS_H diff --git a/main/provision_web_page.h b/main/provision_web_page.h deleted file mode 100644 index eb4c4d2..0000000 --- a/main/provision_web_page.h +++ /dev/null @@ -1,323 +0,0 @@ -#ifndef PROVISION_WEB_PAGE_H -#define PROVISION_WEB_PAGE_H - -// Embedded web interface for device provisioning - -const char PROVISION_WEB_PAGE[] = R"rawliteral( - - - - - - ESP32 Provisioning - - - -
-
-

🔧 Device Provisioning Required

-

Your device needs to be provisioned before first use

-
- -
-

âš ī¸ Provisioning Needed

-
    -
  • Loading status...
  • -
-
- -
-

â„šī¸ What This Does

-

- This will provision your device security hardware: -

-
    -
  • ATECC608B: Write secure configuration (SlotConfig, KeyConfig, ChipOptions) and lock the config zone
  • -
  • ESP32-C6: Write hardcoded HMAC key to EFUSE BLOCK_KEY5 with purpose EFUSE_KEY_PURPOSE_HMAC_UP
  • -
-

- âš ī¸ Warning: These are one-time operations and cannot be undone! -

-
- -
- Device - - - -
- - - - -)rawliteral"; - -#endif // PROVISION_WEB_PAGE_H diff --git a/main/tang_handlers.h b/main/tang_handlers.h deleted file mode 100644 index 19a72e3..0000000 --- a/main/tang_handlers.h +++ /dev/null @@ -1,288 +0,0 @@ -#ifndef TANG_HANDLERS_H -#define TANG_HANDLERS_H - -#include "crypto.h" -#include "cryptoauthlib.h" -#include "encoding.h" -#include "tang_storage.h" -#include -#include -#include -#include -#include - -static const char *TAG_HANDLERS = "tang_handlers"; - -// Forward declarations -extern httpd_handle_t server_http; -extern TangKeyStore keystore; -extern bool unlocked; - -// GET /adv - Advertisement endpoint (signed JWK set) -static esp_err_t handle_adv(httpd_req_t *req) { - if (!unlocked) { - httpd_resp_set_status(req, "503 Service Unavailable"); - httpd_resp_sendstr(req, "Server not active"); - return ESP_FAIL; - } - - uint8_t sig_pub[ATCA_PUB_KEY_SIZE]; - - if (atcab_get_pubkey(1, sig_pub) != ATCA_SUCCESS) { - ESP_LOGE(TAG_HANDLERS, "Failed to read public keys from secure element"); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Hardware error"); - return ESP_FAIL; - } - - char sig_x_b64[64] = {0}, sig_y_b64[64] = {0}; - char rec_x_b64[64] = {0}, rec_y_b64[64] = {0}; - - b64url_encode_buf(&sig_pub[0], 32, sig_x_b64, sizeof(sig_x_b64)); - b64url_encode_buf(&sig_pub[32], 32, sig_y_b64, sizeof(sig_y_b64)); - b64url_encode_buf(&keystore.exc_pub[0], 32, rec_x_b64, sizeof(rec_x_b64)); - b64url_encode_buf(&keystore.exc_pub[32], 32, rec_y_b64, sizeof(rec_y_b64)); - - // Build JWK set payload using cJSON - cJSON *payload_root = cJSON_CreateObject(); - cJSON *keys = cJSON_CreateArray(); - - // Signing/verification key - cJSON *sig_key = cJSON_CreateObject(); - cJSON_AddStringToObject(sig_key, "kty", "EC"); - cJSON_AddStringToObject(sig_key, "alg", "ES256"); - cJSON_AddStringToObject(sig_key, "crv", "P-256"); - cJSON_AddStringToObject(sig_key, "x", sig_x_b64); - cJSON_AddStringToObject(sig_key, "y", sig_y_b64); - - cJSON *sig_key_ops = cJSON_CreateArray(); - cJSON_AddItemToArray(sig_key_ops, cJSON_CreateString("verify")); - cJSON_AddItemToObject(sig_key, "key_ops", sig_key_ops); - cJSON_AddItemToArray(keys, sig_key); - - cJSON *rec_key = cJSON_CreateObject(); - cJSON_AddStringToObject(rec_key, "alg", "ECMR"); - cJSON_AddStringToObject(rec_key, "kty", "EC"); - cJSON_AddStringToObject(rec_key, "crv", "P-256"); - cJSON_AddStringToObject(rec_key, "x", rec_x_b64); - cJSON_AddStringToObject(rec_key, "y", rec_y_b64); - cJSON *rec_key_ops = cJSON_CreateArray(); - cJSON_AddItemToArray(rec_key_ops, cJSON_CreateString("deriveKey")); - cJSON_AddItemToObject(rec_key, "key_ops", rec_key_ops); - cJSON_AddItemToArray(keys, rec_key); - - cJSON_AddItemToObject(payload_root, "keys", keys); - - // Print payload JSON and encode to B64 dynamically - char *payload_json = cJSON_PrintUnformatted(payload_root); - size_t payload_len = strlen(payload_json); - size_t payload_b64_size = ((payload_len + 2) / 3) * 4 + 1; - char *payload_b64 = (char *)malloc(payload_b64_size); - b64url_encode_buf((uint8_t *)payload_json, payload_len, payload_b64, - payload_b64_size); - - free(payload_json); - cJSON_Delete(payload_root); - - // Create protected header - cJSON *protected_root = cJSON_CreateObject(); - cJSON_AddStringToObject(protected_root, "alg", "ES256"); - cJSON_AddStringToObject(protected_root, "cty", "jwk-set+json"); - - char *protected_json = cJSON_PrintUnformatted(protected_root); - size_t protected_len = strlen(protected_json); - size_t protected_b64_size = ((protected_len + 2) / 3) * 4 + 1; - char *protected_b64 = (char *)malloc(protected_b64_size); - b64url_encode_buf((uint8_t *)protected_json, protected_len, protected_b64, - protected_b64_size); - - free(protected_json); - cJSON_Delete(protected_root); - - // Sign the payload - size_t signing_input_size = - strlen(protected_b64) + 1 + strlen(payload_b64) + 1; - char *signing_input = (char *)malloc(signing_input_size); - snprintf(signing_input, signing_input_size, "%s.%s", protected_b64, - payload_b64); - - uint8_t hash[32]; - mbedtls_sha256((const uint8_t *)signing_input, strlen(signing_input), hash, - 0); - free(signing_input); - - uint8_t signature[ATCA_ECCP256_SIG_SIZE]; - if (atcab_sign(1, hash, signature) != ATCA_SUCCESS) { - ESP_LOGE(TAG_HANDLERS, "Hardware signing failed"); - free(payload_b64); - free(protected_b64); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Signing failed"); - return ESP_FAIL; - } - - char sig_b64[128] = {0}; - b64url_encode_buf(signature, sizeof(signature), sig_b64, sizeof(sig_b64)); - - cJSON *jws_root = cJSON_CreateObject(); - cJSON_AddStringToObject(jws_root, "payload", payload_b64); - cJSON_AddStringToObject(jws_root, "protected", protected_b64); - cJSON_AddStringToObject(jws_root, "signature", sig_b64); - - char *response = cJSON_PrintUnformatted(jws_root); - cJSON_Delete(jws_root); - - free(payload_b64); - free(protected_b64); - - httpd_resp_set_type( - req, "application/jose+json"); // Tang expects this specific type - httpd_resp_sendstr(req, response); - free(response); - - ESP_LOGI(TAG_HANDLERS, "Served /adv"); - return ESP_OK; -} - -// POST /rec or /rec/{kid} - Recovery endpoint -static esp_err_t handle_rec(httpd_req_t *req) { - if (!unlocked) { - httpd_resp_set_status(req, "503 Service Unavailable"); - httpd_resp_sendstr(req, "Server not active"); - return ESP_FAIL; - } - - // Read request body - char *buf = (char *)malloc(req->content_len + 1); - if (!buf) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "Memory allocation failed"); - return ESP_FAIL; - } - - int ret = httpd_req_recv(req, buf, req->content_len); - if (ret <= 0) { - free(buf); - if (ret == HTTPD_SOCK_ERR_TIMEOUT) { - httpd_resp_send_408(req); - } - return ESP_FAIL; - } - buf[ret] = '\0'; - - // Parse JSON - cJSON *req_doc = cJSON_Parse(buf); - free(buf); - - if (!req_doc) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - // Extract client's ephemeral public key - cJSON *x_item = cJSON_GetObjectItem(req_doc, "x"); - cJSON *y_item = cJSON_GetObjectItem(req_doc, "y"); - - if (!x_item || !y_item || !cJSON_IsString(x_item) || - !cJSON_IsString(y_item)) { - cJSON_Delete(req_doc); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Missing x or y coordinates"); - return ESP_FAIL; - } - - uint8_t client_pub_key[P256_PUBLIC_KEY_SIZE]; - - // Use the new helper to decode and strictly validate the length of both - // coordinates - if (!b64url_decode_buf(x_item->valuestring, &client_pub_key[0], - P256_COORDINATE_SIZE) || - !b64url_decode_buf(y_item->valuestring, - &client_pub_key[P256_COORDINATE_SIZE], - P256_COORDINATE_SIZE)) { - cJSON_Delete(req_doc); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid key coordinates"); - return ESP_FAIL; - } - - cJSON_Delete(req_doc); - - // Perform ECDH to get shared point - uint8_t shared_point[P256_PUBLIC_KEY_SIZE]; - if (!P256::ecdh_compute_shared_point(client_pub_key, keystore.exc_priv, - shared_point, true)) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "ECDH computation failed"); - return ESP_FAIL; - } - - char shared_x_b64[64] = {0}; - char shared_y_b64[64] = {0}; - - b64url_encode_buf(&shared_point[0], P256_COORDINATE_SIZE, shared_x_b64, - sizeof(shared_x_b64)); - b64url_encode_buf(&shared_point[P256_COORDINATE_SIZE], P256_COORDINATE_SIZE, - shared_y_b64, sizeof(shared_y_b64)); - - // Return shared point as JWK - cJSON *resp_root = cJSON_CreateObject(); - cJSON_AddStringToObject(resp_root, "alg", "ECMR"); - cJSON_AddStringToObject(resp_root, "kty", "EC"); - cJSON_AddStringToObject(resp_root, "crv", "P-256"); - cJSON_AddStringToObject(resp_root, "x", shared_x_b64); - cJSON_AddStringToObject(resp_root, "y", shared_y_b64); - cJSON *key_ops = cJSON_CreateArray(); - cJSON_AddItemToArray(key_ops, cJSON_CreateString("deriveKey")); - cJSON_AddItemToObject(resp_root, "key_ops", key_ops); - - char *response = cJSON_PrintUnformatted(resp_root); - cJSON_Delete(resp_root); - - httpd_resp_set_type(req, "application/jose+json"); - httpd_resp_sendstr(req, response); - free(response); - - ESP_LOGI(TAG_HANDLERS, "Served %s", req->uri); - return ESP_OK; -} - -// --- Administration Handlers --- - -// GET /config - Get ATECC608B configuration -static esp_err_t handle_config(httpd_req_t *req) { - ESP_LOGI(TAG_HANDLERS, "Serving ATECC608B config"); - - char *json_str = atecc608B_get_config_json(); - - if (!json_str) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "Config data not available"); - return ESP_FAIL; - } - - httpd_resp_set_type(req, "application/json"); - httpd_resp_sendstr(req, json_str); - free(json_str); - - ESP_LOGI(TAG_HANDLERS, "Served /config"); - return ESP_OK; -} - -// GET /reboot - Reboot device -static esp_err_t handle_reboot(httpd_req_t *req) { - httpd_resp_sendstr(req, "Rebooting..."); - vTaskDelay(pdMS_TO_TICKS(1000)); - esp_restart(); - return ESP_OK; -} - -// 404 handler - Handle /rec/{kid} by routing to handle_rec -static esp_err_t handle_not_found(httpd_req_t *req, httpd_err_code_t err) { - // Check if it's a POST to /rec/{kid} - if (req->method == HTTP_POST && strncmp(req->uri, "/rec/", 5) == 0) { - return handle_rec(req); - } - - httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Not found"); - return ESP_FAIL; -} - -#endif // TANG_HANDLERS_H diff --git a/main/tang_storage.h b/main/tang_storage.h deleted file mode 100644 index a1568cc..0000000 --- a/main/tang_storage.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef TANG_STORAGE_H -#define TANG_STORAGE_H - -#include "crypto.h" -#include "encoding.h" -#include -#include -#include -#include -#include - -static const char *TAG_STORAGE = "tang_storage"; - -// --- Key Storage & Management --- -class TangKeyStore { -private: - // No longer need salt for password-based encryption - -public: - // Tang server keys (encrypted at rest, decrypted in memory when active) - uint8_t exc_priv[P256_PRIVATE_KEY_SIZE]; - uint8_t exc_pub[P256_PUBLIC_KEY_SIZE]; - - // Admin key (persistent) - uint8_t admin_priv[P256_PRIVATE_KEY_SIZE]; - uint8_t admin_pub[P256_PUBLIC_KEY_SIZE]; - - bool is_configured() { - nvs_handle_t handle; - esp_err_t err = nvs_open("tang-server", NVS_READWRITE, &handle); - if (err != ESP_OK) { - return false; - } - - size_t required_size = 0; - err = nvs_get_blob(handle, "admin_key", nullptr, &required_size); - bool configured = (err == ESP_OK && required_size == P256_PRIVATE_KEY_SIZE); - - nvs_close(handle); - return configured; - } - - // Save Tang keys directly to NVS (no encryption) - bool save_tang_keys() { - nvs_handle_t handle; - esp_err_t err = nvs_open("tang-server", NVS_READWRITE, &handle); - if (err != ESP_OK) { - ESP_LOGE(TAG_STORAGE, "Failed to open NVS: %s", esp_err_to_name(err)); - return false; - } - - // Save exchange key - err = nvs_set_blob(handle, "tang_exc_key", exc_priv, P256_PRIVATE_KEY_SIZE); - if (err != ESP_OK) { - nvs_close(handle); - return false; - } - - err = nvs_commit(handle); - nvs_close(handle); - return (err == ESP_OK); - } - - // Load Tang keys directly from NVS (no decryption) - bool load_tang_keys() { - nvs_handle_t handle; - esp_err_t err = nvs_open("tang-server", NVS_READONLY, &handle); - if (err != ESP_OK) { - ESP_LOGE(TAG_STORAGE, "Failed to open NVS: %s", esp_err_to_name(err)); - return false; - } - - // Load exchange key - size_t len = P256_PRIVATE_KEY_SIZE; - err = nvs_get_blob(handle, "tang_exc_key", exc_priv, &len); - if (err != ESP_OK || len != P256_PRIVATE_KEY_SIZE) { - nvs_close(handle); - return false; - } - P256::compute_public_key(exc_priv, exc_pub); - - nvs_close(handle); - return true; - } -}; - -#endif // TANG_STORAGE_H diff --git a/main/zk_auth.h b/main/zk_auth.h index edb5471..4db388d 100644 --- a/main/zk_auth.h +++ b/main/zk_auth.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -40,8 +41,6 @@ private: uint8_t stored_password_hash[32]; // PBKDF2 hash of the correct password bool initialized; - bool password_set; - bool unlocked = false; // Set to true after successful authentication // Convert binary to hex string void bin_to_hex(const uint8_t *bin, size_t bin_len, char *hex) { @@ -65,7 +64,7 @@ private: } public: - ZKAuth() : initialized(false), password_set(false), unlocked(false) { + ZKAuth() : initialized(false) { mbedtls_ecp_group_init(&grp); mbedtls_mpi_init(&device_private_d); mbedtls_ecp_point_init(&device_public_Q); @@ -83,60 +82,78 @@ public: // Initialize the ZK authentication system bool init() { - if (initialized) + if (initialized) { return true; - - // Seed the random number generator - const char *pers = "zk_auth_esp32"; - int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - (const unsigned char *)pers, strlen(pers)); - if (ret != 0) { - printf("mbedtls_ctr_drbg_seed failed: -0x%04x\n", -ret); - return false; + } else { + initialized = true; + printf("\n=== ZK Authentication Initialized ===\n"); } - - // Setup ECC group for NIST P-256 (secp256r1) - ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); - if (ret != 0) { - printf("mbedtls_ecp_group_load failed: -0x%04x\n", -ret); - return false; - } - - // Generate device keypair - ret = mbedtls_ecdh_gen_public(&grp, &device_private_d, &device_public_Q, - mbedtls_ctr_drbg_random, &ctr_drbg); - if (ret != 0) { - printf("mbedtls_ecdh_gen_public failed: -0x%04x\n", -ret); - return false; - } - - // Export public key to uncompressed format (0x04 + X + Y) - size_t olen; - ret = mbedtls_ecp_point_write_binary( - &grp, &device_public_Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, - device_public_key, sizeof(device_public_key)); - if (ret != 0 || olen != 65) { - printf("mbedtls_ecp_point_write_binary failed: -0x%04x\n", -ret); - return false; - } - - initialized = true; - - printf("\n=== ZK Authentication Initialized ===\n"); - printf("Public Key: "); - for (int i = 0; i < 65; i++) { - printf("%02x", device_public_key[i]); - } - printf("\n"); - printf("=====================================\n\n"); - return true; } + // Helper function to write a 32-bit integer in big-endian format + void write_uint32_be(uint8_t *buf, uint32_t val) { + buf[0] = (val >> 24) & 0xFF; + buf[1] = (val >> 16) & 0xFF; + buf[2] = (val >> 8) & 0xFF; + buf[3] = val & 0xFF; + } + + // Function to generate the authorized_keys string + void generate_ssh_authorized_key(const uint8_t *atec_pubkey) { + uint8_t ssh_blob[104]; + uint32_t offset = 0; + + // 1. Key Type + const char *key_type = "ecdsa-sha2-nistp256"; + write_uint32_be(&ssh_blob[offset], 19); + offset += 4; + memcpy(&ssh_blob[offset], key_type, 19); + offset += 19; + + // 2. Curve Name + const char *curve_name = "nistp256"; + write_uint32_be(&ssh_blob[offset], 8); + offset += 4; + memcpy(&ssh_blob[offset], curve_name, 8); + offset += 8; + + // 3. Public Key (Uncompressed format: 0x04 + 64 bytes X/Y) + write_uint32_be(&ssh_blob[offset], 65); + offset += 4; + ssh_blob[offset++] = 0x04; + memcpy(&ssh_blob[offset], atec_pubkey, 64); + offset += 64; + + // 4. Base64 Encode the blob + size_t b64_len = 0; + // Call once to get required length + mbedtls_base64_encode(NULL, 0, &b64_len, ssh_blob, 104); + + unsigned char b64_out[b64_len]; + // Call again to actually encode + mbedtls_base64_encode(b64_out, b64_len, &b64_len, ssh_blob, 104); + + // 5. Print out the final authorized_keys line + printf("ecdsa-sha2-nistp256 %s esp32-atecc608b\n", b64_out); + } + // Get device identity (for /api/identity endpoint) char *get_identity_json() { - char pubkey_hex[131]; // 65 bytes * 2 + null - bin_to_hex(device_public_key, 65, pubkey_hex); + char pubkey_hex[131]; // 65 bytes * 2 + null + uint8_t atec_pubkey[64]; // 65 bytes * 2 + null + uint8_t standard_pubkey[65]; + standard_pubkey[0] = 0x04; + // Get public key from ATECC608B and convert to hex + ATCA_STATUS status = atcab_get_pubkey(0, atec_pubkey); + if (status != ATCA_SUCCESS) { + printf("Failed to read public key from ATECC608B: 0x%02X\n", status); + } + + generate_ssh_authorized_key(atec_pubkey); + + memcpy(&standard_pubkey[1], atec_pubkey, 64); + bin_to_hex(standard_pubkey, 65, pubkey_hex); // Get MAC address to use as salt uint8_t mac[6]; @@ -152,384 +169,6 @@ public: cJSON_Delete(root); return json_str; } - - void my_host_check_mac(const uint8_t *target_key, const uint8_t *tempkey, - const uint8_t *other_data, const uint8_t *sn, - uint8_t *out_mac) { - // See section 11.2 of the ATECC608B datasheet CheckMac message format - uint8_t msg[88] = {0}; - memcpy(&msg[0], target_key, 32); - memcpy(&msg[32], tempkey, 32); - memcpy(&msg[64], &other_data[0], 4); - memset(&msg[68], 0, 8); - memcpy(&msg[76], &other_data[4], 3); - msg[79] = sn[8]; - memcpy(&msg[80], &other_data[7], 4); - msg[84] = sn[0]; - msg[85] = sn[1]; - memcpy(&msg[86], &other_data[11], 2); - // Hash the exact 88 bytes using the ESP32's hardware accelerator - mbedtls_sha256(msg, sizeof(msg), out_mac, 0); - } - - bool verify_key(const uint8_t *received_key, size_t key_len) { - (void)key_len; - if (received_key == NULL) - return false; - - ATCA_STATUS status; - - bool latch_status = false; - status = atcab_info_get_latch(&latch_status); - - printf("Device Latch Status: %s (0x%02X)\n", - latch_status ? "LATCHED" : "UNLATCHED", latch_status); - - uint8_t sn[ATCA_SERIAL_NUM_SIZE] = {0}; - status = atcab_read_serial_number(sn); - if (status != ATCA_SUCCESS) { - printf("Failed to read Serial Number\n"); - return false; - } - - uint8_t nonce_num_in[20] = {0xAA, 0xBB, 0xCC, 0xDD}; - uint8_t rand_out[32] = {0}; - uint8_t slot10_mac[32] = {0}; - uint8_t other_data_10[13] = "TangActivate"; - struct atca_temp_key temp_key; - memset(&temp_key, 0, sizeof(temp_key)); - - status = atcab_nonce_rand(nonce_num_in, rand_out); - - // Print the nonce output for debugging - if (status != ATCA_SUCCESS) { - printf("Slot 10 Nonce failed: 0x%02X\n", status); - } else { - // Compute the TempKey on the ESP32 - struct atca_nonce_in_out nonce_params; - memset(&nonce_params, 0, sizeof(nonce_params)); - nonce_params.mode = 0x00; - nonce_params.num_in = nonce_num_in; - nonce_params.rand_out = rand_out; - nonce_params.temp_key = &temp_key; - - atcah_nonce(&nonce_params); - - // Host calculates MAC using the calculated TempKey as the challenge - my_host_check_mac(received_key, temp_key.value, other_data_10, sn, - slot10_mac); - - // See section 11.2 of the ATECC608B datasheet CheckMac message format - status = atcab_checkmac(0x01, 10, NULL, slot10_mac, other_data_10); - if (status == ATCA_SUCCESS) { - printf("Slot 10 CheckMac: PASSED\n"); - } else if (status == (ATCA_STATUS)ATCA_CHECKMAC_VERIFY_FAILED) { - // 0xD1 (-47 / 0xFFFFFFD1) see atca_status.h - printf("Slot 10 CheckMac: ACCESS DENIED (Wrong Password)\n"); - } else { - printf("Slot 10 CheckMac: HARDWARE ERROR (0x%02X)\n", status); - } - } - status = atcab_info_set_latch(true); - - if (status != ATCA_SUCCESS) { - printf("Failed to latch device: 0x%02X\n", status); - } - - atcab_info_get_latch(&latch_status); - printf("Device Latch Status after setting: %s (0x%02X)\n", - latch_status ? "LATCHED" : "UNLATCHED", latch_status); - - return (status == ATCA_SUCCESS); - } - - // Process unlock request with ECIES tunnel - char *process_unlock(const char *json_payload, bool *success_out) { - *success_out = false; - - if (!initialized) { - return strdup("{\"error\":\"Not initialized\"}"); - } - - // Parse JSON - cJSON *doc = cJSON_Parse(json_payload); - if (doc == NULL) { - const char *error_ptr = cJSON_GetErrorPtr(); - if (error_ptr != NULL) { - printf("JSON parse error before: %s\n", error_ptr); - } - return strdup("{\"error\":\"Invalid JSON\"}"); - } - - cJSON *client_pub_item = cJSON_GetObjectItem(doc, "clientPub"); - cJSON *encrypted_blob_item = cJSON_GetObjectItem(doc, "blob"); - - const char *client_pub_hex = NULL; - const char *encrypted_blob_hex = NULL; - - if (cJSON_IsString(client_pub_item)) - client_pub_hex = client_pub_item->valuestring; - if (cJSON_IsString(encrypted_blob_item)) - encrypted_blob_hex = encrypted_blob_item->valuestring; - - if (!client_pub_hex || !encrypted_blob_hex) { - cJSON_Delete(doc); - return strdup("{\"error\":\"Missing required fields\"}"); - } - - printf("\n=== Processing Unlock Request ===\n"); - printf("Client Public Key: %s\n", client_pub_hex); - printf("Encrypted Blob: %s\n", encrypted_blob_hex); - - // Convert client public key from hex - size_t client_pub_len = strlen(client_pub_hex) / 2; - uint8_t *client_pub_bin = (uint8_t *)malloc(client_pub_len); - if (!hex_to_bin(client_pub_hex, client_pub_bin, client_pub_len)) { - free(client_pub_bin); - cJSON_Delete(doc); - return strdup("{\"error\":\"Invalid client public key format\"}"); - } - - // Parse client public key point - mbedtls_ecp_point client_point; - mbedtls_ecp_point_init(&client_point); - - int ret = mbedtls_ecp_point_read_binary(&grp, &client_point, client_pub_bin, - client_pub_len); - free(client_pub_bin); - - if (ret != 0) { - mbedtls_ecp_point_free(&client_point); - cJSON_Delete(doc); - printf("Failed to parse client key: -0x%04x\n", -ret); - return strdup("{\"error\":\"Invalid client public key\"}"); - } - - // Compute shared secret using ECDH - mbedtls_mpi shared_secret_mpi; - mbedtls_mpi_init(&shared_secret_mpi); - - ret = mbedtls_ecdh_compute_shared(&grp, &shared_secret_mpi, &client_point, - &device_private_d, - mbedtls_ctr_drbg_random, &ctr_drbg); - - mbedtls_ecp_point_free(&client_point); - - if (ret != 0) { - mbedtls_mpi_free(&shared_secret_mpi); - cJSON_Delete(doc); - printf("ECDH computation failed: -0x%04x\n", -ret); - return strdup("{\"error\":\"ECDH failed\"}"); - } - - // Export shared secret to binary - uint8_t shared_secret_raw[32]; - ret = mbedtls_mpi_write_binary(&shared_secret_mpi, shared_secret_raw, 32); - mbedtls_mpi_free(&shared_secret_mpi); - - if (ret != 0) { - cJSON_Delete(doc); - printf("Shared secret export failed: -0x%04x\n", -ret); - return strdup("{\"error\":\"Shared secret export failed\"}"); - } - - printf("Shared Secret (raw): "); - for (int i = 0; i < 32; i++) - printf("%02x", shared_secret_raw[i]); - printf("\n"); - - // Derive separate keys for encryption and authentication - // This prevents key reuse vulnerabilities - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - - // Encryption key: SHA256("encryption" || shared_secret) - uint8_t enc_key[32]; - mbedtls_md_context_t md_ctx; - mbedtls_md_init(&md_ctx); - mbedtls_md_setup(&md_ctx, md_info, 0); - mbedtls_md_starts(&md_ctx); - mbedtls_md_update(&md_ctx, (const uint8_t *)"encryption", 10); - mbedtls_md_update(&md_ctx, shared_secret_raw, 32); - mbedtls_md_finish(&md_ctx, enc_key); - - // MAC key: SHA256("authentication" || shared_secret) - uint8_t mac_key[32]; - mbedtls_md_starts(&md_ctx); - mbedtls_md_update(&md_ctx, (const uint8_t *)"authentication", 14); - mbedtls_md_update(&md_ctx, shared_secret_raw, 32); - mbedtls_md_finish(&md_ctx, mac_key); - mbedtls_md_free(&md_ctx); - - printf("Encryption Key: "); - for (int i = 0; i < 32; i++) - printf("%02x", enc_key[i]); - printf("\n"); - - printf("MAC Key: "); - for (int i = 0; i < 32; i++) - printf("%02x", mac_key[i]); - printf("\n"); - - // Securely wipe the raw shared secret - memset(shared_secret_raw, 0, 32); - - // Convert encrypted blob from hex - // Format: IV (16 bytes) + Ciphertext (32 bytes) + HMAC (32 bytes) = 80 - // bytes - size_t encrypted_len = strlen(encrypted_blob_hex) / 2; - uint8_t *encrypted_blob = (uint8_t *)malloc(encrypted_len); - if (!hex_to_bin(encrypted_blob_hex, encrypted_blob, encrypted_len)) { - free(encrypted_blob); - memset(enc_key, 0, 32); - memset(mac_key, 0, 32); - cJSON_Delete(doc); - return strdup("{\"error\":\"Invalid blob format\"}"); - } - - // Verify blob length: IV(16) + Ciphertext(32) + HMAC(32) = 80 bytes - if (encrypted_len != 80) { - free(encrypted_blob); - memset(enc_key, 0, 32); - memset(mac_key, 0, 32); - cJSON_Delete(doc); - printf("Expected 80 bytes, got %d\n", encrypted_len); - return strdup("{\"error\":\"Invalid blob length\"}"); - } - - // Extract components from blob - uint8_t *iv = encrypted_blob; // First 16 bytes - uint8_t *ciphertext = encrypted_blob + 16; // Next 32 bytes - uint8_t *received_hmac = encrypted_blob + 48; // Last 32 bytes - - printf("IV: "); - for (int i = 0; i < 16; i++) - printf("%02x", iv[i]); - printf("\n"); - - printf("Received HMAC: "); - for (int i = 0; i < 32; i++) - printf("%02x", received_hmac[i]); - printf("\n"); - - // âš ī¸ CRITICAL: Verify HMAC BEFORE decrypting - // This prevents padding oracle attacks and ensures data authenticity - uint8_t computed_hmac[32]; - ret = mbedtls_md_hmac(md_info, mac_key, 32, encrypted_blob, 48, - computed_hmac); - - if (ret != 0) { - free(encrypted_blob); - memset(enc_key, 0, 32); - memset(mac_key, 0, 32); - cJSON_Delete(doc); - printf("HMAC computation failed: -0x%04x\n", -ret); - return strdup("{\"error\":\"HMAC computation failed\"}"); - } - - printf("Computed HMAC: "); - for (int i = 0; i < 32; i++) - printf("%02x", computed_hmac[i]); - printf("\n"); - - // Constant-time comparison to prevent timing attacks - int hmac_result = 0; - for (int i = 0; i < 32; i++) { - hmac_result |= received_hmac[i] ^ computed_hmac[i]; - } - - // Wipe MAC key after verification - memset(mac_key, 0, 32); - - if (hmac_result != 0) { - free(encrypted_blob); - memset(enc_key, 0, 32); - cJSON_Delete(doc); - printf("❌ HMAC verification FAILED - ciphertext was modified or wrong " - "key!\n"); - return strdup("{\"error\":\"Authentication failed - data tampered or " - "wrong password\"}"); - } - - printf("✅ HMAC verified - data is authentic\n"); - - // Now safe to decrypt (HMAC passed) - mbedtls_aes_context aes; - mbedtls_aes_init(&aes); - - ret = mbedtls_aes_setkey_dec(&aes, enc_key, 256); - if (ret != 0) { - mbedtls_aes_free(&aes); - free(encrypted_blob); - memset(enc_key, 0, 32); - cJSON_Delete(doc); - printf("AES setkey failed: -0x%04x\n", -ret); - return strdup("{\"error\":\"AES setup failed\"}"); - } - - uint8_t *decrypted_data = (uint8_t *)malloc(32); - uint8_t iv_copy[16]; - memcpy(iv_copy, iv, 16); // CBC mode modifies IV - - ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, - 32, // data length - iv_copy, // IV (will be modified) - ciphertext, // input - decrypted_data); // output - - mbedtls_aes_free(&aes); - free(encrypted_blob); - memset(enc_key, 0, 32); - - if (ret != 0) { - free(decrypted_data); - cJSON_Delete(doc); - printf("AES decrypt failed: -0x%04x\n", -ret); - return strdup("{\"error\":\"Decryption failed\"}"); - } - - // Extract the derived key (PBKDF2 hash, 32 bytes) - printf("\n=== DECRYPTED DERIVED KEY ===\n"); - printf("Key (hex): "); - for (size_t i = 0; i < 32; i++) { - printf("%02x", decrypted_data[i]); - } - printf("\n"); - printf("=============================\n\n"); - - // Verify the decrypted key against stored password hash - bool verification_result = verify_key(decrypted_data, 32); - - cJSON *resp_doc = cJSON_CreateObject(); - cJSON_AddBoolToObject(resp_doc, "success", verification_result); - - if (verification_result) { - printf("✅ Password verification SUCCESSFUL\n"); - unlocked = true; - cJSON_AddStringToObject(resp_doc, "message", "Unlock successful"); - } else { - printf("❌ Password verification FAILED\n"); - unlocked = false; - cJSON_AddStringToObject(resp_doc, "error", "Invalid password"); - } - - char *response_str = cJSON_PrintUnformatted(resp_doc); - cJSON_Delete(resp_doc); - cJSON_Delete(doc); - - // Securely wipe decrypted data - memset(decrypted_data, 0, 32); - free(decrypted_data); - - *success_out = verification_result; - return response_str; - } - - // Check if device is unlocked - bool is_unlocked() const { return unlocked; } - - // Lock the device - void lock() { unlocked = false; } }; #endif // ZK_AUTH_H diff --git a/main/zk_handlers.h b/main/zk_handlers.h index 9cb188d..bb9ac47 100644 --- a/main/zk_handlers.h +++ b/main/zk_handlers.h @@ -1,37 +1,25 @@ #ifndef ZK_HANDLERS_H #define ZK_HANDLERS_H -#include "provision.h" #include "zk_auth.h" -#include "zk_web_page.h" #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -static const char *TAG_ZK = "zk_handlers"; - // Global ZK Auth instance (to be initialized in main) extern ZKAuth zk_auth; extern httpd_handle_t server_http; -// Serve the main web interface -static esp_err_t handle_zk_root(httpd_req_t *req) { - // Check if provisioning is needed - if so, redirect - if (needs_provisioning()) { - ESP_LOGI(TAG_ZK, "Provisioning needed - redirecting to /provision"); - httpd_resp_set_status(req, "302 Found"); - httpd_resp_set_hdr(req, "Location", "/provision"); - httpd_resp_sendstr(req, "Redirecting to provisioning page..."); - return ESP_OK; - } - - // Normal landing page - httpd_resp_set_type(req, "text/html"); - httpd_resp_sendstr(req, ZK_WEB_PAGE); - return ESP_OK; -} - // API endpoint: Get device identity static esp_err_t handle_zk_identity(httpd_req_t *req) { char *json_response = zk_auth.get_identity_json(); @@ -49,111 +37,14 @@ static esp_err_t handle_zk_identity(httpd_req_t *req) { } // API endpoint: Process unlock request -static esp_err_t handle_zk_unlock(httpd_req_t *req) { - // Read POST body - char content[1024]; - int ret = httpd_req_recv(req, content, sizeof(content) - 1); - if (ret <= 0) { - if (ret == HTTPD_SOCK_ERR_TIMEOUT) { - httpd_resp_send_408(req); - } - return ESP_FAIL; - } - content[ret] = '\0'; - - bool success = false; - char *response = zk_auth.process_unlock(content, &success); - - if (response == NULL) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Internal error"); - return ESP_FAIL; - } - - httpd_resp_set_type(req, "application/json"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_set_status(req, success ? "200 OK" : "400 Bad Request"); - httpd_resp_sendstr(req, response); - free(response); - return ESP_OK; -} - -static esp_err_t handle_zk_status(httpd_req_t *req) { - unsigned long uptime_ms = esp_timer_get_time() / 1000; - char response[128]; - snprintf(response, sizeof(response), "{\"unlocked\":%s,\"uptime\":%lu}", - zk_auth.is_unlocked() ? "true" : "false", uptime_ms); - - httpd_resp_set_type(req, "application/json"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_sendstr(req, response); - return ESP_OK; -} - -// API endpoint: Lock the device -static esp_err_t handle_zk_lock(httpd_req_t *req) { - zk_auth.lock(); - httpd_resp_set_type(req, "application/json"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_sendstr(req, "{\"unlocked\":false}"); - return ESP_OK; -} - // Handle CORS preflight -static esp_err_t handle_zk_options(httpd_req_t *req) { - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST, GET, OPTIONS"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type"); - httpd_resp_set_status(req, "204 No Content"); - httpd_resp_send(req, NULL, 0); - return ESP_OK; -} - // Register all ZK auth routes to the HTTP server void register_zk_handlers(httpd_handle_t server) { - // Root handler for ZK web interface - httpd_uri_t root_uri = {.uri = "/", - .method = HTTP_GET, - .handler = handle_zk_root, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &root_uri); - - // API endpoints httpd_uri_t identity_uri = {.uri = "/api/identity", .method = HTTP_GET, .handler = handle_zk_identity, .user_ctx = NULL}; httpd_register_uri_handler(server, &identity_uri); - - httpd_uri_t status_uri = {.uri = "/api/status", - .method = HTTP_GET, - .handler = handle_zk_status, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &status_uri); - - httpd_uri_t unlock_uri = {.uri = "/api/unlock", - .method = HTTP_POST, - .handler = handle_zk_unlock, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &unlock_uri); - - httpd_uri_t unlock_options_uri = {.uri = "/api/unlock", - .method = HTTP_OPTIONS, - .handler = handle_zk_options, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &unlock_options_uri); - - httpd_uri_t lock_uri = {.uri = "/api/lock", - .method = HTTP_POST, - .handler = handle_zk_lock, - .user_ctx = NULL}; - httpd_register_uri_handler(server, &lock_uri); - - ESP_LOGI(TAG_ZK, "ZK Auth routes registered:"); - ESP_LOGI(TAG_ZK, " GET / - Web interface"); - ESP_LOGI(TAG_ZK, " GET /api/identity - Device identity"); - ESP_LOGI(TAG_ZK, " GET /api/status - Session status"); - ESP_LOGI(TAG_ZK, " POST /api/unlock - Unlock request"); - ESP_LOGI(TAG_ZK, " POST /api/lock - Lock device"); } #endif // ZK_HANDLERS_H diff --git a/main/zk_web_page.h b/main/zk_web_page.h deleted file mode 100644 index 1deea34..0000000 --- a/main/zk_web_page.h +++ /dev/null @@ -1,833 +0,0 @@ -#ifndef ZK_WEB_PAGE_H -#define ZK_WEB_PAGE_H - -// Embedded web interface for Zero-Knowledge Authentication -// Includes: HTML, CSS, JavaScript, and crypto libraries (elliptic.js, CryptoJS) - -const char ZK_WEB_PAGE[] = R"rawliteral( - - - - - - ESP32 Zero-Knowledge Auth - - - - -
-
-

Zero-Knowledge Auth

-

ESP32-C6 Secure Unlock

- -
- Privacy First: Your password is never transmitted. - The device only receives an encrypted, derived key over an ECIES tunnel. -
- -
- - -
- - - -
-
- -
-
-
- - - -
-

Device Unlocked!

-

Authentication successful

-
- -
-

Session Information

-
- Status - Active -
-
- Authentication Method - Password -
-
- Key Derivation - PBKDF2-SHA256 -
-
- Encryption - ECIES (P-256 + AES-CBC + HMAC) -
-
- Authenticated At - -- -
-
- Device Uptime - -- -
-
- -
-
- - - - - - - -)rawliteral"; - -#endif // ZK_WEB_PAGE_H