feat: Auto-generate auth secret and enforce HMAC on privileged commands

- Generate 128-bit random auth secret on first boot via hardware RNG,
  persist to NVS, log to serial for retrieval
- Gate destructive commands (OTA, FACTORY, REBOOT, TARGET, AUTH,
  HOSTNAME set) behind HMAC authentication
- Read-only and operational commands remain open for monitoring
- Require WPA2/WPA3 for WiFi AP association (reject open/WEP)
This commit is contained in:
user
2026-02-14 18:36:31 +01:00
parent 00b3372a6d
commit ebc8a00b46
2 changed files with 52 additions and 4 deletions

View File

@@ -33,6 +33,7 @@
#include "esp_task_wdt.h"
#include "esp_pm.h"
#include "esp_heap_caps.h"
#include "esp_random.h"
#include "esp_ota_ops.h"
#include "esp_https_ota.h"
#include "esp_partition.h"
@@ -222,6 +223,8 @@ static int64_t s_alert_heap_last = 0;
/* --- NVS helpers --- */
static esp_err_t config_save_str(const char *key, const char *value);
static void config_load_nvs(void)
{
/* Start with Kconfig defaults */
@@ -340,6 +343,18 @@ static void config_load_nvs(void)
ESP_LOGI(TAG, "NVS: no saved config, using defaults");
}
/* Auto-generate auth secret on first boot */
if (s_auth_secret[0] == '\0') {
uint8_t rand_bytes[16];
esp_fill_random(rand_bytes, sizeof(rand_bytes));
for (int i = 0; i < 16; i++) {
snprintf(s_auth_secret + i * 2, 3, "%02x", rand_bytes[i]);
}
s_auth_secret[32] = '\0';
config_save_str("auth_secret", s_auth_secret);
ESP_LOGW(TAG, "AUTH: generated secret: %s (note this for remote access)", s_auth_secret);
}
/* Boot counter — always increment, even on first boot */
nvs_handle_t bh;
if (nvs_open("csi_config", NVS_READWRITE, &bh) == ESP_OK) {
@@ -1437,6 +1452,19 @@ static const char *auth_verify(const char *input, char *reply, size_t reply_size
return cmd;
}
/* --- Privileged command check --- */
static bool cmd_requires_auth(const char *cmd)
{
if (strncmp(cmd, "OTA ", 4) == 0) return true;
if (strcmp(cmd, "FACTORY") == 0) return true;
if (strcmp(cmd, "REBOOT") == 0) return true;
if (strncmp(cmd, "TARGET ", 7) == 0) return true;
if (strncmp(cmd, "AUTH ", 5) == 0) return true;
if (strncmp(cmd, "HOSTNAME ", 9) == 0) return true;
return false;
}
/* --- Command handler --- */
static void reboot_after_delay(void *arg)
@@ -2443,12 +2471,27 @@ static void cmd_task(void *arg)
ESP_LOGI(TAG, "CMD rx: \"%s\"", rx_buf);
const char *verified = auth_verify(rx_buf, reply_buf, sizeof(reply_buf));
/* Authenticate: HMAC grants full access; plain commands are read-only */
const char *cmd = rx_buf;
bool authed = false;
int reply_len;
if (verified) {
reply_len = cmd_handle(verified, reply_buf, sizeof(reply_buf));
} else {
if (strncmp(rx_buf, "HMAC:", 5) == 0) {
cmd = auth_verify(rx_buf, reply_buf, sizeof(reply_buf));
if (cmd) {
authed = true;
}
} else if (s_auth_secret[0] == '\0') {
authed = true;
}
if (!cmd) {
/* HMAC verification failed — error set by auth_verify */
reply_len = strlen(reply_buf);
} else if (!authed && cmd_requires_auth(cmd)) {
reply_len = snprintf(reply_buf, sizeof(reply_buf), "ERR AUTH required");
} else {
reply_len = cmd_handle(cmd, reply_buf, sizeof(reply_buf));
}
sendto(sock, reply_buf, reply_len, 0,
(struct sockaddr *)&src_addr, src_len);