Hardware

Wie man einen sicheren IoT-Prototypen mit Arduino und ESP32 baut

Wie man die Arduino-Anwendung mit den nativen Sicherheitsfunktionen des ESP32-Mikrocontrollers sichert. Eine Schritt-für-Schritt-Anleitung für das Prototyping.

August 2022
18
min Lesezeit
Niklas Völker
Lead Systems Engineer & Developer
Diesen Beitrag teilen

Diejenigen, die sich für das Prototyping und den Bau von IoT-Geräten interessieren, kommen oft zu dem Punkt: "Okay, was sind die nächsten Schritte, um mein Produkt auf den Markt zu bringen? Vielleicht ein paar auf Tindie verkaufen und mehr Feedback einholen.

Das ist großartig, ABER was könnte Sie daran hindern, voranzukommen? IoT-Sicherheit.

Es ist viel Aufwand erforderlich, um die Arduino-Anwendung mit den nativen Sicherheitsfunktionen des ESP32-Mikrocontrollers sicher zu machen. Die Sicherheitsfunktionen zwingen Sie dazu, die Anwendung im ESP-IDF neu zu schreiben oder Sie machen es auf die hacky Art, indem Sie den Arduino-Code in eine native ESP-IDF gesicherte Anwendung einfügen. Außerdem ist die Sicherung der Anwendung nur ein Teil. Sie müssen noch einige andere Dinge bedenken, z. B. den Produktlebenszyklus, die Aktualisierung der Anwendung auf dem Gerät im Feld und die Verwaltung der Zertifikate auf dem Gerät, da diese von Zeit zu Zeit aktualisiert werden müssen. Wenn man mit Arduino und ESP-32 arbeitet, gibt es außerdem eine größere Auswahl an Bibliotheken. Diese zum Laufen zu bringen, ist auch eine kleine Herausforderung bei der Sicherung des Geräts.

Als ich mit dem Kapitel über IoT-Sicherheit begann, nahm die gesamte Implementierung von Grund auf mindestens 50 % des gesamten Produktentwicklungsprozesses in Anspruch. Da dies ein so großer Aufwand war, möchte ich meine Erfahrungen mit Ihnen teilen und Ihnen hoffentlich etwas Zeit sparen.

Zur besseren Übersicht finden Sie hier eine kurze Beschreibung der Systemarchitektur mit den verwendeten Hardware- und Softwarekomponenten und Protokollen.

Backend:

  • rabbitMQ
  • Apache2.0 als Firmware-Server für OTA-Aktualisierungen

Protokoll:

  • MQTT - TLS-verschlüsselt (hier mit selbstsignierten Zertifikaten)

Gerät:

  • esp32 lolin dev-kit - 4MB Flash.

IDE:

  • Arduino und PlatformIO für Anwendungsentwicklung & ESP IDF für sicheres Flashen


Security+ Konzept

Aus dieser Perspektive ist es eher ein Standpunkt der Sicherheit+. Das + ergibt sich aus der weiteren Überlegung, was passiert, wenn sich das Gerät aufgrund eines fehlerhaften OTA-Updates oder eines Zertifikats, das mit der Zeit ungültig wird, selbst aus der sicheren Infrastruktur aussperrt.

1. Kommunikation bedeutet erstens verschlüsselte Kommunikation, zweitens Authentifizierung und drittens Prüfung auf Integrität. Wie erklärt, werden wir die gegenseitige Authentifizierung durch x.509-Zertifikate verwenden, um nicht nur den Backend-Server, sondern auch das IoT-Gerät selbst zu authentifizieren. Weitere Informationen zur allgemeinen Transport - Layer - Security findest du hier.

2. Sichere Speicherung der Zertifikate, so dass niemand sie aus dem Flash oder einer auf dem uC selbst laufenden Anwendung lesen kann.

2.1. Flash-Verschlüsselung: Für den Fall, dass Firmware-Updates nicht erwartet werden oder Updates manuell durchgeführt werden, betten Sie die Zertifikate in die Firmware ein, verschlüsseln die Firmware-Binärdatei und flashen sie auf das Gerät. Weitere Informationen dazu finden Sie in den nächsten Kapiteln.

2.2. Speichern Sie die Zertifikate in einer verschlüsselten Partition des ESP32, das NVS mit OTA-Updates.

3. Sichere OTA-Updates für den optimalen Fall, dass das Gerät Firmware-Updates von einem HTTPs-Firmware-Server bezieht.

4. Verwendung von Secure Boot - das bedeutet, dass Sie Ihre Anwendung mit einem Schlüssel signieren und das uC nur eine signierte Anwendung ausführen wird. Anschließend kann der uC nicht in dem Sinne missbraucht werden, dass er die falsche Anwendung herunterlädt und beginnt, Ihr privates Netzwerk zu überwachen. (Leider wird dies in dem kommenden Beispiel nicht behandelt).

5. Fallback - für den Fall, dass etwas schief geht. Angenommen, die Zertifikate sind nicht mehr gültig, und das Gerät kann keine Verbindung zum Backend herstellen. Implementieren Sie eine zweite sichere Verbindung zu einem anderen Backend-Server. In diesem Fall könnte sie nur passwortgeschützt sein, aber dennoch über TLS verschlüsselt (nicht gegenseitig). Hier können Sie neue Zertifikate für die Geräte veröffentlichen. Dies ist besonders gut, da Sie Zertifikate nur an bestimmte Geräte senden können. Dies könnte einer der vielen Gründe sein, warum einige IoT-Infrastrukturanbieter ihre eigenen Mechanismen entwickelt haben. Die Berücksichtigung dieses Faktors ist wichtig.


Sichere Kommunikation

Wie bereits oben erwähnt, verwenden wir TLS auf Gegenseitigkeit. Wenn wir das Konzept von TLS verstanden haben, was in diesem Artikel nicht möglich zu sein scheint, können wir über die Implementierung auf der uC-Seite sprechen. Die Implementierung innerhalb des Arduino Frameworks hat eine Weile gedauert, da es schwierig war, funktionierende Bibliotheken und gute Dokumentation zu finden. Aus diesem Grund möchte ich diese erstaunliche Bibliothek und das Beispiel mit Ihnen teilen, in dem eine TLS-gesicherte MQTT-Verbindung implementiert wurde. Außerdem enthalten die Beispiele ein Skript zum Erstellen von selbstsignierten Zertifikaten, die Sie auf Ihrem Backend-Server und auf dem uC verwenden.

Wenn Sie das geschafft haben - herzlichen Glückwunsch! Sie haben den ersten Schritt zu einer sichereren IoT-Welt getan.


Firmware/Flash-Verschlüsselung

Weiter - Warum? Weil es einfach zu einfach ist, das Flash mit allen wertvollen Daten im Klartext auszulesen. Wenn Sie selbst testen wollen, wie ein Flash mit einer App von innen aussieht, verwenden Sie diesen Befehl, um das Flash des ESP32 auszulesen. Das gleiche würde auch mit einem anderen Toolset für andere uC funktionieren.



esptool.py --port PORT --baud 230400 read_flash 0 0x40000 ./trick.bin

Kurze Erklärung: Die verfügbaren Baudraten reichen von 115200 bis 576000. Erfahrungsgemäß kann es zu einem Fehler kommen, wenn man die Firmware zu schnell herunterlädt, und man muss wieder von vorne anfangen. Im Abschnitt Flash lesen fügst du die Adressen ein. Da es sich um einen μC mit 4MB Flash handelt, lesen wir die Adresse von 0x0 bis 0x40000 (0x bedeutet einfach, dass Sie eine Hex-Zahl lesen) - und folgen damit dem gesamten Flash, das dann den Bootloader, den physikalischen Schnittstellenspeicher (PHY), wo die WLAN-Daten und die Firmware gespeichert sind, enthält. Wenn du mit Partitionstabellen und Speichertypen nicht vertraut bist, empfehle ich dir, diesen Artikel oder die ESP32-Dokumentation zu lesen.


Danach kannst du die Datei trick.bin in deiner bevorzugten IDE öffnen. Ich verwende VS-Code mit diesem Hex-Reader-Plugin.

Hallo key, hallo WLAN-Passwort, hallo jede Zeichenfolge, die auf deinem uC gespeichert ist, kannst du mit Leichtigkeit auslesen.

Screenshot der Datei trick.bin, die ein Zertifikat enthält

Da dies so einfach war, sollten wir nun damit beginnen, das Gerät mit Hilfe der sofort einsatzbereiten Sicherheitsfunktionen von Espressif zu sichern.


Arduino-Code in PlatformIO vorbereiten

Wichtig ist hier, dass man den Offset jeder Partition für die spätere Verschlüsselung und das Flashen kennt. Was wir in platformIO nicht einstellen können, ist der Offset für die Partitionstabelle und den Bootloader, diese werden in platformIO selbst eingestellt. Du kannst diese Offsets ändern, indem du einige Quelldateien änderst, aber das ist nicht erforderlich, da du sie einfach in der ESP-IDF anpassen kannst. Zu deiner Information, die Standard-Offsets in platformIO:

  • Bootloader: 0x1000
  • Partion Tabelle: 0x8000
  • Firmware:0x10000


In meinem Beispiel verwende ich zwei OTA-Partitionen, die einen verschlüsselten nvs (non-versatile-storage) für die TLS-Zertifikate erzeugen.


Einrichten deines Verschlüsselungsarbeitsbereichs

Nachdem du deine Arduino-Firmware erfolgreich in einem separaten Projekt erstellt hast, lass uns einen separaten Verschlüsselungsarbeitsbereich erstellen, in dem das ESP-IDF läuft. Folge diesen Schritten, um das IDF zu installieren. Kopiere anschließend das hello_world-Beispiel aus dem IDF in deine Projektordner. Hier kannst du Flash-Verschlüsselungszertifikate und einige Shell-Dateien verwalten, die den Verschlüsselungs- und Flash-Prozess deiner Firmware auf deinem versandfertigen, sicheren ESP32 automatisieren. Erstell einen Flash-Verschlüsselungsschlüssel für jedes einzelne Gerät im Arbeitsbereich:

1. Flash-Verschlüsselungscode für das esp32 generieren


espsecure.py generate_flash_encryption_key my_flash_encryption_key.bin

2. Brenne den Schlüssel auf dein versandfertiges esp32


espefuse.py --port PORT burn_key flash_encryption my_flash_encryption_key.bin

Wenn der Schlüssel nicht gebrannt ist und das Gerät nach der Aktivierung der Flash-Verschlüsselung gestartet wird, generiert der ESP32 einen zufälligen Schlüssel, auf den die Software nicht zugreifen oder ihn ändern kann. Das ist genau das, was wir nicht wollen!


3. Setze die Bootloader-Log-Wortwahl auf (keine Ausgabe) - dies verringert die Größe des Bootloaders, so dass du die Flash-Verschlüsselung aktivieren kannst, ohne den Partitionstabellen-Offset in PlatformIO und im IDF zu ändern.


4. Aktiviere die Flash-Verschlüsselung (zum Testen des Verfahrens stelle den Verwendungsmodus auf "Nur Entwicklung" ein)

5. Überprüfe den Offset der Partitionstabelle auf Adresse 0x8000


Flashen und Ausführen deines Arduino-Codes auf dem ESP32

1. Erstelle deinen Arduino-Code in deinem Projektarbeitsbereich

2. von .pio/build/lolin32/ - kopiere die firmware.bin und die partitions.bin in deinen ESP-IDF-Verschlüsselungsarbeitsbereich.

3. Erstelle deine normale Anwendung mit: idf.py build

4. Als nächstes können wir alle Binärdateien, die wir zum Flashen unseres esp-32 benötigen, mit diesen Codezeilen verschlüsseln. Schließlich haben wir eine Mischung aus verschlüsselten Bootloader-Dateien oder Signal aus dem hello_world-Beispiel mit der aktivierten Flash-Verschlüsselungskonfiguration und der verschlüsselten Partitionstabelle und Firmware aus der Arduino-Anwendung.


espsecure.py encrypt_flash_data --keyfile flash_encryption_key.bin --Adresse 0x1000 --Ausgabe bootloader_encrypted.bin build/bootloader/bootloader.binespsecure.py encrypt_flash_data --keyfile flash_encryption_key.bin --Adresse 0x8000 --Ausgabe partition_table_encrypted.bin build/partition_table/partition-table.binespsecure.py encrypt_flash_data --Schlüsseldatei flash_encryption_key.bin --Adresse 0x10000 --Ausgabe firmware_encrypted.bin firmware.bin


5. Flashen des ESP32


esptool.py -p PORT -b 460800 --before default_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x1000 bootloader_encrypted.bin 0x8000 partition_table_encrypted.bin 0x10000 firmware_encrypted.bin

ERFOLG! Deine Anwendung sollte nun auf deinem UC laufen. Du kannst überprüfen, ob die Firmware auf deinem UC nun vollständig verschlüsselt ist und funktioniert.


Verschlüsselte NVS

Das verschlüsselte NVS ist der perfekte Ort zum Speichern von Schlüsseln und Zertifikaten, da diese trotz OTA-Updates hier verbleiben und separat aktualisiert werden können. Außerdem übernimmt die Arduino EEPROM-Bibliothek die NVS-Funktionen des ESP32.

Um das verschlüsselte NVS zu nutzen, muss die Firmware-Verschlüsselung aktiviert und die entsprechenden Flags in der Partitionstabelle auf verschlüsselt gesetzt werden (siehe Screenshot Teiltabelle oben). Sobald das Gerät hochgefahren ist, erstellt es automatisch einen NVS-Schlüssel, um alle mit dem NVS verarbeiteten Daten zu verschlüsseln.

Nachdem dies erfolgreich eingerichtet wurde, können wir alle Schlüssel und Zertifikate auf dem Gerät speichern. Dazu verwenden wir unseren entwickelten Aktualisierungsmechanismus, um auch veraltete Schlüssel zu aktualisieren. In meinem Beispiel verbinde ich mich mit einem passwortgeschützten TLS-gesicherten Broker, in den ich die neuen Schlüssel hochlade. Um Ressourcen zu sparen, habe ich einen weiteren vhost MQTT-Broker in rabbitmq erstellt.


OTA-Aktualisierungen

Nachdem eine gute Grundlage geschaffen wurde, indem alle sensiblen Daten in einem sicheren Fach, dem verschlüsselten NVS, gespeichert wurden, sind die OTA-Updates ziemlich einfach, da sie hoffentlich keine sensiblen Daten mit sich führen.

Glücklicherweise hat der Arduino ESP32 gerade begonnen, HTTPs OTA-Updates zu unterstützen, was bedeutet, dass du sicher sein kannst, dass das Gerät die richtige Firmware herunterladen wird. Für OTA-Updates funktioniert diese Bibliothek sehr gut. Zu beachten ist, dass die Firmware-Binärdatei verschlüsselt wird, sobald sie auf dem Gerät ist.


Einige letzte Worte

Dieser Ansatz spart dir Zeit bei der Entwicklung eines sicheren und schnellen IoT-Prototyps auf Basis von Arduinio und einem ESP32. Alle ESP32-Sicherheitsfunktionen mit Arduino-Code zum Laufen zu bringen, war eine Reise von Versuch und Irrtum, da der ESP32 nicht dafür gedacht ist, auf diese Weise verwendet zu werden. Aber vielleicht wird das in Zukunft der Fall sein. Am Ende freue ich mich, dir zu zeigen, wie man die grundlegenden Funktionen implementiert, die bereits laufen und getestet wurden, vor allem die Flash- und NVS-Verschlüsselung zusammen mit dem OTA-Update. Sicherlich deckt dieser Artikel nicht alle kleinen Fallstricke ab, in die man bei einer eigenen Implementierung geraten könnte.

Allerdings ist eine sichere Software-Implementierung nur so gut wie eine gute Hardware. Bei Verwendung der neuesten Hardwarerevision (ältere Versionen des esp32 werden ausgenutzt) ist es möglich, die Zertifikate für Flash-Verschlüsselung und sicheres Booten durch Spannungsglitching und Manipulation der R/W-Regeln des Speichers auszulesen.

Die hier beschriebenen IoT-Sicherheitsmaßnahmen für einen sicheren IoT-Prototyp waren nur ein Teil der Bemühungen. Es wurde auch viel Zeit in die richtige Einrichtung der Backend- und Frontend-Anwendung investiert.

Danke fürs Lesen! Ich hoffe, dieser kurze Überblick ist für deine bevorstehenden IoT-Prototyp-Projekte nützlich!

Bereit durchzustarten?

Lass uns austauschen und gemeinsam ein Projekt beginnen.

Arbeiten in einem Technologieunternehmen | Motius