Aller au contenu

Firmware signé & OTA sécurisé

Signer le firmware côté build et vérifier la signature à l'embarquement — la seule protection fiable contre l'injection de code malveillant via les mises à jour Over-The-Air.


Pourquoi signer le firmware

Une mise à jour OTA non authentifiée est une porte dérobée permanente. Sans signature, un attaquant qui accède au réseau de mise à jour — ou au serveur OTA — peut pousser un firmware malveillant sur l'ensemble du parc connecté. La signature asymétrique résout ce problème : seul celui qui détient la clé privée peut signer, et n'importe qui peut vérifier avec la clé publique embarquée.

Les propriétés à garantir sont :

  • Authenticité : le firmware a bien été signé par l'OEM légitime
  • Intégrité : aucun octet n'a été modifié depuis la signature
  • Fraîcheur : le firmware est plus récent que celui installé (anti-downgrade)
  • Atomicité : l'application partielle d'une mise à jour échoue proprement

Choix de l'algorithme : ECDSA P-256

ECDSA avec la courbe P-256 (secp256r1) est le standard de facto pour les systèmes embarqués contraints :

Algorithme Taille clé Taille signature Mémoire (vérif) Sécurité
RSA-2048 2048 bits 256 octets ~30 Ko RAM 112 bits
RSA-4096 4096 bits 512 octets ~60 Ko RAM 140 bits
ECDSA P-256 256 bits 64 octets ~8 Ko RAM 128 bits
ECDSA P-384 384 bits 96 octets ~12 Ko RAM 192 bits
Ed25519 256 bits 64 octets ~4 Ko RAM 128 bits

ECDSA P-256 offre le meilleur compromis performance/empreinte mémoire/sécurité pour les MCU Cortex-M. Ed25519 est légèrement plus rapide en vérification sur MCU modernes — MCUboot 2.x supporte les deux.


Structure du manifest signé

Un manifest signé encapsule les métadonnées de la mise à jour. Le device vérifie d'abord le manifest, puis utilise les informations du manifest pour vérifier le firmware.

{
  "manifest_version": 1,
  "sequence_number": 1042,
  "update_description": "Correctif CVE-2025-1234 — stack overflow BLE",
  "firmware": {
    "version": "2.4.1",
    "size_bytes": 524288,
    "sha256": "a3f8c2d1e4b5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
    "target_hardware": "EXT-SENSOR-V2",
    "min_hw_revision": 3
  },
  "anti_downgrade": {
    "monotonic_counter": 42,
    "min_version": "2.0.0"
  },
  "signature": {
    "algorithm": "ECDSA-P256-SHA256",
    "value": "3045022100...base64..."
  }
}

Champs critiques :

Champ Rôle Protection
sequence_number Ordre absolu des mises à jour Anti-replay
sha256 Hash du binaire firmware Intégrité
monotonic_counter Compteur non décrémentable en OTP Anti-downgrade
min_version Version minimale acceptable Anti-downgrade complémentaire
target_hardware Identifiant du modèle cible Anti-bricking cross-model
signature ECDSA sur le hash du manifest Authenticité + intégrité manifest

Flux de signature et vérification OTA

sequenceDiagram
    participant DEV as Développeur
    participant CI as Pipeline CI/CD
    participant HSM as HSM (clé privée OEM)
    participant SRV as Serveur OTA
    participant GW as Gateway IoT
    participant DEV2 as Device embarqué

    DEV->>CI: git push tag v2.4.1
    CI->>CI: Compilation firmware.bin<br/>sha256(firmware.bin) = H
    CI->>HSM: Signer manifest JSON<br/>(contient H + metadata)
    HSM-->>CI: Signature ECDSA P-256
    CI->>CI: Packager: firmware.bin + manifest.json.sig
    CI->>SRV: Publier artefact signé

    Note over SRV: Le serveur OTA ne détient pas<br/>la clé privée — il distribue seulement

    GW->>SRV: Requête mise à jour (device ID, version actuelle)
    SRV-->>GW: Manifest.json + manifest.json.sig + firmware.bin
    GW->>GW: Vérifier signature manifest<br/>(clé publique OEM)
    GW->>GW: Vérifier sha256(firmware.bin) == H
    GW->>DEV2: Transférer firmware vérifié (chunk par chunk)

    DEV2->>DEV2: 1. Vérifier signature manifest
    DEV2->>DEV2: 2. Vérifier hash firmware
    DEV2->>DEV2: 3. Vérifier monotonic counter > actuel
    DEV2->>DEV2: 4. Écrire en slot secondaire (A/B)
    DEV2->>DEV2: 5. Marquer slot comme "pending test"
    DEV2->>DEV2: 6. Reboot sur nouveau firmware
    DEV2->>DEV2: 7. Self-test (30s)
    DEV2-->>GW: Confirmer succès (marquer permanent)
    Note over DEV2: Si self-test échoue → rollback auto sur slot A

Mécanisme anti-downgrade avec compteur monotone

Le compteur monotone (monotonic counter) est stocké dans un registre OTP qui ne peut qu'être incrémenté, jamais décrémenté. Même si un attaquant parvient à pousser un manifest signé d'une version plus ancienne, le device refuse l'installation si le compteur du manifest est inférieur au compteur courant.

stateDiagram-v2
    [*] --> Vérification_Manifest

    Vérification_Manifest --> Signature_Invalide : signature KO
    Vérification_Manifest --> Hash_Invalide : hash firmware KO
    Vérification_Manifest --> Check_Compteur : signature + hash OK

    Signature_Invalide --> Rejet : Abandon mise à jour
    Hash_Invalide --> Rejet : Abandon mise à jour

    Check_Compteur --> Downgrade_Détecté : counter_manifest < counter_device
    Check_Compteur --> Installation : counter_manifest >= counter_device

    Downgrade_Détecté --> Rejet : Abandon + log sécurité

    Installation --> Boot_Test_Slot
    Boot_Test_Slot --> Self_Test
    Self_Test --> Rollback : échec (watchdog / assertion)
    Self_Test --> Confirmer : succès (30s stable)

    Confirmer --> Incrémenter_Compteur_OTP
    Incrémenter_Compteur_OTP --> [*]

    Rollback --> Boot_Ancien_Slot
    Boot_Ancien_Slot --> [*]

Mise à jour A/B (dual-bank)

La mise à jour A/B est le mécanisme de rollback sûr standard. Le device dispose de deux partitions firmware :

graph TD
    subgraph FLASH["FLASH STORAGE"]
        direction LR
        A["Slot A (actif)<br/>firmware v2.3<br/>status : PERM"]
        B["Slot B (mise à jour)<br/>firmware v2.4.1 (pending test)<br/>status : TEST"]
    end
    subgraph BOOT["Bootloader MCUboot"]
        C["Sélectionne le slot<br/>selon flags + vérification"]
    end
    FLASH --> BOOT

Les états d'un slot MCUboot :

Statut Description Action du bootloader
GOOD Firmware vérifié, permanent Boot direct
REVERT Rollback demandé Boot sur l'autre slot
TEST Premier boot post-update Boot + timer watchdog
INVALID Hash invalide Skip, boot sur l'autre slot
EMPTY Slot vide Skip

Sécurisation du canal OTA

La signature protège l'intégrité du firmware même si le canal est compromis. Pour autant, le canal de distribution doit aussi être sécurisé :

Couche Mécanisme Pourquoi
Transport TLS 1.3 + certificate pinning Confidentialité, anti-MitM
Authentification device mTLS (certificat X.509 device) Seuls les devices légitimes reçoivent les MAJ
Autorisation RBAC sur le serveur OTA Contrôle par flotte/groupe
Rate limiting Max X tentatives/heure par device Protection DoS sur l'OTA backend
Delta updates BSDiff / ZBSDIFF Réduction bande passante (critique sur LoRa)

Cas des MCU très contraints

Sur les MCU avec moins de 64 Ko de flash, la mise à jour A/B complète peut être impossible. Alternatives :

Mise à jour in-place avec hash pré-vérification. Le nouveau firmware est téléchargé en RAM (ou partition temporaire EEPROM externe), vérifié, puis écrit sur la flash. Risque : si la coupure de courant survient pendant l'écriture, le device est briqué. Mitigation : checksum partiel + écriture sectorielle.

Incremental patching. Seuls les secteurs modifiés sont transférés. Réduit la bande passante et le temps d'exposition. Nécessite un composant de patch embarqué (JanPatch, HDiffPatch).

Bootloader en ROM immuable. Certains MCU (STM32, ESP32) disposent d'un bootloader ROM incorruptible qui peut toujours restaurer un firmware via UART — filet de sécurité ultime en production.


Ce qu'il faut retenir

  • ECDSA P-256 est le standard embarqué : petit, rapide, 128 bits de sécurité.
  • Le manifest signé doit inclure : hash du binaire, version, compteur monotone, cible matérielle.
  • Le compteur monotone en OTP est la seule protection solide contre le downgrade.
  • La mise à jour A/B avec self-test et rollback automatique est la norme pour les déploiements industriels.
  • La clé privée de signature ne doit jamais quitter le HSM — ni les serveurs CI, ni les workstations développeur.

Chapitre suivant : Segmentation réseau — isoler les devices IoT du réseau IT pour contenir la propagation d'une compromission.