Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:laboratoare:2025:lab10 [2025/12/06 19:13]
dan.tudose [A2. Add the partition table file]
iothings:laboratoare:2025:lab10 [2025/12/06 21:35] (current)
dan.tudose
Line 1: Line 1:
 ====== Lab 10. Secure OTA ====== ====== 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**.+Over-the-air (OTA) updates are a core capability of modern IoT devices, enabling firmware improvements,​ bug fixes, and security patches without physical access to the hardware. In real deployments,​ devices may be installed in hard-to-reach locations or embedded in larger systems, so requiring a USB connection for every update becomes impractical. OTA solves this by allowing a device to receive new firmware over a network, reducing maintenance costs and shortening the time between discovering an issue and delivering a fix. 
 + 
 +Designing OTA for embedded systems also introduces engineering trade-offs that are less visible in traditional software. Devices must update safely despite limited flash, intermittent connectivity,​ and the risk of power loss mid-installation. A robust OTA approach typically uses separate firmware slots so a known-good image remains available if an update fails. Just as importantly,​ OTA is part of a security boundary: update mechanisms must ensure that only authentic, intact firmware can be installed, or they can become a high-impact attack path in an IoT fleet. 
 + 
 +{{ :​iothings:​laboratoare:​2025:​ota1.jpg?​800 |}} 
 + 
 +====== Simple OTA ====== 
 + 
 +This first example ​shows you how to implement **real remote firmware updates from a browser** for an ESP32-C6 board using **PlatformIO + pioarduino**.
  
 You will: You will:
Line 9: Line 17:
   * verify success with a visible NeoPixel behavior change,   * verify success with a visible NeoPixel behavior change,
   * document basic threat-model thinking.   * document basic threat-model thinking.
- 
  
 ===== Learning outcomes ===== ===== Learning outcomes =====
Line 21: Line 28:
   - Identify key security risks for OTA and apply simple mitigations.   - Identify key security risks for OTA and apply simple mitigations.
  
- 
-===== Concept snapshot ===== 
  
 **What you are building:** **What you are building:**
Line 36: Line 41:
  
  
-===== Part A — Create the PlatformIO project ​=====+==== Part A — Create the PlatformIO project ====
  
 Create a new PlatformIO project and create or replace your ''​platformio.ini''​ with: Create a new PlatformIO project and create or replace your ''​platformio.ini''​ with:
Line 71: Line 76:
  
  
-==== Add the partition table file ====+=== Add the partition table file ===
  
 In your project root, next to ''​platformio.ini'',​ create ''​sparrow_ota_4mb.csv''​. In your project root, next to ''​platformio.ini'',​ create ''​sparrow_ota_4mb.csv''​.
Line 89: Line 94:
 This layout assumes **4MB internal flash** and gives you two OTA application slots plus a small SPIFFS area. 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 =====+==== Part B — Baseline firmware with Async OTA web page ====
  
 Create ''​src/​main.cpp''​ and paste the code from [[iothings:​laboratoare:​2025_code:​lab10_1| here]] then build and upload via USB. Create ''​src/​main.cpp''​ and paste the code from [[iothings:​laboratoare:​2025_code:​lab10_1| here]] then build and upload via USB.
Line 109: Line 114:
   * pass: ''​change-me''​   * pass: ''​change-me''​
  
-===== Part C — OTA proof using a NeoPixel blink change ​=====+==== Part C — OTA proof using a NeoPixel blink change ====
  
 Now you will make a visible change and deliver it **without USB**. ​ 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 [[iothings:​laboratoare:​2025_code:​lab10_2| here]]. Add a small LED pattern into main.cpp, you can get the new code from [[iothings:​laboratoare:​2025_code:​lab10_2| 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.
 +
 +{{ :​iothings:​laboratoare:​2025:​ota2.jpg?​800 |}}
 +
 +===== Learning outcomes =====
 +
 +After completing this lab, you can:
 +
 +  - Implement a **manifest-driven update** workflow.
 +  - Perform **streaming SHA-256 verification** during OTA download.
 +  - Store and retrieve update metadata with **Preferences (NVS)**.
 +  - 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:
 +
 +<code ini 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
 +
 +</​code>​
 +===== Part A — Update manifest =====
 +
 +Create a manifest file with the following structure:
 +
 +<code json>
 +{
 +  "​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."​
 +}
 +</​code>​
 +
 +**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**
 +<​code>​
 +shasum -a 256 firmware.bin
 +</​code>​
 +
 +**Windows PowerShell**
 +<​code>​
 +Get-FileHash .\firmware.bin -Algorithm SHA256
 +</​code>​
 +
 +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:
 +
 +<​code>​
 +python3 -m http.server 8000
 +</​code>​
 +
 +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 [[iothings:​laboratoare:​2025_code:​lab10_3|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:
 +
 +<​code>​
 +python -m http.server 8000
 +</​code>​
 +
 +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:
 +    - ''​last_result = "​success"''​
 +    - 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.1765041202.txt.gz · Last modified: 2025/12/06 19:13 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