Restructure and improve
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
idf_component_register(SRCS "main.c"
|
||||
"wifi.c"
|
||||
"atecc608a.c"
|
||||
"ssh_client.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES mbedtls esp-cryptoauthlib esp_http_server esp_wifi nvs_flash json esp_timer efuse)
|
||||
REQUIRES mbedtls esp-cryptoauthlib esp_wifi nvs_flash driver)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
menu "Wi-Fi Credentials"
|
||||
menu "KeyPi"
|
||||
|
||||
config WIFI_SSID
|
||||
string "Wi-Fi SSID"
|
||||
@@ -12,4 +12,34 @@ config WIFI_PASSWORD
|
||||
help
|
||||
Password for the Wi-Fi network.
|
||||
|
||||
config SSH_HOSTNAME
|
||||
string "SSH Server Hostname or IP"
|
||||
default "192.168.1.1"
|
||||
help
|
||||
Hostname or IP address of the SSH server (door controller).
|
||||
|
||||
config SSH_PORT
|
||||
int "SSH Server Port"
|
||||
default 22
|
||||
help
|
||||
TCP port of the SSH server.
|
||||
|
||||
config SSH_USERNAME
|
||||
string "SSH Username"
|
||||
default "pi"
|
||||
help
|
||||
Username to authenticate with on the SSH server.
|
||||
|
||||
config SSH_OPEN_COMMAND
|
||||
string "SSH Open Command"
|
||||
default "door open"
|
||||
help
|
||||
Shell command to execute on the SSH server to open the door.
|
||||
|
||||
config SSH_LOCK_COMMAND
|
||||
string "SSH Lock Command"
|
||||
default "door lock"
|
||||
help
|
||||
Shell command to execute on the SSH server to lock the door.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
#ifndef TANG_SERVER_H
|
||||
#define TANG_SERVER_H
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <esp_event.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <string>
|
||||
|
||||
static const char *TAG = "TangServer";
|
||||
|
||||
// Include core components
|
||||
#include "atecc608a.h"
|
||||
#include "zk_auth.h"
|
||||
#include "zk_handlers.h"
|
||||
|
||||
// --- Configuration ---
|
||||
const char *wifi_ssid = CONFIG_WIFI_SSID;
|
||||
const char *wifi_password = CONFIG_WIFI_PASSWORD;
|
||||
|
||||
// --- Global State ---
|
||||
bool unlocked = false; // Start inactive until provisioned and authenticated
|
||||
httpd_handle_t server_http = NULL;
|
||||
ZKAuth zk_auth; // Zero-Knowledge Authentication
|
||||
|
||||
// WiFi event group
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
const int WIFI_CONNECTED_BIT = BIT0;
|
||||
|
||||
// --- WiFi Event Handler ---
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
ESP_LOGI(TAG, "WiFi connecting...");
|
||||
} else if (event_base == WIFI_EVENT &&
|
||||
event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
esp_wifi_connect();
|
||||
ESP_LOGI(TAG, "WiFi disconnected, reconnecting...");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
ESP_LOGI(TAG, "WiFi connected, IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
// --- WiFi Setup ---
|
||||
void setup_wifi() {
|
||||
// Initialize event group
|
||||
wifi_event_group = xEventGroupCreate();
|
||||
|
||||
// Initialize network interface
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||
assert(sta_netif);
|
||||
|
||||
// Set hostname
|
||||
ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, "esp-tang-lol"));
|
||||
|
||||
// Initialize WiFi
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
// Register event handlers
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
&wifi_event_handler, NULL));
|
||||
|
||||
// Configure WiFi
|
||||
wifi_config_t wifi_config = {};
|
||||
if (strlen(wifi_ssid) > 0) {
|
||||
strncpy((char *)wifi_config.sta.ssid, wifi_ssid,
|
||||
sizeof(wifi_config.sta.ssid));
|
||||
strncpy((char *)wifi_config.sta.password, wifi_password,
|
||||
sizeof(wifi_config.sta.password));
|
||||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to SSID: %s", wifi_ssid);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No WiFi SSID configured");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Initial Setup ---
|
||||
bool perform_initial_setup() {
|
||||
|
||||
ESP_LOGI(TAG, "=======================================================");
|
||||
ESP_LOGI(TAG, "Setup complete! Device is ready to use");
|
||||
ESP_LOGI(TAG, "NOTE: Exchange key stored unencrypted for prototyping");
|
||||
ESP_LOGI(TAG, "=======================================================");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Setup HTTP Server Routes ---
|
||||
httpd_handle_t setup_http_server() {
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.lru_purge_enable = true;
|
||||
config.stack_size = 8192;
|
||||
config.max_uri_handlers = 16;
|
||||
|
||||
httpd_handle_t server = NULL;
|
||||
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
register_zk_handlers(server);
|
||||
ESP_LOGI(TAG, "HTTP server listening on port 80");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to start HTTP server");
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
// --- Main Setup ---
|
||||
void setup() {
|
||||
|
||||
// Initialize NVS (required before any storage operations)
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ESP_LOGI(TAG, "NVS initialized");
|
||||
|
||||
if (atecc608B_init()) {
|
||||
atecc608B_print_config();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "WARNING: ATECC608A initialization failed");
|
||||
}
|
||||
|
||||
// Initialize Zero-Knowledge Authentication
|
||||
ESP_LOGI(TAG, "Initializing Zero-Knowledge Authentication...");
|
||||
if (zk_auth.init()) {
|
||||
ESP_LOGI(TAG, "ZK Auth initialized successfully");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "ZK Auth initialization failed");
|
||||
}
|
||||
|
||||
setup_wifi();
|
||||
server_http = setup_http_server();
|
||||
|
||||
if (server_http) {
|
||||
ESP_LOGI(TAG, "HTTP server listening on port 80");
|
||||
ESP_LOGI(TAG, " - ZK Auth UI: http://<ip>/");
|
||||
ESP_LOGI(TAG, " - Tang Server: http://<ip>/adv");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Main Loop ---
|
||||
void loop() {
|
||||
// Just delay - HTTP server handles requests in its own task
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
#endif // TANG_SERVER_H
|
||||
103
main/atecc608a.c
Normal file
103
main/atecc608a.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "atecc608a.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *TAG = "atecc608b";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Module-private state
|
||||
* ---------------------------------------------------------------------- */
|
||||
static ATCAIfaceCfg s_atecc_cfg;
|
||||
static uint8_t s_config_data[128];
|
||||
static bool s_config_valid = false;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Internal helpers
|
||||
* ---------------------------------------------------------------------- */
|
||||
static bool read_config_zone(void)
|
||||
{
|
||||
ATCA_STATUS status = atcab_read_config_zone(s_config_data);
|
||||
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGI(TAG, "Failed to read config zone (0x%02X) – check wiring", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
s_config_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public API
|
||||
* ---------------------------------------------------------------------- */
|
||||
bool atecc608B_init(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initialising ATECC608B...");
|
||||
|
||||
s_atecc_cfg.iface_type = ATCA_I2C_IFACE;
|
||||
s_atecc_cfg.devtype = ATECC608B;
|
||||
s_atecc_cfg.atcai2c.address = CONFIG_ATCA_I2C_ADDRESS;
|
||||
s_atecc_cfg.atcai2c.bus = 0;
|
||||
s_atecc_cfg.atcai2c.baud = CONFIG_ATCA_I2C_BAUD_RATE;
|
||||
s_atecc_cfg.wake_delay = 1500;
|
||||
s_atecc_cfg.rx_retries = 20;
|
||||
|
||||
ESP_LOGI(TAG, "SDA=GPIO%d SCL=GPIO%d addr=0x%02X (7-bit 0x%02X)",
|
||||
CONFIG_ATCA_I2C_SDA_PIN, CONFIG_ATCA_I2C_SCL_PIN,
|
||||
CONFIG_ATCA_I2C_ADDRESS, CONFIG_ATCA_I2C_ADDRESS >> 1);
|
||||
|
||||
ATCA_STATUS status = atcab_init(&s_atecc_cfg);
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "atcab_init failed: 0x%02X", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
status = atcab_wakeup();
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGW(TAG, "Wake returned 0x%02X (may be normal)", status);
|
||||
}
|
||||
atcab_idle();
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
|
||||
ESP_LOGI(TAG, "ATECC608B initialised");
|
||||
return true;
|
||||
}
|
||||
|
||||
void atecc608B_print_config(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "=== ATECC608B Configuration Zone ===");
|
||||
|
||||
uint8_t serial[9];
|
||||
ATCA_STATUS status = atcab_read_serial_number(serial);
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Failed to read serial number (0x%02X) – check wiring", status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!read_config_zone()) {
|
||||
return;
|
||||
}
|
||||
uint8_t *c = s_config_data;
|
||||
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (i % 16 == 0) printf("\n0x%02X: ", i);
|
||||
printf("%02X ", c[i]);
|
||||
}
|
||||
printf("\n\n");
|
||||
|
||||
ESP_LOGI(TAG, "=== End of ATECC608B Configuration ===");
|
||||
}
|
||||
|
||||
void atecc608B_release(void)
|
||||
{
|
||||
atcab_release();
|
||||
}
|
||||
414
main/atecc608a.h
414
main/atecc608a.h
@@ -2,417 +2,23 @@
|
||||
#define ATECC608B_H
|
||||
|
||||
#include "cryptoauthlib.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <cJSON.h>
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
static const char *TAG_ATECC = "ATECC608B";
|
||||
|
||||
// ATECC608B Configuration
|
||||
ATCAIfaceCfg atecc_cfg;
|
||||
|
||||
bool atecc608B_init() {
|
||||
ESP_LOGI(TAG_ATECC, "\n=== ATECC608B Initialization ===");
|
||||
|
||||
// Configure cryptoauthlib for ATECC608B - it will handle I2C initialization
|
||||
atecc_cfg.iface_type = ATCA_I2C_IFACE;
|
||||
atecc_cfg.devtype = ATECC608B;
|
||||
atecc_cfg.atcai2c.address = CONFIG_ATCA_I2C_ADDRESS;
|
||||
atecc_cfg.atcai2c.bus = 0; // I2C bus number
|
||||
atecc_cfg.atcai2c.baud = CONFIG_ATCA_I2C_BAUD_RATE;
|
||||
atecc_cfg.wake_delay = 1500;
|
||||
atecc_cfg.rx_retries = 20;
|
||||
|
||||
ESP_LOGI(TAG_ATECC,
|
||||
"Configuring ATECC608B: SDA=GPIO%d, SCL=GPIO%d, Address=0x%02X "
|
||||
"(7-bit: 0x%02X)",
|
||||
CONFIG_ATCA_I2C_SDA_PIN, CONFIG_ATCA_I2C_SCL_PIN,
|
||||
CONFIG_ATCA_I2C_ADDRESS, CONFIG_ATCA_I2C_ADDRESS >> 1);
|
||||
|
||||
// Initialize cryptoauthlib (it will initialize I2C internally)
|
||||
ATCA_STATUS status = atcab_init(&atecc_cfg);
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGE(TAG_ATECC, "ERROR: atcab_init failed with status 0x%02X", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG_ATECC, "ATECC608B initialized successfully");
|
||||
|
||||
// Give the device a moment to stabilize
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
// Wake the device and test communication
|
||||
status = atcab_wakeup();
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGW(TAG_ATECC, "WARNING: Wake command returned status 0x%02X", status);
|
||||
}
|
||||
|
||||
// Put device to idle
|
||||
atcab_idle();
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Global storage for config zone data
|
||||
uint8_t g_atecc_config_data[128] = {0};
|
||||
bool g_atecc_config_valid = false;
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Read ATECC608B configuration zone into global buffer
|
||||
* Initialise the ATECC608B over I2C.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool atecc608B_read_config() {
|
||||
ESP_LOGI(TAG_ATECC, "Reading ATECC608B configuration zone...");
|
||||
|
||||
ATCA_STATUS status = atcab_read_config_zone(g_atecc_config_data);
|
||||
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGE(TAG_ATECC, "ERROR: Failed to read config zone, status 0x%02X",
|
||||
status);
|
||||
|
||||
// Try reading it in blocks as a workaround
|
||||
ESP_LOGI(TAG_ATECC, "Attempting to read config in 4-byte blocks...");
|
||||
bool read_success = true;
|
||||
for (uint8_t block = 0; block < 32; block++) {
|
||||
status = atcab_read_zone(ATCA_ZONE_CONFIG, 0, block, 0,
|
||||
&g_atecc_config_data[block * 4], 4);
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGE(TAG_ATECC, "ERROR: Failed to read block %d, status 0x%02X",
|
||||
block, status);
|
||||
read_success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!read_success) {
|
||||
ESP_LOGE(TAG_ATECC, "ERROR: Could not read configuration zone");
|
||||
g_atecc_config_valid = false;
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG_ATECC, "Successfully read config in blocks");
|
||||
}
|
||||
|
||||
g_atecc_config_valid = true;
|
||||
return true;
|
||||
}
|
||||
bool atecc608B_init(void);
|
||||
|
||||
/**
|
||||
* Print ATECC608B configuration zone to console with detailed subzone breakdown
|
||||
* According to ATECC608B Table 2-4
|
||||
* Read the full configuration zone and print it to the console.
|
||||
* Useful for first-time setup diagnostics.
|
||||
*/
|
||||
void atecc608B_print_config() {
|
||||
ESP_LOGI(TAG_ATECC, "\n=== ATECC608B Configuration Zone ===");
|
||||
|
||||
// First, try to read the serial number as a simple communication test
|
||||
uint8_t serial_number[9];
|
||||
ATCA_STATUS status = atcab_read_serial_number(serial_number);
|
||||
|
||||
if (status != ATCA_SUCCESS) {
|
||||
ESP_LOGE(TAG_ATECC, "ERROR: Failed to read serial number, status 0x%02X",
|
||||
status);
|
||||
ESP_LOGE(TAG_ATECC, "This might indicate a communication or wiring issue.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read configuration zone into global buffer
|
||||
if (!atecc608B_read_config()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *config_data = g_atecc_config_data;
|
||||
|
||||
// Print complete hex dump first
|
||||
ESP_LOGI(TAG_ATECC, "\n--- Complete Configuration Zone (128 bytes) ---");
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (i % 16 == 0) {
|
||||
printf("\n0x%02X: ", i);
|
||||
}
|
||||
printf("%02X ", config_data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Print detailed subzone breakdown according to Table 2-4
|
||||
ESP_LOGI(TAG_ATECC, "\n--- Subzone Breakdown (Table 2-4) ---");
|
||||
|
||||
// Bytes 0-3: Serial Number[0:3]
|
||||
printf("\n[Bytes 0-3] Serial Number[0:3]: ");
|
||||
for (int i = 0; i < 4; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Bytes 4-7: Revision Number
|
||||
printf("[Bytes 4-7] Revision Number: ");
|
||||
for (int i = 4; i < 8; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Bytes 8-12: Serial Number[4:8]
|
||||
printf("[Bytes 8-12] Serial Number[4:8]: ");
|
||||
for (int i = 8; i < 13; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Full Serial Number
|
||||
printf(" --> Complete Serial Number: ");
|
||||
for (int i = 0; i < 4; i++)
|
||||
printf("%02X", config_data[i]);
|
||||
for (int i = 8; i < 13; i++)
|
||||
printf("%02X", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Byte 13: Reserved
|
||||
printf("[Byte 13] Reserved: %02X\n", config_data[13]);
|
||||
|
||||
// Byte 14: I2C_Enable
|
||||
printf("[Byte 14] I2C_Enable: %02X\n", config_data[14]);
|
||||
|
||||
// Byte 15: Reserved
|
||||
printf("[Byte 15] Reserved: %02X\n", config_data[15]);
|
||||
|
||||
// Byte 16: I2C_Address
|
||||
printf("[Byte 16] I2C_Address: 0x%02X (7-bit: 0x%02X)\n", config_data[16],
|
||||
config_data[16] >> 1);
|
||||
|
||||
// Byte 17: Reserved
|
||||
printf("[Byte 17] Reserved: %02X\n", config_data[17]);
|
||||
|
||||
// Byte 18: OTPmode
|
||||
printf("[Byte 18] OTPmode: 0x%02X\n", config_data[18]);
|
||||
|
||||
// Byte 19: ChipMode
|
||||
printf("[Byte 19] ChipMode: 0x%02X ", config_data[19]);
|
||||
if (config_data[19] & 0x01)
|
||||
printf("[I2C_UserExtraAdd] ");
|
||||
if (config_data[19] & 0x02)
|
||||
printf("[TTL_Enable] ");
|
||||
if (config_data[19] & 0x04)
|
||||
printf("[Watchdog_1.3s] ");
|
||||
printf("\n");
|
||||
|
||||
// Bytes 20-51: SlotConfig[0:15] (16 slots × 2 bytes)
|
||||
printf("\n[Bytes 20-51] SlotConfig[0:15]:\n");
|
||||
for (int slot = 0; slot < 16; slot++) {
|
||||
int offset = 20 + (slot * 2);
|
||||
uint8_t slot_config_low = config_data[offset];
|
||||
uint8_t slot_config_high = config_data[offset + 1];
|
||||
uint16_t slot_config = (slot_config_high << 8) | slot_config_low;
|
||||
printf(" Slot %2d [Bytes %2d-%2d]: 0x%02X 0x%02X (", slot, offset,
|
||||
offset + 1, slot_config_low, slot_config_high);
|
||||
|
||||
// Print 16-bit binary representation
|
||||
for (int b = 15; b >= 0; b--)
|
||||
printf("%d", (slot_config >> b) & 1);
|
||||
printf(")");
|
||||
|
||||
bool is_secret = (slot_config & 0x8000) != 0;
|
||||
bool encrypt_read = (slot_config & 0x4000) != 0;
|
||||
if (is_secret)
|
||||
printf(" [Secret]");
|
||||
if (encrypt_read)
|
||||
printf(" [EncryptRead]");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Bytes 52-59: Counter[0]
|
||||
printf("\n[Bytes 52-59] Counter[0]: ");
|
||||
for (int i = 52; i < 60; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Bytes 60-67: Counter[1]
|
||||
printf("[Bytes 60-67] Counter[1]: ");
|
||||
for (int i = 60; i < 68; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Bytes 68-83: LastKeyUse[0:15]
|
||||
printf("\n[Bytes 68-83] LastKeyUse[0:15]: ");
|
||||
for (int i = 68; i < 84; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Byte 84: UserExtra
|
||||
printf("\n[Byte 84] UserExtra: 0x%02X\n", config_data[84]);
|
||||
|
||||
// Byte 85: Selector
|
||||
printf("[Byte 85] Selector: 0x%02X\n", config_data[85]);
|
||||
|
||||
// Byte 86: LockValue (Data/OTP Zone Lock)
|
||||
printf("[Byte 86] LockValue (Data/OTP): 0x%02X %s\n", config_data[86],
|
||||
config_data[86] == 0x00 ? "[LOCKED]" : "[UNLOCKED]");
|
||||
|
||||
// Byte 87: LockConfig (Config Zone Lock)
|
||||
printf("[Byte 87] LockConfig: 0x%02X %s\n", config_data[87],
|
||||
config_data[87] == 0x00 ? "[LOCKED]" : "[UNLOCKED]");
|
||||
|
||||
// Bytes 88-89: SlotLocked
|
||||
printf("\n[Bytes 88-89] SlotLocked: %02X %02X\n", config_data[88],
|
||||
config_data[89]);
|
||||
|
||||
// Bytes 90-91: ChipOptions
|
||||
printf("[Bytes 90-91] ChipOptions: ");
|
||||
uint16_t chip_options = (config_data[91] << 8) | config_data[90];
|
||||
printf("0x%04X\n", chip_options);
|
||||
|
||||
// Bytes 92-95: X509format
|
||||
printf("[Bytes 92-95] X509format: ");
|
||||
for (int i = 92; i < 96; i++)
|
||||
printf("%02X ", config_data[i]);
|
||||
printf("\n");
|
||||
|
||||
// Bytes 96-127: KeyConfig[0:15] (16 slots × 2 bytes)
|
||||
printf("\n[Bytes 96-127] KeyConfig[0:15]:\n");
|
||||
for (int slot = 0; slot < 16; slot++) {
|
||||
int offset = 96 + (slot * 2);
|
||||
uint8_t key_config_low = config_data[offset];
|
||||
uint8_t key_config_high = config_data[offset + 1];
|
||||
uint16_t key_config = (key_config_high << 8) | key_config_low;
|
||||
printf(" Slot %2d [Bytes %2d-%2d]: 0x%02X 0x%02X (", slot, offset,
|
||||
offset + 1, key_config_low, key_config_high);
|
||||
|
||||
// Print 16-bit binary representation
|
||||
for (int b = 15; b >= 0; b--)
|
||||
printf("%d", (key_config >> b) & 1);
|
||||
printf(")");
|
||||
|
||||
bool is_private = (key_config & 0x0001) != 0;
|
||||
if (is_private)
|
||||
printf(" [Private]");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG_ATECC, "\n=== End of ATECC608B Configuration ===\n");
|
||||
}
|
||||
void atecc608B_print_config(void);
|
||||
|
||||
/**
|
||||
* Get ATECC608B configuration as JSON string (caller must free the returned
|
||||
* string)
|
||||
* Release cryptoauthlib resources.
|
||||
*/
|
||||
char *atecc608B_get_config_json() {
|
||||
if (!g_atecc_config_valid) {
|
||||
ESP_LOGE(TAG_ATECC,
|
||||
"Config data not valid. Call atecc608B_print_config() first.");
|
||||
return NULL;
|
||||
}
|
||||
void atecc608B_release(void);
|
||||
|
||||
uint8_t *config = g_atecc_config_data;
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
|
||||
// Add raw hex data
|
||||
char hex_str[385]; // 128 bytes * 3 chars per byte + null
|
||||
char *ptr = hex_str;
|
||||
for (int i = 0; i < 128; i++) {
|
||||
ptr += sprintf(ptr, "%02X ", config[i]);
|
||||
}
|
||||
cJSON_AddStringToObject(root, "raw_hex", hex_str);
|
||||
|
||||
// Serial Number
|
||||
char serial_str[19];
|
||||
sprintf(serial_str, "%02X%02X%02X%02X%02X%02X%02X%02X%02X", config[0],
|
||||
config[1], config[2], config[3], config[8], config[9], config[10],
|
||||
config[11], config[12]);
|
||||
cJSON_AddStringToObject(root, "serial_number", serial_str);
|
||||
|
||||
// Revision
|
||||
char revision_str[12];
|
||||
sprintf(revision_str, "%02X%02X%02X%02X", config[4], config[5], config[6],
|
||||
config[7]);
|
||||
cJSON_AddStringToObject(root, "revision", revision_str);
|
||||
|
||||
// I2C settings
|
||||
cJSON_AddNumberToObject(root, "i2c_enable", config[14]);
|
||||
cJSON_AddNumberToObject(root, "i2c_address", config[16]);
|
||||
|
||||
// Mode settings
|
||||
cJSON_AddNumberToObject(root, "otp_mode", config[18]);
|
||||
cJSON_AddNumberToObject(root, "chip_mode", config[19]);
|
||||
|
||||
// Lock status
|
||||
cJSON *locks = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(locks, "config_locked", config[87] == 0x00);
|
||||
cJSON_AddBoolToObject(locks, "data_otp_locked", config[86] == 0x00);
|
||||
cJSON_AddItemToObject(root, "locks", locks);
|
||||
|
||||
// Slot configurations
|
||||
cJSON *slots = cJSON_CreateArray();
|
||||
for (int slot = 0; slot < 16; slot++) {
|
||||
cJSON *slot_obj = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(slot_obj, "slot", slot);
|
||||
|
||||
int slot_config_offset = 20 + (slot * 2);
|
||||
uint8_t slot_config_low = config[slot_config_offset];
|
||||
uint8_t slot_config_high = config[slot_config_offset + 1];
|
||||
uint16_t slot_config = (slot_config_high << 8) | slot_config_low;
|
||||
|
||||
cJSON *slot_config_arr = cJSON_CreateArray();
|
||||
char slot_config_low_str[5];
|
||||
char slot_config_high_str[5];
|
||||
sprintf(slot_config_low_str, "0x%02X", slot_config_low);
|
||||
sprintf(slot_config_high_str, "0x%02X", slot_config_high);
|
||||
cJSON_AddItemToArray(slot_config_arr,
|
||||
cJSON_CreateString(slot_config_low_str));
|
||||
cJSON_AddItemToArray(slot_config_arr,
|
||||
cJSON_CreateString(slot_config_high_str));
|
||||
cJSON_AddItemToObject(slot_obj, "slot_config", slot_config_arr);
|
||||
|
||||
// Add 16-bit binary representation
|
||||
char slot_config_bin[18];
|
||||
for (int b = 0; b < 16; b++) {
|
||||
slot_config_bin[15 - b] = ((slot_config >> b) & 1) ? '1' : '0';
|
||||
}
|
||||
slot_config_bin[16] = '\0';
|
||||
cJSON_AddStringToObject(slot_obj, "slot_config_binary", slot_config_bin);
|
||||
|
||||
int key_config_offset = 96 + (slot * 2);
|
||||
uint8_t key_config_low = config[key_config_offset];
|
||||
uint8_t key_config_high = config[key_config_offset + 1];
|
||||
uint16_t key_config = (key_config_high << 8) | key_config_low;
|
||||
|
||||
cJSON *key_config_arr = cJSON_CreateArray();
|
||||
char key_config_low_str[5];
|
||||
char key_config_high_str[5];
|
||||
sprintf(key_config_low_str, "0x%02X", key_config_low);
|
||||
sprintf(key_config_high_str, "0x%02X", key_config_high);
|
||||
cJSON_AddItemToArray(key_config_arr,
|
||||
cJSON_CreateString(key_config_low_str));
|
||||
cJSON_AddItemToArray(key_config_arr,
|
||||
cJSON_CreateString(key_config_high_str));
|
||||
cJSON_AddItemToObject(slot_obj, "key_config", key_config_arr);
|
||||
|
||||
// Add 16-bit binary representation
|
||||
char key_config_bin[18];
|
||||
for (int b = 0; b < 16; b++) {
|
||||
key_config_bin[15 - b] = ((key_config >> b) & 1) ? '1' : '0';
|
||||
}
|
||||
key_config_bin[16] = '\0';
|
||||
cJSON_AddStringToObject(slot_obj, "key_config_binary", key_config_bin);
|
||||
|
||||
cJSON_AddBoolToObject(slot_obj, "is_secret", (slot_config & 0x8000) != 0);
|
||||
cJSON_AddBoolToObject(slot_obj, "encrypt_read",
|
||||
(slot_config & 0x4000) != 0);
|
||||
cJSON_AddBoolToObject(slot_obj, "is_private", (key_config & 0x0001) != 0);
|
||||
|
||||
cJSON_AddItemToArray(slots, slot_obj);
|
||||
}
|
||||
cJSON_AddItemToObject(root, "slots", slots);
|
||||
|
||||
// Additional fields
|
||||
cJSON_AddNumberToObject(root, "user_extra", config[84]);
|
||||
cJSON_AddNumberToObject(root, "selector", config[85]);
|
||||
|
||||
char *json_str = cJSON_Print(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
return json_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release ATECC608B resources
|
||||
*/
|
||||
void atecc608B_release() { atcab_release(); }
|
||||
|
||||
#endif // ATECC608B_H
|
||||
#endif /* ATECC608B_H */
|
||||
|
||||
281
main/main.c
Normal file
281
main/main.c
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* keypitecc – door controller
|
||||
*
|
||||
* Hardware:
|
||||
* GPIO 9 – Boot button (active-low, press = GND)
|
||||
* GPIO 15 – User LED (active-high, 1 = on)
|
||||
*
|
||||
* Button state machine (5-second window):
|
||||
* IDLE
|
||||
* └─ press ──► PENDING_OPEN (slow blink, will run SSH open command)
|
||||
* └─ press ──► PENDING_LOCK (fast blink, will run SSH lock command)
|
||||
* └─ press ──► IDLE (cancelled)
|
||||
* Any pending state expires after 5 s → command is executed → IDLE
|
||||
*
|
||||
* LED idle behaviour:
|
||||
* WiFi connected → LED ON
|
||||
* WiFi disconnected → LED OFF
|
||||
*/
|
||||
|
||||
#include "atecc608a.h"
|
||||
#include "ssh_client.h"
|
||||
#include "wifi.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/timers.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* GPIO defines
|
||||
* ---------------------------------------------------------------------- */
|
||||
#define GPIO_BTN 9 /* Boot button, active-low */
|
||||
#define GPIO_LED 15 /* User LED, active-high */
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Button / LED types
|
||||
* ---------------------------------------------------------------------- */
|
||||
typedef enum {
|
||||
BTN_STATE_IDLE,
|
||||
BTN_STATE_PENDING_OPEN,
|
||||
BTN_STATE_PENDING_LOCK,
|
||||
} btn_state_t;
|
||||
|
||||
typedef enum {
|
||||
LED_MODE_WIFI_STATUS, /* ON = connected, OFF = disconnected */
|
||||
LED_MODE_SLOW_BLINK, /* 500 ms on / 500 ms off (open pending) */
|
||||
LED_MODE_FAST_BLINK, /* 100 ms on / 100 ms off (lock pending) */
|
||||
} led_mode_t;
|
||||
|
||||
typedef enum {
|
||||
EVT_BUTTON_PRESS,
|
||||
EVT_TIMER_EXPIRED,
|
||||
} event_type_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Shared state
|
||||
* ---------------------------------------------------------------------- */
|
||||
static volatile led_mode_t s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
static volatile btn_state_t s_btn_state = BTN_STATE_IDLE;
|
||||
|
||||
static QueueHandle_t s_event_queue;
|
||||
static TimerHandle_t s_action_timer;
|
||||
|
||||
/* Debounce: ignore button edges within 200 ms of the previous one */
|
||||
static volatile TickType_t s_last_btn_tick = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* SSH task – spawned on demand so the button task stays responsive
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void ssh_task(void *arg)
|
||||
{
|
||||
const char *cmd = (const char *)arg;
|
||||
ESP_LOGI(TAG, "Executing SSH command: %s", cmd);
|
||||
bool ok = ssh_execute_command(cmd);
|
||||
ESP_LOGI(TAG, "SSH command %s: %s", cmd, ok ? "succeeded" : "FAILED");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void trigger_ssh(const char *cmd)
|
||||
{
|
||||
/* Stack size 16 kB – SSH needs heap + TLS, keep priority low */
|
||||
if (xTaskCreate(ssh_task, "ssh", 16384, (void *)cmd, 3, NULL) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create SSH task");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* FreeRTOS software timer callback – runs in timer daemon task context
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void action_timer_cb(TimerHandle_t xTimer)
|
||||
{
|
||||
(void)xTimer;
|
||||
event_type_t evt = EVT_TIMER_EXPIRED;
|
||||
xQueueSend(s_event_queue, &evt, 0);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* GPIO ISR
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void IRAM_ATTR button_isr(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
TickType_t now = xTaskGetTickCountFromISR();
|
||||
if ((now - s_last_btn_tick) < pdMS_TO_TICKS(200)) {
|
||||
return; /* debounce */
|
||||
}
|
||||
s_last_btn_tick = now;
|
||||
|
||||
event_type_t evt = EVT_BUTTON_PRESS;
|
||||
xQueueSendFromISR(s_event_queue, &evt, NULL);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Button task – owns the state machine
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void button_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
event_type_t evt;
|
||||
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_event_queue, &evt, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (s_btn_state) {
|
||||
|
||||
case BTN_STATE_IDLE:
|
||||
if (evt == EVT_BUTTON_PRESS) {
|
||||
ESP_LOGI(TAG, "Button: PENDING_OPEN in 5s (press again for LOCK, third press cancels)");
|
||||
s_btn_state = BTN_STATE_PENDING_OPEN;
|
||||
s_led_mode = LED_MODE_SLOW_BLINK;
|
||||
xTimerStart(s_action_timer, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_PENDING_OPEN:
|
||||
if (evt == EVT_BUTTON_PRESS) {
|
||||
ESP_LOGI(TAG, "Button: switching to PENDING_LOCK");
|
||||
s_btn_state = BTN_STATE_PENDING_LOCK;
|
||||
s_led_mode = LED_MODE_FAST_BLINK;
|
||||
xTimerReset(s_action_timer, 0); /* restart 5s from now */
|
||||
} else if (evt == EVT_TIMER_EXPIRED) {
|
||||
ESP_LOGI(TAG, "Timer expired: executing OPEN command");
|
||||
s_btn_state = BTN_STATE_IDLE;
|
||||
s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
trigger_ssh(CONFIG_SSH_OPEN_COMMAND);
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_PENDING_LOCK:
|
||||
if (evt == EVT_BUTTON_PRESS) {
|
||||
ESP_LOGI(TAG, "Button: action CANCELLED");
|
||||
xTimerStop(s_action_timer, 0);
|
||||
s_btn_state = BTN_STATE_IDLE;
|
||||
s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
} else if (evt == EVT_TIMER_EXPIRED) {
|
||||
ESP_LOGI(TAG, "Timer expired: executing LOCK command");
|
||||
s_btn_state = BTN_STATE_IDLE;
|
||||
s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
trigger_ssh(CONFIG_SSH_LOCK_COMMAND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* LED task – drives GPIO 15 according to the current led_mode
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void led_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
bool led_state = false;
|
||||
|
||||
for (;;) {
|
||||
led_mode_t mode = s_led_mode; /* atomic read (single-byte enum) */
|
||||
|
||||
switch (mode) {
|
||||
case LED_MODE_WIFI_STATUS:
|
||||
led_state = wifi_is_connected();
|
||||
gpio_set_level(GPIO_LED, led_state ? 0 : 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
break;
|
||||
|
||||
case LED_MODE_SLOW_BLINK:
|
||||
gpio_set_level(GPIO_LED, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
gpio_set_level(GPIO_LED, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
break;
|
||||
|
||||
case LED_MODE_FAST_BLINK:
|
||||
gpio_set_level(GPIO_LED, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(GPIO_LED, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* GPIO initialisation
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void gpio_init(void)
|
||||
{
|
||||
/* LED output */
|
||||
gpio_config_t led_cfg = {
|
||||
.pin_bit_mask = (1ULL << GPIO_LED),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&led_cfg);
|
||||
gpio_set_level(GPIO_LED, 1); /* off (active-high) */
|
||||
|
||||
/* Button input with interrupt on falling edge (active-low) */
|
||||
gpio_config_t btn_cfg = {
|
||||
.pin_bit_mask = (1ULL << GPIO_BTN),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_NEGEDGE,
|
||||
};
|
||||
gpio_config(&btn_cfg);
|
||||
|
||||
gpio_install_isr_service(0);
|
||||
gpio_isr_handler_add(GPIO_BTN, button_isr, NULL);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* app_main
|
||||
* ---------------------------------------------------------------------- */
|
||||
void app_main(void)
|
||||
{
|
||||
/* NVS (required by WiFi) */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
/* ATECC608B */
|
||||
if (!atecc608B_init()) {
|
||||
ESP_LOGW(TAG, "ATECC608B init failed – SSH authentication will not work");
|
||||
} else {
|
||||
atecc608B_print_config();
|
||||
ssh_print_public_key(); /* print key for authorized_keys setup */
|
||||
}
|
||||
|
||||
/* WiFi */
|
||||
wifi_init();
|
||||
|
||||
/* GPIO and event infrastructure */
|
||||
gpio_init();
|
||||
|
||||
s_event_queue = xQueueCreate(8, sizeof(event_type_t));
|
||||
configASSERT(s_event_queue);
|
||||
|
||||
/* 5-second one-shot timer (auto-reload disabled) */
|
||||
s_action_timer = xTimerCreate("action", pdMS_TO_TICKS(5000),
|
||||
pdFALSE, NULL, action_timer_cb);
|
||||
configASSERT(s_action_timer);
|
||||
|
||||
/* Tasks */
|
||||
xTaskCreate(button_task, "button", 4096, NULL, 5, NULL);
|
||||
xTaskCreate(led_task, "led", 2048, NULL, 4, NULL);
|
||||
|
||||
ESP_LOGI(TAG, "keypitecc ready – press the boot button to operate the door");
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* ESP32 Tang Server - Main Entry Point
|
||||
*/
|
||||
#include "TangServer.h"
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
setup();
|
||||
|
||||
while (true) {
|
||||
loop();
|
||||
}
|
||||
}
|
||||
327
main/ssh_client.c
Normal file
327
main/ssh_client.c
Normal file
@@ -0,0 +1,327 @@
|
||||
#include "ssh_client.h"
|
||||
|
||||
#include "cryptoauthlib.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <esp_log.h>
|
||||
#include <libssh2.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *TAG = "ssh_client";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* SSH blob / key helpers
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
/* Write a big-endian uint32 into buf. */
|
||||
static void write_be32(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format one ECDSA integer as an SSH mpint:
|
||||
* 4-byte big-endian length | optional 0x00 pad | value (leading zeros stripped)
|
||||
*
|
||||
* Returns number of bytes written to buf.
|
||||
*/
|
||||
static uint32_t write_mpint(uint8_t *buf, const uint8_t *val, uint32_t size)
|
||||
{
|
||||
uint32_t start = 0;
|
||||
while (start < (size - 1) && val[start] == 0x00) {
|
||||
start++;
|
||||
}
|
||||
|
||||
bool pad = (val[start] & 0x80) != 0;
|
||||
uint32_t len = (size - start) + (pad ? 1 : 0);
|
||||
|
||||
uint32_t off = 0;
|
||||
buf[off++] = (len >> 24) & 0xFF;
|
||||
buf[off++] = (len >> 16) & 0xFF;
|
||||
buf[off++] = (len >> 8) & 0xFF;
|
||||
buf[off++] = len & 0xFF;
|
||||
|
||||
if (pad) {
|
||||
buf[off++] = 0x00;
|
||||
}
|
||||
memcpy(&buf[off], &val[start], size - start);
|
||||
return off + (size - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a 104-byte SSH public key blob for ecdsa-sha2-nistp256:
|
||||
* [uint32 19] "ecdsa-sha2-nistp256"
|
||||
* [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.
|
||||
*/
|
||||
static void build_pubkey_blob(const uint8_t *atecc_pubkey, uint8_t *out_blob)
|
||||
{
|
||||
uint32_t off = 0;
|
||||
|
||||
/* Key type */
|
||||
const char *key_type = "ecdsa-sha2-nistp256";
|
||||
write_be32(&out_blob[off], 19); off += 4;
|
||||
memcpy(&out_blob[off], key_type, 19); off += 19;
|
||||
|
||||
/* Curve name */
|
||||
const char *curve = "nistp256";
|
||||
write_be32(&out_blob[off], 8); off += 4;
|
||||
memcpy(&out_blob[off], curve, 8); off += 8;
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* libssh2 signing callback (ATECC608B signs the hash)
|
||||
* ---------------------------------------------------------------------- */
|
||||
static int sign_callback(LIBSSH2_SESSION *session,
|
||||
unsigned char **sig, size_t *sig_len,
|
||||
const unsigned char *data, size_t data_len,
|
||||
void **abstract)
|
||||
{
|
||||
(void)session;
|
||||
(void)abstract;
|
||||
|
||||
uint8_t digest[32];
|
||||
uint8_t raw_sig[64];
|
||||
|
||||
/* 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!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Encode as [mpint R][mpint S] (no outer string wrapper) */
|
||||
unsigned char *buf = (unsigned char *)malloc(80);
|
||||
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 */
|
||||
|
||||
*sig = buf;
|
||||
*sig_len = off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Helpers for non-blocking libssh2
|
||||
* ---------------------------------------------------------------------- */
|
||||
static int waitsocket(int sock_fd, LIBSSH2_SESSION *session)
|
||||
{
|
||||
struct timeval tv = { .tv_sec = 10, .tv_usec = 0 };
|
||||
fd_set fd;
|
||||
FD_ZERO(&fd);
|
||||
FD_SET(sock_fd, &fd);
|
||||
|
||||
fd_set *rfd = NULL, *wfd = NULL;
|
||||
int dir = libssh2_session_block_directions(session);
|
||||
if (dir & LIBSSH2_SESSION_BLOCK_INBOUND) rfd = &fd;
|
||||
if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) wfd = &fd;
|
||||
|
||||
return select(sock_fd + 1, rfd, wfd, NULL, &tv);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public API
|
||||
* ---------------------------------------------------------------------- */
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t blob[104];
|
||||
build_pubkey_blob(raw_key, blob);
|
||||
|
||||
size_t b64_len = 0;
|
||||
mbedtls_base64_encode(NULL, 0, &b64_len, blob, sizeof(blob));
|
||||
|
||||
unsigned char *b64 = (unsigned char *)malloc(b64_len + 1);
|
||||
if (!b64) {
|
||||
ESP_LOGE(TAG, "malloc failed for base64 buffer");
|
||||
return;
|
||||
}
|
||||
mbedtls_base64_encode(b64, b64_len, &b64_len, blob, sizeof(blob));
|
||||
b64[b64_len] = '\0';
|
||||
|
||||
printf("\n=== SSH Public Key (add to authorized_keys) ===\n");
|
||||
printf("ecdsa-sha2-nistp256 %s keypitecc\n", b64);
|
||||
printf("================================================\n\n");
|
||||
|
||||
free(b64);
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
uint8_t pubkey_blob[104];
|
||||
build_pubkey_blob(raw_key, pubkey_blob);
|
||||
|
||||
/* --- TCP connect ------------------------------------------------------- */
|
||||
int rc;
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) {
|
||||
ESP_LOGE(TAG, "socket() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_in sin = {};
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(CONFIG_SSH_PORT);
|
||||
sin.sin_addr.s_addr = inet_addr(CONFIG_SSH_HOSTNAME);
|
||||
|
||||
if (sin.sin_addr.s_addr == 0xFFFFFFFF) {
|
||||
struct hostent *hp = gethostbyname(CONFIG_SSH_HOSTNAME);
|
||||
if (!hp) {
|
||||
ESP_LOGE(TAG, "gethostbyname(%s) failed", CONFIG_SSH_HOSTNAME);
|
||||
close(sock);
|
||||
return false;
|
||||
}
|
||||
sin.sin_addr.s_addr = ((struct ip4_addr *)hp->h_addr)->addr;
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
|
||||
ESP_LOGE(TAG, "connect to %s:%d failed", CONFIG_SSH_HOSTNAME, CONFIG_SSH_PORT);
|
||||
close(sock);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG, "TCP connected to %s:%d", CONFIG_SSH_HOSTNAME, CONFIG_SSH_PORT);
|
||||
|
||||
/* --- libssh2 session -------------------------------------------------- */
|
||||
rc = libssh2_init(0);
|
||||
if (rc) {
|
||||
ESP_LOGE(TAG, "libssh2_init failed: %d", rc);
|
||||
close(sock);
|
||||
return false;
|
||||
}
|
||||
|
||||
LIBSSH2_SESSION *session = libssh2_session_init();
|
||||
if (!session) {
|
||||
ESP_LOGE(TAG, "libssh2_session_init failed");
|
||||
libssh2_exit();
|
||||
close(sock);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = libssh2_session_handshake(session, sock);
|
||||
if (rc) {
|
||||
ESP_LOGE(TAG, "SSH handshake failed: %d", rc);
|
||||
goto cleanup_session;
|
||||
}
|
||||
ESP_LOGI(TAG, "SSH handshake OK");
|
||||
|
||||
/* --- Authenticate with ATECC608B hardware key ------------------------- */
|
||||
void *abstract = NULL;
|
||||
rc = libssh2_userauth_publickey(session, CONFIG_SSH_USERNAME,
|
||||
pubkey_blob, sizeof(pubkey_blob),
|
||||
sign_callback, &abstract);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "SSH public-key authentication failed: %d", rc);
|
||||
goto cleanup_session;
|
||||
}
|
||||
ESP_LOGI(TAG, "SSH authenticated as '%s'", CONFIG_SSH_USERNAME);
|
||||
|
||||
/* --- Open channel and execute command --------------------------------- */
|
||||
LIBSSH2_CHANNEL *channel;
|
||||
do {
|
||||
channel = libssh2_channel_open_session(session);
|
||||
if (!channel && libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) {
|
||||
waitsocket(sock, session);
|
||||
}
|
||||
} while (!channel);
|
||||
|
||||
if (!channel) {
|
||||
ESP_LOGE(TAG, "Failed to open SSH channel");
|
||||
goto cleanup_session;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Executing: %s", cmd);
|
||||
while ((rc = libssh2_channel_exec(channel, cmd)) == LIBSSH2_ERROR_EAGAIN) {
|
||||
waitsocket(sock, session);
|
||||
}
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "channel_exec failed: %d", rc);
|
||||
libssh2_channel_free(channel);
|
||||
goto cleanup_session;
|
||||
}
|
||||
|
||||
/* --- Read output ------------------------------------------------------- */
|
||||
char buf[256];
|
||||
int bytes;
|
||||
ESP_LOGI(TAG, "--- command output ---");
|
||||
for (;;) {
|
||||
do {
|
||||
bytes = libssh2_channel_read(channel, buf, sizeof(buf) - 1);
|
||||
} while (bytes == LIBSSH2_ERROR_EAGAIN && (waitsocket(sock, session), 1));
|
||||
|
||||
if (bytes <= 0) break;
|
||||
buf[bytes] = '\0';
|
||||
printf("%s", buf);
|
||||
}
|
||||
ESP_LOGI(TAG, "--- end output ---");
|
||||
|
||||
/* --- Shutdown --------------------------------------------------------- */
|
||||
while ((rc = libssh2_channel_send_eof(channel)) == LIBSSH2_ERROR_EAGAIN) {
|
||||
waitsocket(sock, session);
|
||||
}
|
||||
while (libssh2_channel_wait_eof(channel) == LIBSSH2_ERROR_EAGAIN) {
|
||||
waitsocket(sock, session);
|
||||
}
|
||||
while (libssh2_channel_wait_closed(channel) == LIBSSH2_ERROR_EAGAIN) {
|
||||
waitsocket(sock, session);
|
||||
}
|
||||
|
||||
int exit_status = libssh2_channel_get_exit_status(channel);
|
||||
ESP_LOGI(TAG, "Command exited with status: %d", exit_status);
|
||||
|
||||
libssh2_channel_free(channel);
|
||||
|
||||
libssh2_session_disconnect(session, "Bye");
|
||||
libssh2_session_free(session);
|
||||
libssh2_exit();
|
||||
close(sock);
|
||||
return (exit_status == 0);
|
||||
|
||||
cleanup_session:
|
||||
libssh2_session_disconnect(session, "Error");
|
||||
libssh2_session_free(session);
|
||||
libssh2_exit();
|
||||
close(sock);
|
||||
return false;
|
||||
}
|
||||
25
main/ssh_client.h
Normal file
25
main/ssh_client.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef SSH_CLIENT_H
|
||||
#define SSH_CLIENT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Output format:
|
||||
* ecdsa-sha2-nistp256 <base64-blob> keypitecc
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param cmd Shell command string to run on the remote host.
|
||||
* @return true on success, false on any error.
|
||||
*/
|
||||
bool ssh_execute_command(const char *cmd);
|
||||
|
||||
#endif /* SSH_CLIENT_H */
|
||||
81
main/wifi.c
Normal file
81
main/wifi.c
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "wifi.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "wifi";
|
||||
|
||||
static EventGroupHandle_t s_wifi_event_group;
|
||||
static const int WIFI_CONNECTED_BIT = BIT0;
|
||||
static volatile bool s_connected = false;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Event handler
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
ESP_LOGI(TAG, "WiFi connecting...");
|
||||
} else if (event_base == WIFI_EVENT &&
|
||||
event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
s_connected = false;
|
||||
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
esp_wifi_connect();
|
||||
ESP_LOGI(TAG, "WiFi disconnected, reconnecting...");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
ESP_LOGI(TAG, "WiFi connected, IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
s_connected = true;
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Public API
|
||||
* ---------------------------------------------------------------------- */
|
||||
void wifi_init(void)
|
||||
{
|
||||
s_wifi_event_group = xEventGroupCreate();
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||
configASSERT(sta_netif);
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, "keypitecc"));
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
&wifi_event_handler, NULL));
|
||||
|
||||
wifi_config_t wifi_config = {};
|
||||
strncpy((char *)wifi_config.sta.ssid, CONFIG_WIFI_SSID,
|
||||
sizeof(wifi_config.sta.ssid) - 1);
|
||||
strncpy((char *)wifi_config.sta.password, CONFIG_WIFI_PASSWORD,
|
||||
sizeof(wifi_config.sta.password) - 1);
|
||||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to SSID: %s", CONFIG_WIFI_SSID);
|
||||
}
|
||||
|
||||
bool wifi_is_connected(void)
|
||||
{
|
||||
return s_connected;
|
||||
}
|
||||
19
main/wifi.h
Normal file
19
main/wifi.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef WIFI_H
|
||||
#define WIFI_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Initialise WiFi in STA mode, connect to the SSID defined in Kconfig,
|
||||
* and register event handlers. Blocks until a first connection attempt
|
||||
* is made, but does NOT block forever – reconnection is handled in the
|
||||
* background.
|
||||
*/
|
||||
void wifi_init(void);
|
||||
|
||||
/**
|
||||
* Returns true when the station has a valid IP address (i.e. WiFi is up).
|
||||
*/
|
||||
bool wifi_is_connected(void);
|
||||
|
||||
#endif /* WIFI_H */
|
||||
411
main/zk_auth.h
411
main/zk_auth.h
@@ -1,411 +0,0 @@
|
||||
#ifndef ZK_AUTH_H
|
||||
#define ZK_AUTH_H
|
||||
|
||||
#include "cryptoauthlib.h"
|
||||
#include "host/atca_host.h"
|
||||
#include "libssh2_config.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "netdb.h"
|
||||
#include <cJSON.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_system.h>
|
||||
#include <libssh2.h>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/ecdh.h>
|
||||
#include <mbedtls/ecp.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/pkcs5.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
// Added network headers for ESP-IDF (lwIP) sockets
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define BUFSIZE 3200
|
||||
|
||||
class ZKAuth {
|
||||
private:
|
||||
mbedtls_ecp_group grp;
|
||||
mbedtls_mpi device_private_d;
|
||||
mbedtls_ecp_point device_public_Q;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
|
||||
uint8_t device_public_key[65]; // Uncompressed: 0x04 + X(32) + Y(32)
|
||||
uint8_t stored_password_hash[32]; // PBKDF2 hash of the correct password
|
||||
|
||||
bool initialized;
|
||||
|
||||
// Convert binary to hex string
|
||||
void bin_to_hex(const uint8_t *bin, size_t bin_len, char *hex) {
|
||||
for (size_t i = 0; i < bin_len; i++) {
|
||||
sprintf(hex + (i * 2), "%02x", bin[i]);
|
||||
}
|
||||
hex[bin_len * 2] = '\0';
|
||||
}
|
||||
|
||||
// Convert hex string to binary
|
||||
bool hex_to_bin(const char *hex, uint8_t *bin, size_t bin_len) {
|
||||
if (strlen(hex) != bin_len * 2)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < bin_len; i++) {
|
||||
if (sscanf(hex + (i * 2), "%2hhx", &bin[i]) != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
ZKAuth() : initialized(false) {
|
||||
mbedtls_ecp_group_init(&grp);
|
||||
mbedtls_mpi_init(&device_private_d);
|
||||
mbedtls_ecp_point_init(&device_public_Q);
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
}
|
||||
|
||||
~ZKAuth() {
|
||||
mbedtls_ecp_group_free(&grp);
|
||||
mbedtls_mpi_free(&device_private_d);
|
||||
mbedtls_ecp_point_free(&device_public_Q);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
}
|
||||
|
||||
// Initialize the ZK authentication system
|
||||
bool init() {
|
||||
if (initialized) {
|
||||
return true;
|
||||
} else {
|
||||
initialized = true;
|
||||
printf("\n=== ZK Authentication Initialized ===\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Marked as static so it can be passed as a C callback to libssh2
|
||||
// Helper to safely format SSH integers (mpint) exactly how OpenSSH demands
|
||||
static uint32_t write_mpint(uint8_t *buf, const uint8_t *val, uint32_t size) {
|
||||
uint32_t start = 0;
|
||||
|
||||
// Strip unnecessary leading zeros
|
||||
while (start < (size - 1) && val[start] == 0x00) {
|
||||
start++;
|
||||
}
|
||||
|
||||
// If the most significant bit is 1, we must prepend a 0x00 padding byte
|
||||
bool pad = (val[start] & 0x80) != 0;
|
||||
uint32_t len = (size - start) + (pad ? 1 : 0);
|
||||
|
||||
uint32_t offset = 0;
|
||||
buf[offset++] = (len >> 24) & 0xFF;
|
||||
buf[offset++] = (len >> 16) & 0xFF;
|
||||
buf[offset++] = (len >> 8) & 0xFF;
|
||||
buf[offset++] = len & 0xFF;
|
||||
|
||||
if (pad) {
|
||||
buf[offset++] = 0x00;
|
||||
}
|
||||
memcpy(&buf[offset], &val[start], size - start);
|
||||
return offset + (size - start);
|
||||
}
|
||||
|
||||
// The main signing callback
|
||||
static int sign_callback(LIBSSH2_SESSION *session, unsigned char **sig,
|
||||
size_t *sig_len, const unsigned char *data,
|
||||
size_t data_len, void **abstract) {
|
||||
uint8_t digest[32];
|
||||
uint8_t raw_sig[64];
|
||||
|
||||
// 1. Hash the fully assembled challenge buffer provided by libssh2
|
||||
mbedtls_sha256(data, data_len, digest, 0);
|
||||
|
||||
// 2. Request signature from the ATECC608B
|
||||
if (atcab_sign(0x0, digest, raw_sig) != ATCA_SUCCESS) {
|
||||
printf("CRITICAL: ATECC608B Signing Failed!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 3. Allocate memory JUST for the two mathematical integers (max ~74 bytes)
|
||||
unsigned char *buf = (unsigned char *)malloc(80);
|
||||
if (!buf)
|
||||
return -1;
|
||||
|
||||
// 4. Format strictly as [mpint R] [mpint S]. NO outer strings!
|
||||
uint32_t offset = 0;
|
||||
offset += write_mpint(&buf[offset], &raw_sig[0], 32); // Format R
|
||||
offset += write_mpint(&buf[offset], &raw_sig[32], 32); // Format S
|
||||
|
||||
// Hand ownership to libssh2
|
||||
*sig = buf;
|
||||
*sig_len = offset;
|
||||
|
||||
return 0; // Success!
|
||||
}
|
||||
|
||||
// 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 *atecc_pubkey,
|
||||
uint8_t *out_blob) {
|
||||
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], atecc_pubkey, 64);
|
||||
offset += 64;
|
||||
|
||||
memcpy(out_blob, ssh_blob, 104);
|
||||
}
|
||||
|
||||
static void my_trace_handler(LIBSSH2_SESSION *session, void *context,
|
||||
const char *data, size_t length) {
|
||||
// Print the trace data to the console
|
||||
printf("LIBSSH2_TRACE: %.*s", (int)length, data);
|
||||
}
|
||||
|
||||
static int waitsocket(int socket_fd, LIBSSH2_SESSION *session) {
|
||||
struct timeval timeout;
|
||||
int rc;
|
||||
fd_set fd;
|
||||
fd_set *writefd = NULL;
|
||||
fd_set *readfd = NULL;
|
||||
int dir;
|
||||
|
||||
timeout.tv_sec = 10;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
FD_ZERO(&fd);
|
||||
|
||||
FD_SET(socket_fd, &fd);
|
||||
|
||||
/* now make sure we wait in the correct direction */
|
||||
dir = libssh2_session_block_directions(session);
|
||||
|
||||
if (dir & LIBSSH2_SESSION_BLOCK_INBOUND)
|
||||
readfd = &fd;
|
||||
|
||||
if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)
|
||||
writefd = &fd;
|
||||
|
||||
rc = select(socket_fd + 1, readfd, writefd, NULL, &timeout);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Get device identity (for /api/identity endpoint)
|
||||
char *get_identity_json() {
|
||||
uint8_t atecc_pubkey[64]; // 65 bytes * 2 + null
|
||||
// Get public key from ATECC608B and convert to hex
|
||||
ATCA_STATUS status = atcab_get_pubkey(0, atecc_pubkey);
|
||||
if (status != ATCA_SUCCESS) {
|
||||
printf("Failed to read public key from ATECC608B: 0x%02X\n", status);
|
||||
}
|
||||
|
||||
uint8_t pubkey_blob[104];
|
||||
|
||||
generate_ssh_authorized_key(atecc_pubkey, pubkey_blob);
|
||||
|
||||
// 4. Base64 Encode the blob
|
||||
size_t b64_len = 0;
|
||||
mbedtls_base64_encode(NULL, 0, &b64_len, pubkey_blob, 104);
|
||||
unsigned char b64_out[b64_len];
|
||||
mbedtls_base64_encode(b64_out, b64_len, &b64_len, pubkey_blob, 104);
|
||||
printf("ecdsa-sha2-nistp256 %s esp32-atecc608b\n", b64_out);
|
||||
|
||||
// Concat type string with pubkey and identifier for authorized_keys format
|
||||
char authorized_keys_line[150]; // 19 + 1 + b64_len + 1 + 13 + 1 = 150
|
||||
snprintf(authorized_keys_line, sizeof(authorized_keys_line),
|
||||
"ecdsa-sha2-nistp256 %s esp32-atecc608b", b64_out);
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "sshPublicKey", authorized_keys_line);
|
||||
char *json_str = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
const char *username = "jonathan";
|
||||
|
||||
int sock;
|
||||
struct sockaddr_in sin;
|
||||
const char *hostname = "192.168.178.86";
|
||||
unsigned short port = 22;
|
||||
void *my_abstract = NULL;
|
||||
LIBSSH2_SESSION *session;
|
||||
|
||||
ESP_LOGI(TAG, "libssh2_version is %s", LIBSSH2_VERSION);
|
||||
int rc = libssh2_init(0);
|
||||
if (rc) {
|
||||
ESP_LOGE(TAG, "libssh2 initialization failed (%d)", rc);
|
||||
while (1) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "hostname=%s", hostname);
|
||||
ESP_LOGD(TAG, "port=%d", port);
|
||||
sin.sin_family = AF_INET;
|
||||
// sin.sin_port = htons(22);
|
||||
sin.sin_port = htons(port);
|
||||
sin.sin_addr.s_addr = inet_addr(hostname);
|
||||
ESP_LOGD(TAG, "sin.sin_addr.s_addr=%" PRIx32, sin.sin_addr.s_addr);
|
||||
if (sin.sin_addr.s_addr == 0xffffffff) {
|
||||
struct hostent *hp;
|
||||
hp = gethostbyname(hostname);
|
||||
if (hp == NULL) {
|
||||
ESP_LOGE(TAG, "gethostbyname fail");
|
||||
ESP_LOGE(TAG, "hostname=%s", hostname);
|
||||
ESP_LOGE(TAG, "port=%d", port);
|
||||
while (1) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
struct ip4_addr *ip4_addr;
|
||||
ip4_addr = (struct ip4_addr *)hp->h_addr;
|
||||
sin.sin_addr.s_addr = ip4_addr->addr;
|
||||
ESP_LOGD(TAG, "sin.sin_addr.s_addr=%" PRIx32, sin.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == -1) {
|
||||
ESP_LOGE(TAG, "failed to create socket!");
|
||||
while (1) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) !=
|
||||
0) {
|
||||
ESP_LOGE(TAG, "failed to connect!");
|
||||
ESP_LOGE(TAG, "hostname=%s", hostname);
|
||||
ESP_LOGE(TAG, "port=%d", port);
|
||||
while (1) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Connected. Starting SSH handshake...\n");
|
||||
|
||||
session = libssh2_session_init();
|
||||
if (!session) {
|
||||
ESP_LOGE(TAG, "failed to session init");
|
||||
while (1) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Enables extensive logging from libssh2
|
||||
// Needs debug logging enabled in menuconfig > components > libssh2
|
||||
// libssh2_trace(session, ~0);
|
||||
// libssh2_trace_sethandler(session, NULL, my_trace_handler);
|
||||
|
||||
printf("Session initialized. Setting timeout...\n");
|
||||
// libssh2_session_set_timeout(session, 10000);
|
||||
|
||||
printf("Performing handshake...\n");
|
||||
rc = libssh2_session_handshake(session, sock);
|
||||
if (rc) {
|
||||
ESP_LOGE(TAG, "Failure establishing SSH session: %d", rc);
|
||||
while (1) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Handshake completed. Authenticating with public key...\n");
|
||||
|
||||
// PUBLIC KEY AUTH: using custom callback
|
||||
rc = libssh2_userauth_publickey(session, username, pubkey_blob,
|
||||
sizeof(pubkey_blob), sign_callback,
|
||||
&my_abstract);
|
||||
|
||||
if (rc == 0) {
|
||||
printf("SSH authentication successful!\n");
|
||||
|
||||
// 1. Open a channel within the authenticated session
|
||||
LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(session);
|
||||
if (!channel) {
|
||||
printf("Failed to open a session channel!\n");
|
||||
} else {
|
||||
// 2. Execute the command
|
||||
const char *cmd = "ls -la";
|
||||
printf("Executing command: %s\n", cmd);
|
||||
|
||||
rc = libssh2_channel_exec(channel, cmd);
|
||||
if (rc != 0) {
|
||||
printf("Failed to execute command. Error: %d\n", rc);
|
||||
} else {
|
||||
printf("--- Command Output ---\n");
|
||||
|
||||
char buffer[256];
|
||||
int bytes_read;
|
||||
|
||||
// 3. Read the output in a loop until the channel closes (EOF)
|
||||
// libssh2_channel_read returns the amount of bytes read, 0 on EOF, or
|
||||
// <0 on error
|
||||
while ((bytes_read = libssh2_channel_read(channel, buffer,
|
||||
sizeof(buffer) - 1)) > 0) {
|
||||
buffer[bytes_read] =
|
||||
'\0'; // Null-terminate the chunk so printf handles it safely
|
||||
printf("%s", buffer);
|
||||
}
|
||||
|
||||
if (bytes_read < 0) {
|
||||
printf("\n[Read failed with error code: %d]\n", bytes_read);
|
||||
}
|
||||
printf("\n----------------------\n");
|
||||
}
|
||||
|
||||
// 4. Gracefully close the channel and grab the exit code (e.g., 0 for
|
||||
// success)
|
||||
libssh2_channel_close(channel);
|
||||
int exit_status = libssh2_channel_get_exit_status(channel);
|
||||
printf("Command exited with status: %d\n", exit_status);
|
||||
|
||||
// 5. Free the channel memory
|
||||
libssh2_channel_free(channel);
|
||||
}
|
||||
} else {
|
||||
printf("Authentication failed\n");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
libssh2_session_disconnect(session, "Bye");
|
||||
libssh2_session_free(session);
|
||||
close(sock);
|
||||
libssh2_exit();
|
||||
|
||||
return json_str;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ZK_AUTH_H
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef ZK_HANDLERS_H
|
||||
#define ZK_HANDLERS_H
|
||||
|
||||
#include "zk_auth.h"
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/ecdsa.h>
|
||||
#include <mbedtls/ecp.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/gcm.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/pkcs5.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/sha512.h>
|
||||
#include <string.h>
|
||||
|
||||
// Global ZK Auth instance (to be initialized in main)
|
||||
extern ZKAuth zk_auth;
|
||||
extern httpd_handle_t server_http;
|
||||
|
||||
// API endpoint: Get device identity
|
||||
static esp_err_t handle_zk_identity(httpd_req_t *req) {
|
||||
char *json_response = zk_auth.get_identity_json();
|
||||
if (json_response == NULL) {
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Failed to get identity");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||
httpd_resp_sendstr(req, json_response);
|
||||
free(json_response);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// API endpoint: Process unlock request
|
||||
// Handle CORS preflight
|
||||
// Register all ZK auth routes to the HTTP server
|
||||
void register_zk_handlers(httpd_handle_t server) {
|
||||
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);
|
||||
}
|
||||
|
||||
#endif // ZK_HANDLERS_H
|
||||
@@ -1,9 +1,13 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.5.2 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c6"
|
||||
CONFIG_WIFI_SSID="DoNotSetTheRealValueHere"
|
||||
CONFIG_WIFI_PASSWORD="PutTheRealPassInTheSdkconfigFile"
|
||||
CONFIG_IDF_TARGET="esp32c6"
|
||||
CONFIG_SSH_HOSTNAME="192.168.178.1"
|
||||
CONFIG_SSH_USERNAME="user"
|
||||
CONFIG_SSH_OPEN_COMMAND="open"
|
||||
CONFIG_SSH_LOCK_COMMAND="lock"
|
||||
CONFIG_ATECC608A_TCUSTOM=y
|
||||
CONFIG_ATCA_I2C_SDA_PIN=22
|
||||
CONFIG_ATCA_I2C_SCL_PIN=23
|
||||
|
||||
Reference in New Issue
Block a user