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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user