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:12]
dan.tudose [Part A — Create the PlatformIO project]
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:
  
  
-==== A2. 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.
  
 You should see: You should see:
Line 97: Line 102:
   * Wi-Fi connection dots   * Wi-Fi connection dots
   * A printed IP address   * A printed IP address
-  * A message telling you to open ``/update``+  * A message telling you to open ''​/update''​
  
 In your browser: In your browser:
  
-  * ``http://<​device-ip>/​``   +  * ''​http://<​device-ip>/​'' ​  
-  * ``http://<​device-ip>/​update``+  * ''​http://<​device-ip>/​update''​
  
 Log in with: Log in with:
  
-  * user: ``admin`` +  * user: ''​admin''​ 
-  * pass: ``change-me``+  * pass: ''​change-me''​ 
 + 
 +==== Part C — OTA proof using a NeoPixel blink change ==== 
 + 
 +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]]. 
 + 
 + 
 + 
 +====== 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 ====
  
-===== Part C — OTA proof using a NeoPixel blink change ​=====+Intentionally ​change ​the manifest hash to a wrong value.
  
-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]].+**Expected:** 
 +  * Device refuses update. 
 +  * ''​last_error = "​sha_mismatch"''​ 
 +  * Old firmware continues running.
  
  
  
iothings/laboratoare/2025/lab10.1765041144.txt.gz · Last modified: 2025/12/06 19:12 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