Sécurité embarquée

Mise à jour sécurisée OTA sur ESP32

Une chaîne de mise à jour firmware avec Secure Boot v2, chiffrement flash AES-256, OTA HTTPS signée et retour arrière automatique.

ESP32Secure BootOTATLS/PKIC
View on GitHub
Problème

Flasher le firmware par câble série ne passe pas à l'échelle sur des appareils déjà déployés, et un chemin OTA naïf fait confiance à tout ce que le serveur envoie. Le difficile n'est pas de télécharger un binaire : il faut prouver qu'il est signé par la bonne clé, refuser une version antérieure et récupérer quand la nouvelle image est défectueuse.

Approche

Construire le chemin de mise à jour comme une chaîne de confiance. Secure Boot v2 (RSA-3072 / RSA-PSS) vérifie le bootloader et l'application, le chiffrement flash protège l'image au repos, un manifeste versionné est récupéré en TLS, et le bootloader revient automatiquement à l'image précédente si l'autotest après mise à jour échoue. Les outils de release signent l'image et émettent un manifeste SHA-256.

Architecture
Hôte de release : signature + manifeste→
Serveur HTTPS→
Appareil : lecture du manifeste→
Filtre de version→
esp_https_ota→
Autotest après mise à jour→
Validation ou retour arrière

Tech Stack

Outils de releaseBash + Python + ESP-IDFBuild signé, manifeste, serveur HTTPS local
FirmwareESP-IDF 5.3 / CWi-Fi, manifeste, comparaison de version, OTA signée
Chaîne de bootSecure Boot v2 + AES-256Vérification RSA-PSS, protection de la flash au repos
Testspytest + cryptographyIntégrité du manifeste, falsification, confiance PKI

Décisions clés

  • →Deux slots OTA sans partition factory : l'anti-rollback impose ce schéma et les deux images restent récupérables.
  • →RSA-3072 / RSA-PSS pour Secure Boot v2, avec la clé de signature conservée hors ligne.
  • →Le SHA-256 du manifeste est une défense en profondeur, pas la racine de confiance : l'authenticité vient des eFuses.
  • →Le chiffrement flash reste en mode développement pour pouvoir reflasher la carte de démo pendant l'itération.
Deep Dive

Filtre de version et manifeste

L'appareil télécharge un petit manifeste JSON à côté de l'image. Il compare la version annoncée à celle de l'application en cours et ne lance la mise à jour que si le manifeste est plus récent, ce qui évite de rejouer une ancienne release.

json
manifest.json
{
  "version": "1.1.0",
  "firmware": "firmware.bin",
  "size": 1048576,
  "sha256": "<empreinte>"
}

Autotest et retour arrière

Une nouvelle image démarre en ESP_OTA_IMG_PENDING_VERIFY. L'application vérifie la description de la partition courante et fait un aller-retour NVS ; elle appelle ensuite esp_ota_mark_app_valid_cancel_rollback(). En cas d'échec, elle invalide l'image et le bootloader revient au slot précédent.

c
if (app_self_test())
    esp_ota_mark_app_valid_cancel_rollback();
else
    esp_ota_mark_app_invalid_rollback_and_reboot();

Tester le chemin d'échec

Un retour arrière jamais exercé n'est pas testé. Le build expose un interrupteur de développement qui force l'échec de l'autotest, afin de reproduire le rollback sur une vraie carte au lieu de le supposer.

bash
idf.py -D CONFIG_APP_SELF_TEST_FAIL=y build
Résultats
10 / 10
Tests hôte
2 (sans factory)
Slots OTA
RSA-3072 / PSS
Signature image
AES-256
Flash au repos
en attente du matériel
Mesures carte

Fonctionnalités démo

  • →Deux configurations de build : développement et Secure Boot v2 + chiffrement flash
  • →Les tests hôte couvrent la falsification du manifeste, l'ordre des versions et la PKI de test
  • →Interrupteur de développement pour forcer un autotest en échec et reproduire le rollback
  • →Une commande produit une image signée et son manifeste SHA-256
Leçons apprises
  • →La sécurité est une chaîne : une image signée ne sert à rien si le bootloader, la table de partitions ou le manifeste sont acceptés aveuglément.
  • →Tester le chemin d'échec, pas seulement le chemin nominal. L'interrupteur de rollback existe parce qu'un retour arrière non testé reste une hypothèse.
  • →Des releases reproductibles comptent plus qu'un script de build astucieux.