diff --git a/firmware/lib/ota_updater/ota_updater.cpp b/firmware/lib/ota_updater/ota_updater.cpp index 4e1245b..b646dbc 100644 --- a/firmware/lib/ota_updater/ota_updater.cpp +++ b/firmware/lib/ota_updater/ota_updater.cpp @@ -49,6 +49,14 @@ bool ota_verify_signature_with_key(const uint8_t hash32[32], const uint8_t sig64 // ── device-only code ─────────────────────────────────────────────────────── #ifndef NATIVE_TEST +#include +#include +#include +#include +#include +#include +#include +#include "hmac.h" #include "ota_pubkey.h" #include "version.h" @@ -71,6 +79,148 @@ void ota_updater_init(const char* server_base, const char* device_id, s_last_check_ms = 0; // force first check on next call } -bool ota_updater_check_and_apply() { return false; } // filled in Task 8 +static void add_hmac_headers(HTTPClient& http, const char* method, const char* path) { + uint32_t ts = (uint32_t)(esp_timer_get_time() / 1000000ULL); + String sig = hmac_sign(s_hmac_secret, method, path, ts, ""); + if (sig.isEmpty()) { + log_e("[OTA] HMAC sign failed"); + return; + } + http.addHeader("X-Device-Id", s_device_id); + http.addHeader("X-Timestamp", String(ts)); + http.addHeader("X-HMAC-Signature", sig); +} + +static bool download_and_flash(const char* fw_url, size_t expected_size, + const uint8_t sig64[64]) { + const esp_partition_t* target = esp_ota_get_next_update_partition(nullptr); + if (!target) { + log_e("[OTA] No update partition found"); + return false; + } + + esp_ota_handle_t handle; + if (esp_ota_begin(target, OTA_WITH_SEQUENTIAL_WRITES, &handle) != ESP_OK) { + log_e("[OTA] esp_ota_begin failed"); + return false; + } + + mbedtls_sha256_context sha_ctx; + mbedtls_sha256_init(&sha_ctx); + mbedtls_sha256_starts(&sha_ctx, 0); // 0 = SHA-256 + + HTTPClient http; + http.begin(fw_url); + add_hmac_headers(http, "GET", "/ota/firmware"); + int code = http.GET(); + if (code != HTTP_CODE_OK) { + log_e("[OTA] Firmware fetch failed: HTTP %d", code); + http.end(); + mbedtls_sha256_free(&sha_ctx); + esp_ota_abort(handle); + return false; + } + + WiFiClient* stream = http.getStreamPtr(); + uint8_t buf[4096]; + size_t written = 0; + + while (written < expected_size) { + size_t want = min((size_t)sizeof(buf), expected_size - written); + int got = stream->readBytes(buf, want); + if (got <= 0) break; + esp_ota_write(handle, buf, (size_t)got); + mbedtls_sha256_update(&sha_ctx, buf, (size_t)got); + written += (size_t)got; + } + http.end(); + + uint8_t hash[32]; + mbedtls_sha256_finish(&sha_ctx, hash); + mbedtls_sha256_free(&sha_ctx); + + if (written != expected_size) { + log_e("[OTA] Download truncated (%zu/%zu bytes)", written, expected_size); + esp_ota_abort(handle); + return false; + } + + if (!ota_verify_signature_with_key(hash, sig64, kOtaPublicKey)) { + log_e("[OTA] SIGNATURE INVALID — staying on current firmware"); + esp_ota_abort(handle); + return false; + } + + if (esp_ota_end(handle) != ESP_OK || + esp_ota_set_boot_partition(target) != ESP_OK) { + log_e("[OTA] Commit failed"); + return false; + } + + log_i("[OTA] Firmware verified and committed — rebooting"); + esp_restart(); + return true; // unreachable +} + +bool ota_updater_check_and_apply() { + if (!s_server_base || !s_device_id || !s_hmac_secret) return false; + if (s_last_check_ms != 0 && + (uint32_t)(millis() - s_last_check_ms) < s_interval_ms) { + return false; + } + s_last_check_ms = millis(); + + char check_path[128]; + snprintf(check_path, sizeof(check_path), "/ota/check?version=%s", FW_VERSION); + char check_url[256]; + snprintf(check_url, sizeof(check_url), "%s%s", s_server_base, check_path); + + HTTPClient http; + http.begin(check_url); + add_hmac_headers(http, "GET", check_path); + int code = http.GET(); + if (code != HTTP_CODE_OK) { + log_w("[OTA] Check failed: HTTP %d", code); + http.end(); + return false; + } + + JsonDocument doc; + DeserializationError err = deserializeJson(doc, http.getStream()); + http.end(); + if (err) { + log_w("[OTA] JSON parse error: %s", err.c_str()); + return false; + } + + if (!doc["update"].as()) { + log_i("[OTA] Firmware up to date (%s)", FW_VERSION); + return false; + } + + const char* remote_ver = doc["version"] | ""; + size_t fw_size = doc["size"] | 0; + const char* sig_b64 = doc["sig_b64"] | ""; + + if (fw_size == 0 || strlen(sig_b64) == 0) { + log_e("[OTA] Invalid update manifest"); + return false; + } + + log_i("[OTA] Update: %s → %s (%zu bytes)", FW_VERSION, remote_ver, fw_size); + + uint8_t sig64[64]; + size_t sig_len = 0; + if (mbedtls_base64_decode(sig64, sizeof(sig64), &sig_len, + (const uint8_t*)sig_b64, strlen(sig_b64)) != 0 || + sig_len != 64) { + log_e("[OTA] Bad signature encoding (len=%zu)", sig_len); + return false; + } + + char fw_url[256]; + snprintf(fw_url, sizeof(fw_url), "%s/ota/firmware", s_server_base); + return download_and_flash(fw_url, fw_size, sig64); +} #endif // NATIVE_TEST