Relais BLE — modèle de menaces

Ce que peut faire un attaquant qui connaît le code du micrologiciel mais n’a pas accès aux secrets de l’appareil.

Surface d’attaque

Le micrologiciel est public : les formats de paquets, les longueurs et les algorithmes (X25519, HMAC-SHA256, HKDF) sont connus. Le BLE est un air libre : les annonces et les écritures GATT peuvent être écoutées. Ni dans le micrologiciel ni dans les ondes ne figure le moindre secret.

Où vivent les clés :

Ce qu’un attaquant ne peut pas faire

AttackDefence
Falsifier un FIREHMAC-SHA256. Sans owner_secret ni guest_key, c’est un 2128 de force brute — irréalisable.
Falsifier une écriture TIMELe même HMAC sur bleId + epochMs.
Rejouer une annonce interceptéeLe compteur est monotone : en cas de répétition ou de diminution, le HMAC ne correspond pas.
Rejouer un fire interceptéL’anneau de nonces invalide ceux déjà utilisés.
Reculer l’horloge du contrôleurLe cliquet : tout TIME < cliquet est rejeté. Le cliquet réside en NVS et survit à une coupure de courant.
Utiliser un lot expiréAprès le fonctionnement du contrôleur, le cliquet vaut au moins la dernière heure réelle. Pour un lot expiré, validUntil est sous le cliquet : le fire échoue sur la durée de vie.
Avancer l’horloge d’un an et exclure tous les invitésUn plafond de +24 h par écriture TIME d’invité. Le propriétaire n’a pas de plafond : on lui fait confiance.
S’appairer comme second propriétaire par-dessus le premierAprès l’appairage, le contrôleur garde owner_secret en NVS et ne repasse jamais en mode appairage sans réinitialisation d’usine.

Ce qu’un attaquant peut faire, et ce que cela coûte

1. Avancer l’heure — déni de service pour les invités légitimes

Requires: un lot invité valide et non expiré.

Action: à chaque passage à portée, écrire un TIME qui saute de 24 heures.

Limit: +24 h par session, et un fire récent est exigé si la dernière synchronisation date de moins d’une heure. Après dix visites, le cliquet a dix jours d’avance et les jetons des autres invités légitimes semblent expirés.

Le revers : le jeton de l’attaquant est jugé sur cette même heure falsifiée et expire donc plus tôt.

Que faire : le propriétaire passe et écrit l’heure réelle. Le cliquet ne bouge pas, car il ne recule pas, mais les nouveaux jetons invités sont émis avec un validUntil courant et fonctionnent ensuite pour tous. La reprise ne demande rien de plus que de rafraîchir la validité sur le serveur.

Severity: un déni de service temporaire jusqu’à la venue du propriétaire. Pas critique.

2. Homme du milieu pendant l’appairage

Requires: se trouver physiquement à portée du contrôleur pendant la fenêtre de 90 secondes qui suit le lancement de l’appairage.

Action: substituer sa propre clé publique X25519 dans la poignée de main d’appairage.

Result: le contrôleur s’appaire avec l’attaquant et non avec le propriétaire. Le propriétaire croit l’appairage réussi mais n’ouvre rien depuis son téléphone.

Défense actuelle : seulement la fenêtre temporelle et la présence physique du propriétaire — il voit que l’appairage n’a pas été confirmé et le recommence.

Hardening: ajouter un code de vérification simple — quatre chiffres sur l’écran du contrôleur ou dans le Serial — que le propriétaire confirme dans l’application. Ce code n’existe pas aujourd’hui.

Severity: la fenêtre est étroite et exige d’être à proximité pendant ces 90 secondes précises. Difficile à réaliser en pratique.

3. Réinitialisation d’usine physique

Requires: un accès physique au contrôleur et un appui long sur le bouton.

Action: La NVS est effacée, owner_secret supprimée, le contrôleur revient en mode appairage.

Result: l’attaquant appaire le contrôleur avec lui-même et le propriétaire perd l’accès.

Defence: monter le contrôleur dans un endroit protégé — dans un boîtier, sous un capot.

Severity: comme arracher un interphone d’une porte — ce n’est pas un problème cryptographique.

4. Extraction de owner_secret via UART ou JTAG

Requires: un accès physique et un dump de la flash, avec démarrage sécurisé et chiffrement de la flash désactivés — c’est le cas aujourd’hui.

Action: lire la NVS qui contient owner_secret.

Result: compromission totale de l’appareil et de tous les jetons invités.

Defence: activer le démarrage sécurisé et le chiffrement de la flash de l’ESP32 (efuse, irréversible). Non activé aujourd’hui.

Severity: acceptable pour un usage domestique. Sur les sites sensibles, le démarrage sécurisé est nécessaire.

5. Squattage des connexions GATT — déni de service

Requires: être à portée BLE.

Action: se connecter et ne jamais se déconnecter. NimBLE ne supporte qu’un nombre limité de connexions simultanées.

Result: les clients légitimes ne peuvent pas se connecter tant que l’attaquant occupe un emplacement.

Défense actuelle : seulement les délais d’attente de NimBLE.

Hardening: ajouter un délai d’inactivité : si un client n’a pas effectué de FIRE valide en 5 secondes, couper la connexion.

Severity: un déni de service temporaire tant que l’attaquant est à proximité.

6. Un flot d’écritures invalides (usurpation de connexion)

Action: bombarder le contrôleur d’écritures parasites.

Result: le contrôleur les rejette et les consigne dans le Serial. Son état ne change pas, et dès que l’attaquant s’éloigne tout fonctionne comme d’habitude.

Ce qui n’est délibérément pas une défense

Trois endroits ressemblent à une protection sans en être une. Mieux vaut les nommer que de laisser quelqu’un s’y appuyer.

L’octet d’état dans l’annonce porte une étiquette de 16 bits. Cela suffit contre une altération accidentelle dans les ondes, pas davantage : un faussaire déterminé parcourt 16 bits en quelques secondes. Cet octet ne pilote que l’indicateur ouvert/fermé sur la carte et n’accorde aucun droit ; l’étiquette reste donc courte — chaque octet supplémentaire coûte de la batterie à chaque réveil.

Les clés réservées à l’application reposent sur un secret compilé dans l’APK. Qui décompresse le fichier obtient le secret et falsifie l’attestation. Cela relève la barre face à un client web de passage, rien de plus. Le vrai contrôle pour une telle clé, c’est son rattachement à l’empreinte d’un appareil — cette vérification tourne sur le serveur et ne dépend pas d’un secret gardé par l’application.

L’expiration d’une clé invité est assurée par le contrôleur lui-même en BLE : le jeton porte une durée et le contrôleur a une horloge. Pour les autres types d’objets, il n’y a aucune durée : la clé vit jusqu’à ce que le propriétaire la révoque. L’application ne prétend pas le contraire, mais si une limite de temps apparaît un jour dans l’interface pour eux aussi, il faudra l’imposer sur le serveur et non la dessiner sur la carte.

En résumé

Durcissement standard pour un usage industriel :

  1. Démarrage sécurisé et chiffrement de la flash (protection contre JTAG et dumps).
  2. Un code de vérification dans le parcours d’appairage (protection contre l’homme du milieu).
  3. Un délai d’inactivité sur les connexions GATT (protection contre le squattage).
  4. Comparer les signatures à temps constant — fait dans le micrologiciel et dans l’application : l’ancienne comparaison octet par octet révélait, par le temps de réponse, combien d’octets d’une signature avaient été devinés.

Rien de tout cela n’est critique pour les scénarios actuels, mais les sites sensibles méritent ce durcissement.