This is an old revision of the document!


Lab 10. Secure OTA

This lab shows how to implement real remote firmware updates from a browser for an ESP32-C6 board using PlatformIO + pioarduino and an Async Web OTA page powered by ElegantOTA.

You will:

  • flash a first “baseline” firmware over USB,
  • host an authenticated OTA webpage on the device,
  • upload a new firmware from your browser,
  • verify success with a visible NeoPixel behavior change,
  • document basic threat-model thinking.

Learning outcomes

After completing this lab, you can:

  1. Explain why OTA requires an A/B (dual-slot) style partition table.
  2. Configure PlatformIO to use pioarduino and an OTA partition scheme.
  3. Implement a device-hosted OTA updater available at /update.
  4. Validate OTA end-to-end by changing device behavior without USB access.
  5. Identify key security risks for OTA and apply simple mitigations.

Simple OTA

What you are building:

  • Your device runs a small web server.
  • Visiting a web page opens a firmware upload page.
  • The new firmware is written into the inactive OTA slot.
  • After reboot, the bootloader selects the new slot.

Why two slots?

If an update fails mid-way (power loss, bad binary), the device still has a valid previous firmware to boot. This is the core reliability concept behind OTA on constrained devices.

Part A — Create the PlatformIO project

Create a new PlatformIO project and create or replace your platformio.ini with:

platformio.ini
[env:sparrow_c6]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-c6-devkitm-1
framework = arduino
monitor_speed = 115200
 
; Use OTA-capable partition table
board_build.partitions = sparrow_ota_4mb.csv
 
; Optional but often helpful
build_flags =
  -D CORE_DEBUG_LEVEL=1
  -D ARDUINO_USB_MODE=1
  -D ARDUINO_USB_CDC_ON_BOOT=1
  -D ESP32_C6_env
  -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1
 
lib_deps =
  ESP32Async/AsyncTCP@^3.4.9
  ESP32Async/ESPAsyncWebServer@^3.9.2
  ayushsharma82/ElegantOTA
  adafruit/Adafruit NeoPixel
 
 
lib_ignore =
  AsyncTCP_RP2040W

Add the partition table file

In your project root, next to platformio.ini, create sparrow_ota_4mb.csv.

Paste this:

# Name,   Type, SubType, Offset,  Size,     Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xE000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x140000,
app1,     app,  ota_1,   ,        0x140000,
spiffs,   data, spiffs,  ,        0xD0000,

This layout assumes 4MB internal flash and gives you two OTA application slots plus a small SPIFFS area.

Part B — Baseline firmware with Async OTA web page

Create src/main.cpp and paste the code from here then build and upload via USB.

You should see:

  • Wi-Fi connection dots
  • A printed IP address
  • A message telling you to open /update

In your browser:

  • http:<device-ip>/ * http:<device-ip>/update

Log in with:

  • user: admin
  • pass: change-me

Now you will make a visible change and deliver it without USB.

Add a small LED pattern into main.cpp, you can get the new code from here.

Pull-Based OTA + Hash Integrity + Telemetry

You will upgrade your firmware update architecture from push OTA (human uploads a .bin to /update) to a more production-like pull OTA, where the device:

  • checks an update manifest hosted on a server,
  • compares versions,
  • downloads a firmware image,
  • verifies SHA-256 integrity,
  • writes the update to the inactive OTA slot,
  • stores update telemetry in NVS,
  • exposes a /status JSON endpoint.

Learning outcomes

After completing this lab, you can:

  1. Implement a manifest-driven update workflow.
  2. Perform streaming SHA-256 verification during OTA download.
  3. Store and retrieve update metadata with Preferences (NVS).
  4. Design basic fleet-friendly status telemetry endpoints.

Architecture

Update server hosts:

  • manifest.json
  • firmware-<version>.bin

Device workflow:

1. GET manifest
2. Parse JSON
3. If manifest.version > current_version:
   - download firmware
   - compute SHA-256 while streaming
   - compare expected vs computed
   - write to OTA slot
   - commit update + reboot
4. Record telemetry in NVS

Platformio Setup

You will need to edit your platformio.ini file to this:

platformio.ini
[env:sparrow_c6]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-c6-devkitm-1
framework = arduino
monitor_speed = 115200
 
; Use OTA-capable partition table
board_build.partitions = sparrow_ota_4mb.csv
 
; Optional but often helpful
build_flags =
  -D CORE_DEBUG_LEVEL=1
  -D ARDUINO_USB_MODE=1
  -D ARDUINO_USB_CDC_ON_BOOT=1
  -D ESP32_C6_env
 
lib_deps =
  ESP32Async/AsyncTCP@^3.4.9
  ESP32Async/ESPAsyncWebServer@^3.9.2
  adafruit/Adafruit NeoPixel
  bblanchon/ArduinoJson
 
 
lib_ignore =
  AsyncTCP_RP2040W

Part A — Update manifest

Create a manifest file with the following structure:

{
  "project": "sparrow-c6-lab",
  "version": "1.0.1",
  "url": "http://YOUR_PC_IP:8000/firmware-1.0.1.bin",
  "sha256": "REPLACE_WITH_SHA256_HEX",
  "notes": "NeoPixel speed fix + status endpoint."
}

Rules:

  • version uses semantic versioning: MAJOR.MINOR.PATCH
  • url must be reachable by the device
  • sha256 is lowercase hex of the binary

A1. Compute SHA-256 for a firmware binary

After building in PlatformIO, your binary is typically:

  • .pio/build/sparrow_c6/firmware.bin

Compute its SHA-256 on your computer:

Linux/macOS

shasum -a 256 firmware.bin

Windows PowerShell

Get-FileHash .\firmware.bin -Algorithm SHA256

Copy the hash into manifest.json.

A2. Host a simple local update server

In the directory containing:

  • manifest.json
  • firmware-1.0.1.bin

Run:

python3 -m http.server 8000

You should be able to open from your laptop browser http://YOUR_PC_IP:8000/manifest.json

Part B — Device firmware (pull OTA + hash + telemetry)

Create/replace src/main.cpp with this template.

You must edit:

  • Wi-Fi credentials
  • MANIFEST_URL to your PC/server IP

Part C — Build, host, update

1) Flash this version once over USB (your pull-OTA baseline).

2) Build a new firmware with:

  • FW_VERSION = “1.0.1”

Also update the homepage text or the blink color of the Neopixel so the new firmware is easy to confirm.

3) Build in PlatformIO and copy:

  • .pio/build/sparrow_c6/firmware.bin

Rename it to:

  • firmware-1.0.1.bin

4) Compute SHA-256 and update manifest.json.

5) Host the files:

python -m http.server 8000

6) Reboot the device.

7) Open http://DEVICE-IP/status and confirm telemetry fields exist.

Part D — Test cases

Complete these tests and record results.

Test 1: No update needed

  • Manifest version equals device version.

Expected:

  • Device prints “No update needed.”
  • last_error clears or remains empty.

Test 2: Successful update

  • Manifest version is higher.
  • Hash is correct.

Expected:

  • Device updates and reboots.
  • /status shows:
    1. last_result = “success”
    2. correct version transition values

Test 3: Hash mismatch

Intentionally change the manifest hash to a wrong value.

Expected:

  • Device refuses update.
  • last_error = “sha_mismatch”
  • Old firmware continues running.
iothings/laboratoare/2025/lab10.1765049544.txt.gz · Last modified: 2025/12/06 21:32 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0