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_task_wdt.h"
|
||||||
#include "esp_pm.h"
|
#include "esp_pm.h"
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
|
#include "esp_random.h"
|
||||||
#include "esp_ota_ops.h"
|
#include "esp_ota_ops.h"
|
||||||
#include "esp_https_ota.h"
|
#include "esp_https_ota.h"
|
||||||
#include "esp_partition.h"
|
#include "esp_partition.h"
|
||||||
@@ -222,6 +223,8 @@ static int64_t s_alert_heap_last = 0;
|
|||||||
|
|
||||||
/* --- NVS helpers --- */
|
/* --- NVS helpers --- */
|
||||||
|
|
||||||
|
static esp_err_t config_save_str(const char *key, const char *value);
|
||||||
|
|
||||||
static void config_load_nvs(void)
|
static void config_load_nvs(void)
|
||||||
{
|
{
|
||||||
/* Start with Kconfig defaults */
|
/* Start with Kconfig defaults */
|
||||||
@@ -340,6 +343,18 @@ static void config_load_nvs(void)
|
|||||||
ESP_LOGI(TAG, "NVS: no saved config, using defaults");
|
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 */
|
/* Boot counter — always increment, even on first boot */
|
||||||
nvs_handle_t bh;
|
nvs_handle_t bh;
|
||||||
if (nvs_open("csi_config", NVS_READWRITE, &bh) == ESP_OK) {
|
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;
|
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 --- */
|
/* --- Command handler --- */
|
||||||
|
|
||||||
static void reboot_after_delay(void *arg)
|
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);
|
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;
|
int reply_len;
|
||||||
if (verified) {
|
|
||||||
reply_len = cmd_handle(verified, reply_buf, sizeof(reply_buf));
|
if (strncmp(rx_buf, "HMAC:", 5) == 0) {
|
||||||
} else {
|
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);
|
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,
|
sendto(sock, reply_buf, reply_len, 0,
|
||||||
(struct sockaddr *)&src_addr, src_len);
|
(struct sockaddr *)&src_addr, src_len);
|
||||||
|
|||||||
@@ -82,3 +82,8 @@ CONFIG_ESP_WIFI_IRAM_OPT=n
|
|||||||
#
|
#
|
||||||
CONFIG_PM_ENABLE=y
|
CONFIG_PM_ENABLE=y
|
||||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||||
|
|
||||||
|
#
|
||||||
|
# WiFi Authentication (reject open/WEP APs)
|
||||||
|
#
|
||||||
|
CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK=y
|
||||||
|
|||||||
Reference in New Issue
Block a user