Decouple user provisioning from HTTPS identity storage while retaining compatible v1 user records and migrating TLS material to the credential-free v2 format. Add focused security regression coverage and update operator documentation.
323 lines
13 KiB
C
323 lines
13 KiB
C
#include "driver/uart.h"
|
|
#include "admin_ssh_console.h"
|
|
#include "console_completion.h"
|
|
#include "esp_console.h"
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "esp_psram.h"
|
|
#include "network_console.h"
|
|
#include "local_display.h"
|
|
#include "local_boot_animation.h"
|
|
#include "local_status_ui.h"
|
|
#include "local_ui_config.h"
|
|
#include "local_ui_console.h"
|
|
#include "local_ui_hw_test.h"
|
|
#include "mdns_config.h"
|
|
#include "mdns_console.h"
|
|
#include "mdns_service.h"
|
|
#include "rs232_hw_test.h"
|
|
#include "rs232_port_owner.h"
|
|
#include "secure_random.h"
|
|
#include "serial_config.h"
|
|
#include "serial_console.h"
|
|
#include "serial_service.h"
|
|
#include "session_broker.h"
|
|
#include "session_console.h"
|
|
#include "ssh_console.h"
|
|
#include "ssh_security.h"
|
|
#include "ssh_transport.h"
|
|
#include "status_led.h"
|
|
#include "system_console.h"
|
|
#include "usb_cdc_transport.h"
|
|
#include "usb_console.h"
|
|
#include "user_console.h"
|
|
#include "user_database.h"
|
|
#include "web_console.h"
|
|
#include "web_security.h"
|
|
#include "web_server.h"
|
|
#include "wifi_config.h"
|
|
#include "wifi_console.h"
|
|
#include "wifi_manager.h"
|
|
|
|
#define CONSOLE_BAUD_RATE 115200
|
|
#define CONSOLE_TX_GPIO 43
|
|
#define CONSOLE_RX_GPIO 44
|
|
|
|
static const char *TAG = "firmware";
|
|
|
|
void app_main(void)
|
|
{
|
|
|
|
if (esp_psram_is_initialized()) {
|
|
ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size());
|
|
} else {
|
|
ESP_LOGW(TAG, "PSRAM is not initialized");
|
|
}
|
|
|
|
/* Seed the sole device DRBG before any future RF, Bluetooth, or ADC use. */
|
|
esp_err_t random_error = secure_random_init();
|
|
if (random_error != ESP_OK) {
|
|
ESP_LOGE(TAG, "Secure random initialization failed: %s",
|
|
esp_err_to_name(random_error));
|
|
}
|
|
|
|
/* Blue means the firmware is initialized and waiting for a console command. */
|
|
ESP_ERROR_CHECK(status_led_init());
|
|
ESP_ERROR_CHECK(rs232_port_owner_init());
|
|
ESP_ERROR_CHECK(rs232_hw_test_init());
|
|
/* Reserve the shared UART0 dispatcher before optional SSH/network services. */
|
|
ESP_ERROR_CHECK(admin_ssh_console_init());
|
|
|
|
/* The optional display can fail without affecting UART0 or serial transports. */
|
|
esp_err_t local_display_error = local_display_init();
|
|
if (local_display_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "Local display bus unavailable: %s",
|
|
esp_err_to_name(local_display_error));
|
|
} else {
|
|
local_display_error = local_display_start();
|
|
if (local_display_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "Local display unavailable: %s",
|
|
esp_err_to_name(local_display_error));
|
|
} else {
|
|
local_display_error = local_boot_animation_play();
|
|
if (local_display_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "Local boot animation unavailable: %s",
|
|
esp_err_to_name(local_display_error));
|
|
}
|
|
}
|
|
}
|
|
|
|
esp_err_t local_ui_error = local_ui_hw_test_init();
|
|
if (local_ui_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "Local UI button diagnostics unavailable: %s",
|
|
esp_err_to_name(local_ui_error));
|
|
}
|
|
|
|
local_ui_config_t local_ui_config;
|
|
bool used_stored_local_ui_config = false;
|
|
esp_err_t local_ui_config_error =
|
|
local_ui_config_load(&local_ui_config, &used_stored_local_ui_config);
|
|
if (local_ui_config_error != ESP_OK) {
|
|
local_ui_config_defaults(&local_ui_config);
|
|
ESP_LOGW(TAG,
|
|
"NVS local UI configuration unavailable (%s); using RAM defaults",
|
|
esp_err_to_name(local_ui_config_error));
|
|
}
|
|
|
|
serial_config_t serial_config;
|
|
bool used_stored_config = false;
|
|
esp_err_t config_error = serial_config_load(&serial_config, &used_stored_config);
|
|
if (config_error != ESP_OK) {
|
|
serial_config_defaults(&serial_config);
|
|
ESP_LOGW(
|
|
TAG,
|
|
"NVS serial configuration unavailable (%s); using RAM defaults without erasing storage",
|
|
esp_err_to_name(config_error));
|
|
}
|
|
ESP_ERROR_CHECK(serial_service_init(&serial_config));
|
|
ESP_ERROR_CHECK(session_broker_init());
|
|
/* Native USB owns GPIO19/20; UART0 logging stays on the USB-to-UART bridge. */
|
|
ESP_ERROR_CHECK(usb_cdc_transport_init());
|
|
|
|
/* Provision HTTPS identity before Wi-Fi starts; failures leave UART/USB recovery intact. */
|
|
web_security_load_result_t web_security_source = WEB_SECURITY_LOAD_STORED;
|
|
esp_err_t web_security_error = random_error;
|
|
if (web_security_error == ESP_OK) {
|
|
web_security_error = web_security_init(&web_security_source);
|
|
}
|
|
if (web_security_error != ESP_OK) {
|
|
ESP_LOGE(TAG,
|
|
"HTTPS security material unavailable (%s); use UART0 'web reset --force' to replace it",
|
|
esp_err_to_name(web_security_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Using %s HTTPS identity",
|
|
web_security_source == WEB_SECURITY_LOAD_STORED
|
|
? "stored"
|
|
: (web_security_source == WEB_SECURITY_LOAD_MIGRATED_V1
|
|
? "migrated v1"
|
|
: "newly generated"));
|
|
}
|
|
|
|
user_database_load_result_t user_database_source = USER_DATABASE_LOAD_EMPTY;
|
|
esp_err_t user_database_error = user_database_init(&user_database_source);
|
|
if (user_database_error != ESP_OK) {
|
|
ESP_LOGE(TAG, "User database unavailable: %s; HTTPS and SSH authentication will fail closed; use UART0 'user recover --force'",
|
|
esp_err_to_name(user_database_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Using %s user database",
|
|
user_database_source == USER_DATABASE_LOAD_STORED
|
|
? "stored"
|
|
: "new empty");
|
|
}
|
|
|
|
esp_err_t web_runtime_error = web_server_init();
|
|
if (web_runtime_error != ESP_OK) {
|
|
ESP_LOGE(TAG, "HTTPS runtime initialization failed: %s",
|
|
esp_err_to_name(web_runtime_error));
|
|
}
|
|
|
|
ssh_security_load_result_t ssh_security_source = SSH_SECURITY_LOAD_STORED;
|
|
esp_err_t ssh_security_error = random_error;
|
|
if (ssh_security_error == ESP_OK) {
|
|
ssh_security_error = ssh_security_init(&ssh_security_source);
|
|
}
|
|
if (ssh_security_error != ESP_OK) {
|
|
ESP_LOGE(TAG,
|
|
"SSH host key unavailable (%s); use UART0 'ssh reset --force' to replace it",
|
|
esp_err_to_name(ssh_security_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Using %s SSH host key",
|
|
ssh_security_source == SSH_SECURITY_LOAD_STORED
|
|
? "stored"
|
|
: "newly generated");
|
|
}
|
|
|
|
esp_err_t ssh_runtime_error = ssh_transport_init();
|
|
if (ssh_runtime_error != ESP_OK) {
|
|
ESP_LOGE(TAG, "SSH runtime initialization failed: %s",
|
|
esp_err_to_name(ssh_runtime_error));
|
|
}
|
|
|
|
wifi_app_config_t wifi_config;
|
|
wifi_config_load_source_t wifi_config_source;
|
|
esp_err_t wifi_config_error = random_error;
|
|
if (wifi_config_error == ESP_OK) {
|
|
wifi_config_error = wifi_config_load(&wifi_config, &wifi_config_source);
|
|
}
|
|
if (wifi_config_error != ESP_OK) {
|
|
ESP_LOGW(TAG,
|
|
"NVS Wi-Fi configuration unavailable (%s); trying generated RAM defaults",
|
|
esp_err_to_name(wifi_config_error));
|
|
wifi_config_error = wifi_config_defaults(&wifi_config);
|
|
} else if (wifi_config_source == WIFI_CONFIG_LOAD_GENERATED_MISSING) {
|
|
/* Persist the random per-device fallback-AP credential on true first boot. */
|
|
esp_err_t save_error = wifi_config_save(&wifi_config);
|
|
if (save_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "Could not persist initial Wi-Fi defaults: %s",
|
|
esp_err_to_name(save_error));
|
|
}
|
|
} else if (wifi_config_source == WIFI_CONFIG_LOAD_GENERATED_INVALID) {
|
|
ESP_LOGW(TAG,
|
|
"Stored Wi-Fi configuration is incompatible; using RAM defaults without overwriting it");
|
|
}
|
|
|
|
mdns_config_t mdns_config;
|
|
bool used_stored_mdns_config = false;
|
|
esp_err_t mdns_config_error = mdns_config_load(&mdns_config, &used_stored_mdns_config);
|
|
if (mdns_config_error != ESP_OK) {
|
|
mdns_config_defaults(&mdns_config);
|
|
ESP_LOGW(TAG, "NVS mDNS configuration unavailable (%s); using RAM defaults",
|
|
esp_err_to_name(mdns_config_error));
|
|
}
|
|
esp_err_t mdns_service_error = mdns_service_init(&mdns_config);
|
|
if (mdns_service_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "mDNS configuration service unavailable: %s; Wi-Fi will continue",
|
|
esp_err_to_name(mdns_service_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Using %s mDNS suffix sak-%s.local",
|
|
used_stored_mdns_config ? "stored" : "default", mdns_config.suffix);
|
|
}
|
|
|
|
esp_err_t wifi_error = wifi_config_error;
|
|
if (wifi_config_error == ESP_OK) {
|
|
wifi_error = wifi_manager_init(&wifi_config);
|
|
if (wifi_error == ESP_OK && wifi_config.enabled_at_boot != 0U) {
|
|
wifi_error = wifi_manager_start();
|
|
}
|
|
if (wifi_error != ESP_OK) {
|
|
/* UART0 and native USB remain recovery paths if networking is unavailable. */
|
|
ESP_LOGE(TAG, "Wi-Fi manager unavailable: %s", esp_err_to_name(wifi_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Using %s Wi-Fi configuration; AP policy=%s",
|
|
wifi_config_source == WIFI_CONFIG_LOAD_STORED
|
|
? "stored"
|
|
: "generated default",
|
|
wifi_config_ap_policy_to_string(wifi_config.ap_policy));
|
|
}
|
|
} else {
|
|
ESP_LOGE(TAG, "Could not create a valid Wi-Fi configuration: %s",
|
|
esp_err_to_name(wifi_config_error));
|
|
}
|
|
wifi_config_secure_wipe(&wifi_config, sizeof(wifi_config));
|
|
|
|
if (wifi_error == ESP_OK && web_security_error == ESP_OK &&
|
|
web_runtime_error == ESP_OK) {
|
|
esp_err_t start_error = web_server_start();
|
|
if (start_error != ESP_OK) {
|
|
ESP_LOGE(TAG, "HTTPS startup failed: %s; UART0 recovery remains available",
|
|
esp_err_to_name(start_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Authenticated HTTPS listening on TCP port 443");
|
|
}
|
|
}
|
|
if (wifi_error == ESP_OK && ssh_security_error == ESP_OK &&
|
|
ssh_runtime_error == ESP_OK) {
|
|
esp_err_t start_error = ssh_transport_start();
|
|
if (start_error != ESP_OK) {
|
|
ESP_LOGE(TAG, "SSH startup failed: %s; UART0 recovery remains available",
|
|
esp_err_to_name(start_error));
|
|
} else {
|
|
ESP_LOGI(TAG, "Authenticated SSH listening on TCP port %u",
|
|
SSH_TRANSPORT_PORT);
|
|
}
|
|
}
|
|
|
|
if (local_ui_error == ESP_OK) {
|
|
esp_err_t local_status_ui_error = local_status_ui_start(&local_ui_config);
|
|
if (local_status_ui_error != ESP_OK) {
|
|
ESP_LOGW(TAG, "Local status UI unavailable: %s",
|
|
esp_err_to_name(local_status_ui_error));
|
|
}
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Using %s local UI configuration",
|
|
used_stored_local_ui_config ? "stored" : "default");
|
|
ESP_LOGI(
|
|
TAG,
|
|
"Using %s serial configuration; UART service starts on 'serial start' or native USB open",
|
|
used_stored_config ? "stored" : "default");
|
|
|
|
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
|
repl_config.prompt = "serial-tool> ";
|
|
repl_config.max_cmdline_length = ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY;
|
|
/* The stock REPL task remains dormant; our shared frontend owns line dispatch. */
|
|
repl_config.task_stack_size = 2048;
|
|
|
|
/*
|
|
* UART0 remains dedicated to development and diagnostics. The external
|
|
* RS-232 data path uses UART1 on GPIO17/18 and cannot disturb this REPL.
|
|
*/
|
|
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
|
uart_config.channel = UART_NUM_0;
|
|
uart_config.baud_rate = CONSOLE_BAUD_RATE;
|
|
uart_config.tx_gpio_num = CONSOLE_TX_GPIO;
|
|
uart_config.rx_gpio_num = CONSOLE_RX_GPIO;
|
|
|
|
esp_console_repl_t *repl = NULL;
|
|
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
|
|
|
/* The REPL constructor initializes esp_console and installs `help`. */
|
|
ESP_ERROR_CHECK(rs232_hw_test_register_console_commands());
|
|
ESP_ERROR_CHECK(local_ui_console_register_commands());
|
|
ESP_ERROR_CHECK(serial_console_register_commands());
|
|
ESP_ERROR_CHECK(session_console_register_commands());
|
|
ESP_ERROR_CHECK(usb_console_register_commands());
|
|
ESP_ERROR_CHECK(user_console_register_commands());
|
|
ESP_ERROR_CHECK(wifi_console_register_commands());
|
|
if (mdns_service_error == ESP_OK) {
|
|
ESP_ERROR_CHECK(mdns_console_register_commands());
|
|
}
|
|
ESP_ERROR_CHECK(web_console_register_commands());
|
|
ESP_ERROR_CHECK(ssh_console_register_commands());
|
|
ESP_ERROR_CHECK(network_console_register_root_commands());
|
|
ESP_ERROR_CHECK(system_console_register_commands());
|
|
ESP_ERROR_CHECK(admin_ssh_console_register_commands());
|
|
/* Upgrade late UART terminals safely and add nested completion. */
|
|
console_completion_install();
|
|
ESP_ERROR_CHECK(admin_ssh_console_start_uart_frontend());
|
|
|
|
ESP_LOGI(TAG, "Shared UART0/SSH administration console ready at %d baud",
|
|
CONSOLE_BAUD_RATE);
|
|
ESP_LOGI(TAG, "Type 'help' for commands; native USB starts UART1 only when its host port opens");
|
|
}
|