feat(firmware): OTA SHA-256 verification and captive portal URL decoding
- Add git-based version header generation for firmware builds - Implement SHA-256 hash verification for OTA downloads with mbedtls - Add URL decoding for captive portal form parsing (spaces, special chars) - Add mbedtls dependency for SHA-256 verification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7b325703bd
commit
f76ab62698
5 changed files with 101 additions and 9 deletions
|
|
@ -3,5 +3,30 @@ cmake_minimum_required(VERSION 3.16)
|
|||
# SPAXEL ESP32-S3 Firmware
|
||||
# WiFi CSI-based indoor positioning node
|
||||
|
||||
# Get version from git tags, fallback to 0.1.0
|
||||
execute_process(
|
||||
COMMAND git describe --tags --dirty 2>/dev/null
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
|
||||
if(GIT_VERSION)
|
||||
set(PROJECT_VERSION "${GIT_VERSION}")
|
||||
else()
|
||||
set(PROJECT_VERSION "0.1.0")
|
||||
endif()
|
||||
|
||||
message(STATUS "Building SPAXEL firmware version: ${PROJECT_VERSION}")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(spaxel-firmware)
|
||||
project(spaxel-firmware VERSION "${PROJECT_VERSION}")
|
||||
|
||||
# Generate version header
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main/version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/spaxel-firmware/main/version.h
|
||||
@ONLY
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ idf_component_register(
|
|||
"csi.c"
|
||||
"ble.c"
|
||||
"provision.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_wifi esp_netif nvs_flash mdns esp_http_client esp_timer bt driver log esp_http_server
|
||||
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}/spaxel-firmware/main"
|
||||
REQUIRES esp_wifi esp_netif nvs_flash mdns esp_http_client esp_timer bt driver log esp_http_server mbedtls
|
||||
)
|
||||
|
|
|
|||
4
firmware/main/version.h.in
Normal file
4
firmware/main/version.h.in
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Auto-generated at build time - do not edit
|
||||
#pragma once
|
||||
|
||||
#define SPAXEL_FIRMWARE_VERSION "@PROJECT_VERSION@"
|
||||
|
|
@ -7,11 +7,13 @@
|
|||
#include "esp_system.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semaphore.h"
|
||||
#include "cJSON.h"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// ESP-IDF WebSocket client
|
||||
#include "esp_websocket_client.h"
|
||||
|
|
@ -633,15 +635,29 @@ static void ota_task(void *arg) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Initialize SHA-256 for verification
|
||||
mbedtls_sha256_context sha_ctx;
|
||||
bool do_sha_verify = (strlen(s_ota_sha256) == 64);
|
||||
if (do_sha_verify) {
|
||||
mbedtls_sha256_init(&sha_ctx);
|
||||
mbedtls_sha256_starts(&sha_ctx, 0); // 0 = SHA-256
|
||||
}
|
||||
|
||||
// Download and write
|
||||
char *buf = malloc(4096);
|
||||
int total_read = 0;
|
||||
int read;
|
||||
|
||||
while ((read = esp_http_client_read(http, buf, 4096)) > 0) {
|
||||
// Update SHA-256 hash if verifying
|
||||
if (do_sha_verify) {
|
||||
mbedtls_sha256_update(&sha_ctx, (unsigned char *)buf, read);
|
||||
}
|
||||
|
||||
err = esp_ota_write(ota_handle, buf, read);
|
||||
if (err != ESP_OK) {
|
||||
free(buf);
|
||||
if (do_sha_verify) mbedtls_sha256_free(&sha_ctx);
|
||||
esp_ota_abort(ota_handle);
|
||||
esp_http_client_cleanup(http);
|
||||
websocket_send_ota_status("failed", 0, "write_failed");
|
||||
|
|
@ -660,7 +676,29 @@ static void ota_task(void *arg) {
|
|||
// Verify and complete
|
||||
websocket_send_ota_status("verifying", 100, NULL);
|
||||
|
||||
// SHA256 verification would go here if s_ota_sha256 is set
|
||||
// SHA-256 verification
|
||||
if (do_sha_verify) {
|
||||
unsigned char hash[32];
|
||||
mbedtls_sha256_finish(&sha_ctx, hash);
|
||||
mbedtls_sha256_free(&sha_ctx);
|
||||
|
||||
// Convert binary hash to hex string
|
||||
char hash_hex[65];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
sprintf(hash_hex + (i * 2), "%02x", hash[i]);
|
||||
}
|
||||
hash_hex[64] = '\0';
|
||||
|
||||
// Compare with expected hash (case-insensitive)
|
||||
if (strcasecmp(hash_hex, s_ota_sha256) != 0) {
|
||||
ESP_LOGE(TAG, "SHA-256 mismatch: expected %s, got %s", s_ota_sha256, hash_hex);
|
||||
esp_ota_abort(ota_handle);
|
||||
websocket_send_ota_status("failed", 0, "sha256_mismatch");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "SHA-256 verified: %s", hash_hex);
|
||||
}
|
||||
|
||||
err = esp_ota_end(ota_handle);
|
||||
if (err != ESP_OK) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
#include "lwip/sockets.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static const char *TAG = "wifi";
|
||||
|
||||
|
|
@ -297,6 +299,26 @@ static void captive_dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p,
|
|||
pbuf_free(p);
|
||||
}
|
||||
|
||||
// URL decode helper for captive portal form parsing
|
||||
static void url_decode(char *dst, const char *src, size_t dst_size) {
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
|
||||
while (src[i] && j < dst_size - 1) {
|
||||
if (src[i] == '+') {
|
||||
dst[j++] = ' ';
|
||||
i++;
|
||||
} else if (src[i] == '%' && isxdigit(src[i+1]) && isxdigit(src[i+2])) {
|
||||
char hex[3] = {src[i+1], src[i+2], 0};
|
||||
dst[j++] = (char)strtol(hex, NULL, 16);
|
||||
i += 3;
|
||||
} else {
|
||||
dst[j++] = src[i++];
|
||||
}
|
||||
}
|
||||
dst[j] = '\0';
|
||||
}
|
||||
|
||||
static esp_err_t captive_root_handler(httpd_req_t *req) {
|
||||
const char *html =
|
||||
"<!DOCTYPE html>"
|
||||
|
|
@ -338,17 +360,20 @@ static esp_err_t captive_save_handler(httpd_req_t *req) {
|
|||
char ssid[33] = {0};
|
||||
char password[65] = {0};
|
||||
char ms_ip[47] = {0};
|
||||
char decoded[128];
|
||||
|
||||
// Simple parsing (url-encoded)
|
||||
// Parse URL-encoded form data
|
||||
char *p = strtok(buf, "&");
|
||||
while (p) {
|
||||
if (strncmp(p, "ssid=", 5) == 0) {
|
||||
// URL decode would go here, simplified for now
|
||||
strncpy(ssid, p + 5, sizeof(ssid) - 1);
|
||||
url_decode(decoded, p + 5, sizeof(decoded));
|
||||
strncpy(ssid, decoded, sizeof(ssid) - 1);
|
||||
} else if (strncmp(p, "password=", 9) == 0) {
|
||||
strncpy(password, p + 9, sizeof(password) - 1);
|
||||
url_decode(decoded, p + 9, sizeof(decoded));
|
||||
strncpy(password, decoded, sizeof(password) - 1);
|
||||
} else if (strncmp(p, "ms_ip=", 6) == 0) {
|
||||
strncpy(ms_ip, p + 6, sizeof(ms_ip) - 1);
|
||||
url_decode(decoded, p + 6, sizeof(decoded));
|
||||
strncpy(ms_ip, decoded, sizeof(ms_ip) - 1);
|
||||
}
|
||||
p = strtok(NULL, "&");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue