ESP32 Certificate Pinning, das Einbetten einer Root-CA und die Nutzung des ESP-IDF-Zertifikatsbundles sind drei verschiedene Wege, sicherzustellen, dass Ihr Gerät mit dem echten MQTT-Broker spricht und nicht mit einem Betrüger. Dieses Tutorial vergleicht sie ehrlich, erklärt, welcher Weg Zertifikatserneuerungen übersteht, und liefert funktionierenden Arduino- und ESP-IDF-Code, der sich auf Port 8883 mit einem MQTT-Broker verbindet.
Warum Validierung auf Mikrocontrollern wichtig ist
Viele ESP32-Tutorials rufen client.setInsecure() auf, um „TLS zum Laufen zu bringen“. Das verschlüsselt zwar den Verkehr, überspringt aber die Serverauthentifizierung vollständig. Ein Angreifer im Netzwerk, etwa ein gefälschter Access Point mit derselben SSID, kann ein beliebiges Zertifikat präsentieren, MQTT-Benutzername und -Passwort des Geräts abgreifen und jede Nachricht weiterleiten oder verändern. Verschlüsselung ohne Authentifizierung schützt nur vor passiven Lauschern.
Anders als ein Laptop hat ein ESP32 keinen CA-Store eines Betriebssystems. Sie müssen entscheiden, wem das Gerät vertraut, und das in die Firmware schreiben. Diese Entscheidung bestimmt auch, wie sich das Gerät Jahre später verhält, wenn sich die Zertifikate des Brokers ändern. Deshalb verdient sie mehr Überlegung, als das erstbeste Snippet zu kopieren. Für Hersteller im DACH-Raum kommt hinzu, dass der EU Cyber Resilience Act und Normen wie IEC 62443 eine nachvollziehbare Serverauthentifizierung und einen Plan für Sicherheitsupdates über die gesamte Lebensdauer erwarten; der Beitrag zum EU Cyber Resilience Act für IoT ordnet die Pflichten ein.
Drei Validierungsstrategien im Vergleich
| Strategie | Wem das Gerät vertraut | Übersteht Leaf-Erneuerung | Übersteht CA-Wechsel | Flash-Bedarf | Aufwand |
|---|---|---|---|---|---|
| Eingebettete Root-CA | Einem oder zwei Root-Zertifikaten | Ja | Nur wenn die Root gleich bleibt | Gering (ca. 2 KB pro PEM) | Gering |
| ESP-IDF-Zertifikatsbundle | Vielen gängigen öffentlichen Roots | Ja | Meist ja | Größer (mehrere zehn KB) | Gering |
| Pinning von Leaf-Zertifikat oder Schlüssel | Genau einem Serverzertifikat oder Schlüssel | Oft nicht | Nein | Winzig | Hoch (erfordert OTA-Disziplin) |
Eingebettete Root-CA (empfohlener Standard)
Sie betten das Root-Zertifikat ein, das die Kette des Brokers verankert. Jedes gültige Zertifikat, das unter dieser Root für den richtigen Hostnamen ausgestellt wurde, wird akzeptiert, sodass routinemäßige Erneuerungen für das Gerät unsichtbar bleiben. Das ist für die meisten Projekte die richtige Wahl. Streng genommen ist es ebenfalls eine Form von Pinning: Sie binden das Vertrauen an eine einzige CA statt an die Hunderte, denen ein Browser vertraut.
ESP-IDF-Zertifikatsbundle
ESP-IDF liefert ein Bundle gängiger Root-Zertifikate (abgeleitet von der Mozilla-Liste), das in die Firmware kompiliert werden kann. Das Gerät vertraut denselben öffentlichen CAs wie ein Browser. Sie geben etwas Strenge auf, aber wenn der Broker jemals zu einer anderen öffentlichen CA wechselt, funktionieren die Geräte weiter.
Leaf- oder Public-Key-Pinning
Sie codieren den Fingerprint des exakten Serverzertifikats oder öffentlichen Schlüssels fest ein. Das bietet das engste Vertrauen, bricht aber jedes Mal, wenn das Serverzertifikat ersetzt wird. Bei Let's-Encrypt-Zertifikaten, die kurzlebig sind und automatisch erneuert werden, wird das Leaf-Zertifikat alle paar Monate oder früher ersetzt, und auch der Schlüssel kann sich ändern. Das Leaf eines solchen Brokers zu pinnen, ist ein zuverlässiger Weg, eine Flotte offline zu nehmen. Pinnen Sie Leaf-Schlüssel nur, wenn Sie den Server kontrollieren, den Schlüssel bewusst stabil halten und vor jeder Änderung OTA-Updates ausrollen können.
ESP32 Certificate Pinning auf eine Root-CA mit Arduino
Der ConnectMQ-Broker präsentiert ein Let's-Encrypt-Zertifikat. Die aktuellen Ketten von Let's Encrypt führen zu ISRG Root X1 (RSA), außerdem betreibt Let's Encrypt ISRG Root X2 (ECDSA). Wenn Sie beide einbetten, erhalten Sie einen engen Vertrauenskreis mit Spielraum für einen künftigen Kettenwechsel. Laden Sie die PEM-Dateien von der Zertifikatsseite von Let's Encrypt herunter und fügen Sie ihren vollständigen Inhalt nacheinander in den folgenden String ein. mbedTLS parst mehrere aneinandergehängte PEM-Zertifikate aus einem einzigen Puffer.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 8883;
const char* CLIENT_ID = "esp32-pin-5b21"; // auf einem privaten Broker YOUR_CLIENT_ID verwenden
const char* TOPIC = "connectmq/demo/5b21/status";
// Gepinnte Vertrauensanker: vollständige PEMs von ISRG Root X1 und ISRG Root X2 einfügen
static const char PINNED_ROOTS[] = R"PEM(
-----BEGIN CERTIFICATE-----
...ISRG Root X1...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...ISRG Root X2...
-----END CERTIFICATE-----
)PEM";
WiFiClientSecure tlsClient;
PubSubClient mqtt(tlsClient);
void syncClock() {
configTime(0, 0, "pool.ntp.org", "time.google.com");
Serial.print("Waiting for NTP");
while (time(nullptr) < 1700000000) {
delay(300);
Serial.print(".");
}
Serial.println(" done");
}
void connectMqtt() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT over TLS... ");
// Privater Broker: mqtt.connect(CLIENT_ID, "YOUR_USERNAME", "YOUR_PASSWORD")
if (mqtt.connect(CLIENT_ID)) {
Serial.println("connected");
mqtt.publish(TOPIC, "online", true);
} else {
char err[100];
tlsClient.lastError(err, sizeof(err));
Serial.printf("failed, state=%d, tls=%s\n", mqtt.state(), err);
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
Serial.println("Wi-Fi connected");
syncClock();
tlsClient.setCACert(PINNED_ROOTS);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(512);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last > 10000) {
last = millis();
char msg[48];
snprintf(msg, sizeof(msg), "{\"uptime\":%lu}", millis() / 1000);
mqtt.publish(TOPIC, msg);
}
}
Den Aufruf lastError() sollten Sie auch in Produktions-Builds behalten: Er macht aus einem vagen state=-2 eine lesbare mbedTLS-Meldung, etwa zu einem abgelaufenen Zertifikat oder einer unbekannten CA. Die Hostname-Prüfung erfolgt automatisch, weil WiFiClientSecure den Hostnamen über setServer() erhält. Ersetzen Sie den Hostnamen daher niemals durch eine IP-Adresse.
Das ESP-IDF-Zertifikatsbundle verwenden
Wenn Sie direkt mit ESP-IDF bauen, kann die Komponente esp-mqtt gegen das eingebaute Bundle prüfen. Aktivieren Sie es in menuconfig unter Component config, mbedTLS, Certificate Bundle (die Standardoption mit gängigen CAs genügt), dann:
#include "esp_crt_bundle.h"
#include "esp_log.h"
#include "mqtt_client.h"
static const char *TAG = "mqtt_bundle";
static void mqtt_event_handler(void *args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event = event_data;
if (event_id == MQTT_EVENT_CONNECTED) {
ESP_LOGI(TAG, "Connected over TLS");
esp_mqtt_client_publish(event->client,
"connectmq/demo/5b21/idf", "hello", 0, 1, 0);
} else if (event_id == MQTT_EVENT_ERROR) {
ESP_LOGE(TAG, "MQTT error, check TLS and credentials");
}
}
void mqtt_start(void)
{
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtts://broker.connectmq.com:8883",
.broker.verification.crt_bundle_attach = esp_crt_bundle_attach,
.credentials.client_id = "esp32-idf-5b21",
// .credentials.username = "YOUR_USERNAME",
// .credentials.authentication.password = "YOUR_PASSWORD",
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID,
mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}
Rufen Sie mqtt_start() auf, nachdem das WLAN verbunden ist und SNTP die Uhrzeit gesetzt hat. Um stattdessen in ESP-IDF eine einzelne Root zu pinnen, setzen Sie .broker.verification.certificate auf Ihren eingebetteten PEM-String und lassen crt_bundle_attach weg. Die ESP-MQTT-Dokumentation von Espressif listet alle Prüfoptionen auf.
Eine unbrauchbare Flotte vermeiden
Welche Strategie Sie auch wählen, planen Sie für den Tag, an dem sich Vertrauensanker ändern:
- Pinnen Sie Roots, nicht Leafs, es sei denn, Sie verantworten den Lebenszyklus des Serverschlüssels.
- Betten Sie einen Ersatzanker ein, etwa eine zweite Root derselben CA, damit ein Kettenwechsel die Konnektivität nicht unterbricht.
- Halten Sie OTA-Updates unabhängig vom MQTT-Vertrauen, oder testen Sie zumindest, dass der OTA-Pfad noch funktioniert, wenn MQTT-TLS fehlschlägt. Unser Leitfaden zum ESP32-OTA-Update über MQTT zeigt einen Ansatz.
- Synchronisieren Sie die Zeit vor dem Verbinden. Je nach Core-Konfiguration prüft mbedTLS die Gültigkeitsdaten von Zertifikaten, und eine Uhr, die im Jahr 1970 hängt, lässt jedes Zertifikat als noch nicht gültig erscheinen.
- Protokollieren Sie TLS-Fehler remote, sobald eine Verbindung schließlich gelingt, damit Sie Validierungsprobleme im Feld früh bemerken.
- Verfolgen Sie CA-Ankündigungen. Öffentliche CAs kündigen Änderungen an Roots und Zwischenzertifikaten lange im Voraus an; tragen Sie sich eine Erinnerung in Ihren Wartungskalender ein.
Fehlerbehebung
state=-2mit „X509 - Certificate verification failed“: falsches oder abgeschnittenes PEM, oder die eingebettete Root verankert die Kette des Brokers nicht. Prüfen Sie das mitopenssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -showcerts.- „The certificate validity starts in the future“: NTP ist noch nicht abgeschlossen.
- Speichermangel während des Handshakes: TLS braucht grob mehrere zehn Kilobyte Heap; geben Sie Puffer frei, reduzieren Sie andere Allokationen und vermeiden Sie mehrere gleichzeitige TLS-Sockets.
- Funktioniert auf 1883, scheitert auf 8883: Eine Firewall blockiert eventuell 8883. Probieren Sie Secure WebSocket auf 8084, wenn Ihre Bibliothek es unterstützt.
Weitere Symptome finden Sie unter MQTT-TLS-Handshake-Fehler beheben und in unserem Tutorial ESP32 MQTT über SSL.
Die richtige Strategie für Ihr Projekt wählen
Für ein Hobbyprojekt oder eine kleine Flotte, die Sie von Hand neu flashen können, ist die eingebettete Root-CA der klare Gewinner: wenige Zeilen Code, geringer Flash-Bedarf und keine Überraschungen bei der Zertifikatserneuerung. Bei einem kommerziellen Produkt, das jahrelang im Feld bleibt, sollten Sie zuerst das Szenario eines CA-Wechsels durchdenken. Betten Sie entweder zwei Roots der CA des Brokers ein oder nutzen Sie das ESP-IDF-Bundle, damit Geräte jeder gängigen öffentlichen CA weiter vertrauen, und stellen Sie sicher, dass Ihr OTA-Pfad funktioniert, falls MQTT-TLS jemals fehlschlägt. Leaf-Pinning gehört nur in Systeme, in denen Sie den Server betreiben, seinen Schlüssel bewusst stabil halten und einen getesteten Prozess haben, um einen neuen Pin auszurollen, bevor sich der Schlüssel ändert. Was immer Sie wählen: Dokumentieren Sie die Entscheidung neben der Firmware-Version, damit spätere Betreuer der Flotte wissen, welche Vertrauensanker wo im Einsatz sind.
Warum ein verwalteter MQTT-Broker das Pinning vereinfacht
Pinning ist nur so stabil wie das Zertifikatsmanagement des Servers. Bei einem selbst betriebenen Broker verantworten Sie Erneuerungen, Kettenwahl und Schlüsselwechsel, und jeder Fehler zeigt sich als Gerät, das offline ist. Ein verwalteter MQTT-Broker wie ConnectMQ erneuert sein öffentlich vertrauenswürdiges Let's-Encrypt-Zertifikat automatisch, sodass Geräte, die die Let's-Encrypt-Roots pinnen, auch über routinemäßige Erneuerungen hinweg verbunden bleiben. Sie können mit dem obigen Code auf dem öffentlichen MQTT-Broker prototypen und dann auf private Zugangsdaten umsteigen, ohne den TLS-Code anzufassen. Für den vollständigen geräteseitigen Ablauf beginnen Sie mit unserem ESP32-IoT-Tutorial.
Starten Sie mit ConnectMQ
Registrieren Sie sich unter app.connectmq.com, wählen Sie den kostenlosen Serverless-Plan ohne Kreditkarte und erstellen Sie einen Broker. Kopieren Sie den generierten Benutzernamen, das Passwort und die Client ID in den obigen Sketch, behalten Sie die gepinnten Roots bei, und Ihr ESP32 verbindet sich auf Port 8883 mit vollständiger Zertifikatsprüfung.
Häufig gestellte Fragen
Ist setInsecure() zum Testen akzeptabel?
Nur auf dem Labortisch mit Wegwerf-Zugangsdaten. Es deaktiviert die Serverauthentifizierung; liefern Sie daher niemals Firmware aus, die es verwendet.
Sollte ich den Zertifikats-Fingerprint von broker.connectmq.com pinnen?
Nein. Das Serverzertifikat wird automatisch erneuert, und sein Fingerprint ändert sich. Pinnen Sie stattdessen die Root-Zertifikate von Let's Encrypt.
Wie viel Flash belegt das ESP-IDF-Zertifikatsbundle?
Das hängt davon ab, welche CA-Liste Sie in menuconfig auswählen; die vollständige Liste gängiger CAs kostet mehrere zehn Kilobyte, ein einzelnes eingebettetes Root-PEM etwa zwei Kilobyte.
Ersetzt Certificate Pinning Benutzername und Passwort?
Nein. Pinning authentifiziert den Broker gegenüber dem Gerät. Das Gerät braucht weiterhin eigene Zugangsdaten, damit der Broker es authentifizieren kann.



