Hello,
I need some help with my MQTT connection with SSL/TLS. The setup: NodeMCU (ESP8266) connected to Wifi, which connects to a MQTT server running on a Raspberry Pi5 (mosquitto service) connected to the same Wifi.
First the MQTT server configuration (mosquitto.conf):
per_listener_settings true
pid_file /run/mosquitto/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_type all
log_dest file /var/log/mosquitto/mosquitto.log
connection_messages true
include_dir /etc/mosquitto/conf.d
# ========================================
# PORT 1883 - Username/Password Authentication
# ========================================
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd
# ========================================
# PORT 8883 - TLS Certificate Authentication
# ========================================
listener 8883
protocol mqtt
# Server certificates
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
# Client certificate authority
cafile /etc/mosquitto/certs/ca.crt
require_certificate true
# Security settings
tls_version tlsv1.2
allow_anonymous false
password_file /etc/mosquitto/passwd
For the unsecure port 1883, the connection of the NodeMCU works great, publishing and subscribing, no problems at all. However, then I try to use port 8883 which requires certificates, I can't get the NodeMCU to connect to the MQTT server. I always get an SSL error. Here is the Arduino IDE code for the NodeMCU:
const char client_private_key[] PROGMEM = R"EOF(
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC18gfeZy68YX5v
G5Luq9k7yeTOb9tVg6tYwdA7UWrZaHxhhQz5WW7OLYXRWoz3SPFoHUmd3Gev+a9Z
1hUWL4mUfYFPzSwNVojUBwl4FC7WD+YidV200HpMzsayvPvHIhosos5W/Qn/4oPp
tSHH5kqVBBAIk2olYxWGWyF17ry/G+meQ6ngMHfYVnG6qGCxM1Ekij+P+3wK8wJk
Ql90NOkZtGR1oOEG5gEKnBPO/oUMFNe9luFMdX0tPOVukmFxLz+9EHzgxe9YzuIN
haHj/P7VYf2xvPC3CtDYX/NSbA8Hq5ob5U/ScGeLkM71kE3kpAuXO9VWxq2QBuil
KRpYJkc9AgMBAAECggEAFYr1hw4ZEMmLZpzxcgyLcWKXkqDYa8DmGCGEpg1fFSRj
u8k87sY50jQ+KLprv/jVSpJTLduTtFigPEcWopSY2endVoVQzrh9BXva0Wq0W/iw
c0qJwJfBr1f5W174gczGJBxXyaF23mMj4tdlS023bXr4ageIVSps+UZPiS7uDsOM
Phom+FrFt6zHW4kQul5li9zDFK12s51W6P9M7bUrBxXtkWi5rwSz9PQ7GK4nGT8g
WTlolcvNQjy9tT5sILCoZm/j98RsSniBU+R7CktcxrOTl6ewao0XR9Si9xbVIFSm
ZfWPYdSmy6IHpy83yuuW98XDO9JwoABzS4I62NKNGQKBgQDsMpddS+iFB74v1312
yakIFKIoOzUcs8EMuZ2/PMwwuaddTdFw4hWO0k/90JuFkzlE6oCQZWCGkn22+Ok3
joI6FKUWIZNDH2pNeU3H50OffowGH8gnnyuHEXOGiFYB8j/ul4qpB2DlaYKCvdAY
N1JRbtrxSNYEEWA3ZP3F/wLgVQKBgQDFMwxCNBJvY8AI71odYWVKAIfu5ylhtIIV
ydhqYBElkPPxELk9j5nOWYNAxwMCqLYs5XRcygEXwkcptZ4Ikb+VrQdV1XqCGTeW
cCYrsjv0q3Ql5eRSHuQJQgodeOC5AnVSCuASRb5r0EtFjgi9mCjY2heRlUESD5O4
ngCcePYTSQKBgBNAtHHYZCWj0e7YY9Ow8GRGOkmJOfUS+eNtpdwWupYmK+xBEZdf
9l3j6r1HqcWjkIF7k+sxOEL/4dtuQqRSq27MjABX0EciYmnaP2rSAQMZL5S4Nnp0
UPn7hC0tI8WY13k9RQIuJcnNZeP+UN4IdF9K5/HO/BLsz6j0RHNFLBjZAoGADzIB
zdApY3/yOj4HWaA5p9wmnVgerjK0m200Se4hJB3pTjgVrhK0rVvka0dqiffqIceE
kenW77e+uA4QoWRpuOTeXdTXXdMHn4YAyvku+YmEp67xSwqqU7RNBzxbRKZV1/6S
iZXqOHCSfEeXkNR6wUr86fDwVK3ymhVCwsvnWlECgYEA6pfeT0l4UHBoKCNB8n7w
z6/UuN0j561K9S6tbui7KVQmA7wIPgPvM5fSGlNfxArXlxByFt0jYgZ4EoOSM9uJ
EvwBNvV+EbVo4SZnneh1hfcgryZc9x6vOVXQcq6IWlT9Su2rvpCNh748sWHc/izD
AfKL5JeIn4XKmL4A0QUU2e0=
-----END PRIVATE KEY-----
)EOF";
const char client_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDVTCCAj0CFDI9ov2lL/AfyjdRit2I+h1pl6QWMA0GCSqGSIb3DQEBCwUAMGMx
CzAJBgNVBAYTAlJPMQ0wCwYDVQQIDARDbHVqMQ0wCwYDVQQHDARDbHVqMRMwEQYD
VQQKDApVVENOIE9VPUNTMQ8wDQYDVQQDDAZNUVRUQ0ExEDAOBgkqhkiG9w0BCQEW
AS4wHhcNMjUwNTAyMTAwODMxWhcNMjYwNTAyMTAwODMxWjBrMQswCQYDVQQGEwJS
TzENMAsGA1UECAwEQ2x1ajENMAsGA1UEBwwEQ2x1ajENMAsGA1UECgwEVVRDTjEL
MAkGA1UECwwCQ1MxEDAOBgNVBAMMB0VTUDgyNjYxEDAOBgkqhkiG9w0BCQEWAS4w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC18gfeZy68YX5vG5Luq9k7
yeTOb9tVg6tYwdA7UWrZaHxhhQz5WW7OLYXRWoz3SPFoHUmd3Gev+a9Z1hUWL4mU
fYFPzSwNVojUBwl4FC7WD+YidV200HpMzsayvPvHIhosos5W/Qn/4oPptSHH5kqV
BBAIk2olYxWGWyF17ry/G+meQ6ngMHfYVnG6qGCxM1Ekij+P+3wK8wJkQl90NOkZ
tGR1oOEG5gEKnBPO/oUMFNe9luFMdX0tPOVukmFxLz+9EHzgxe9YzuINhaHj/P7V
Yf2xvPC3CtDYX/NSbA8Hq5ob5U/ScGeLkM71kE3kpAuXO9VWxq2QBuilKRpYJkc9
AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD2G8YcCPMaWrCZ27PfdDS+Y+CUUBMa0
OSvEAyY44gocYNBBAVGYJ0wc90W23CcYlGNoZBGgFNem1/RwTbGEM0gxMidBN/Ve
d2StUF7MgeoTTmhTot8WzHbhLvFkCodT6gdTUDxGTUMcq+KNpkZWgAJieslmGXuz
XW8k9c3PKRWP+JIbfcMxX9pZnYoFStBl2NH8FvCP52bXUW7SifjtZ8w9hNOxD99Q
pR/0FXrN59RbcUrePN3U/Zm/X4maYztL5f4p159Bob7fZeyt+WEpttCYpZwqYBGy
6W+t+Z30t+yQ5SxC1/TKqrCrZsUP31LQmNM/mczUAYPE3LIWcgVFyQk=
-----END CERTIFICATE-----
)EOF";
static const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDpzCCAo+gAwIBAgIUGJxUafCgcaj10+tt8KZHrnqAc4gwDQYJKoZIhvcNAQEL
BQAwYzELMAkGA1UEBhMCUk8xDTALBgNVBAgMBENsdWoxDTALBgNVBAcMBENsdWox
EzARBgNVBAoMClVUQ04gT1U9Q1MxDzANBgNVBAMMBk1RVFRDQTEQMA4GCSqGSIb3
DQEJARYBLjAeFw0yNTA1MDIxMDA4MzFaFw0zNTA0MzAxMDA4MzFaMGMxCzAJBgNV
BAYTAlJPMQ0wCwYDVQQIDARDbHVqMQ0wCwYDVQQHDARDbHVqMRMwEQYDVQQKDApV
VENOIE9VPUNTMQ8wDQYDVQQDDAZNUVRUQ0ExEDAOBgkqhkiG9w0BCQEWAS4wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHT4eH2XEpKrm0cDAiicttBDLQ
YztH+dQ4/Q20a/AB6cDXwjcGIPY51Qa6xt4JJqYbGisWmClql6Gd73Qs2XJXvCf1
9J48uhGVI1OkEu3KmSqSHD4RivUMlDpdQwXMl3756PDua4nw+L4mXRSjlzYjP1rJ
MjsRJaaU6n4222FlL+hjjS/bLbKXUfv6JJoSgtROHukbiA670bwFladRU6uVA1mh
fZ7JPU/LqKIkPtE8KdhnENBSwPqKMe5C9YZdOrTc79I6PiChN07n57XykXhCsu9M
GbqRTCVkMY8cbZbfc08maogSUWW3+SOJ9bxkvRwW3wr2kv0VVHrcAtIjN2WDAgMB
AAGjUzBRMB0GA1UdDgQWBBR57M/b5ssnpmt2qs4bWC2bwM7ObzAfBgNVHSMEGDAW
gBR57M/b5ssnpmt2qs4bWC2bwM7ObzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQA12mkK1z5H2L3aVssCxTApn23GWFrTmAQYv5IaR8MQiQfojx/t
GmWW0NLJaRtZDOLOiyOXW2w+bUs1FnSt4XGZDVihCG+nd2mBxZQrdwUyyTsHV3uv
pPLwXWTfCJOw+QBDl8GgH5phPVV3f0LjBE+S1tBBqZaIHdHLpynrnkW5VOpNIvTB
AeIt26U+dMLHS0PNlGGvg05wh1s4dhTYPVDU5YDGf7KnAF20zmX4cRsN4aEH8E5K
N4qQWsHvWLhJIf2DAX34KYNmDTkHsHmHy/hR93XzzvGdse/YEk7LFQNea+obcila
k7ugFXyQhP7pZQaqLtHv+GVOb4PW2Kitktvt
-----END CERTIFICATE-----
)EOF";
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "************";
const char* password = "*************";
const char* mqtt_server = "192.168.0.52";
const char* MQTT_USERNAME = "******";
const char* MQTT_PASSWORD = "******";
BearSSL::WiFiClientSecure espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;
void setClock() {
configTime(2 * 3600, 3600, "pool.ntp.org", "time.nist.gov", "time.google.com");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
// Wait until time is synced (year > 2020)
while (now < 1700000000) { // Wait until after 2020-09-13
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("");
struct tm timeinfo;
localtime_r(&now, &timeinfo); // Use localtime_r instead of gmtime_r
Serial.print("Current LOCAL time: ");
Serial.print(asctime(&timeinfo));
}
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Switch on the LED if an 1 was received as first character
if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level
// but actually the LED is on; this is because
// it is active low on the ESP-01)
} else {
digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH
}
}
void reconnect() {
char err_buf[256];
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266_Client1";
// Attempt to connect
if (client.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
//if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("esp8266/client1", "hello world");
// ... and resubscribe
client.subscribe("rpi/broadcast");
} else {
Serial.print("failed, rc=");
Serial.println(client.state());
espClient.getLastSSLError(err_buf, sizeof(err_buf));
Serial.print("SSL error: ");
Serial.println(err_buf);
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
setClock(); // Required for X.509 validation
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(ca_cert, sizeof(ca_cert));
BearSSL::X509List *serverCertList = new BearSSL::X509List(client_cert, sizeof(client_cert));
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(client_key, sizeof(client_key));
espClient.setBufferSizes(4096, 512);
espClient.setTrustAnchors(serverTrustedCA);
espClient.setClientRSACert(serverCertList, serverPrivKey);
//espClient.allowSelfSignedCerts();
//espClient.setInsecure(); //for testing only!!! - works fine
client.setServer(mqtt_server, 8883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long now = millis();
if (now - lastMsg > 5000) {
lastMsg = now;
++value;
snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value);
Serial.print("Publish message: ");
Serial.println(msg);
client.publish("esp8266/client1", msg);
}
}
The error I am now receiving in the Serial Monitor:
Attempting MQTT connection...failed, rc=-2
SSL error: Expected server name was not found in the chain.
try again in 5 seconds**
I have done a lot of debugging in the last couple of weeks, but no success whatsoever. I have tried the following:
- Using "espClient.setInsecure()" works fine, but this skips the certificate full validation.
- Using only the certificate authority certificate (ca.crt) and not the client certificate and key, it still doesn't connect to the MQTT server.
- Using "espClient.allowSelfSignedCerts();" gives another error: SSL error: Chain could not be linked to a trust anchor.
- Time synchronization in function "setClock()" is working fine, so no issues there.
- I even tried to regenerate the certificates using ECC not RSA for smaller sized certificates, but still won't work.
- Tried with DER format for the ESP8266, same error.
I used the same certificates (CA, client and client key) in a Python code with another Raspberry Pi5 connected to the same network and it works great. Even tried with a PC, also working just fine.
So, I assume it must be either the libraries used on the ESP8266/NodeMCU, or it just doesn't have enough SRAM to validate the certificates and securely connect to the MQTT.
Here is also the script used to generate the certificates:
#!/bin/bash
# Certificate Authority (CA) Subject Information
COUNTRY="****"
STATE="****"
LOCALITY="****"
ORGANIZATION="****"
ORGANIZATIONAL_UNIT="****"
CA_COMMON_NAME="MQTTCA"
EMAIL="."
# Server Certificate Subject Information
SERVER_COMMON_NAME="raspberry" # <-- Changed to hostname
SERVER_SAN="IP:192.168.0.52,DNS:raspberry" # <-- SAN defined here
SERVER_EMAIL="."
CLIENT_COMMON_NAME="ESP8266"
# Remove previous files
sudo rm -rf /etc/mosquitto/certs
sudo rm -f /home/admin/{ca.*,ca_cert.*,client.*,client_cert.*,client_key.*,server.*}
echo -e "\e[32mDeleted previous files\e[0m\n"
# Create CA (RSA 2048)
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \
-subj "/C=$COUNTRY/ST=$STATE/L=$LOCALITY/O=$ORGANIZATION OU=$ORGANIZATIONAL_UNIT/CN=$CA_COMMON_NAME/emailAddress=$EMAIL"
echo -e "\e[32mGenerated certificate: CA\e[0m\n"
# Create Server Certificate (RSA 2048)
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
-subj "/C=$COUNTRY/ST=$STATE/L=$LOCALITY/O=$ORGANIZATION/OU=$ORGANIZATIONAL_UNIT/CN=$SERVER_COMMON_NAME/emailAddress=$SERVER_EMAIL" \
-addext "subjectAltName=$SERVER_SAN" # <-- SAN added here
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 \
-extfile <(printf "subjectAltName=IP:192.168.0.52,DNS:raspberry")
echo -e "\e[32mGenerated certificate: Server\e[0m\n"
# Create Client Certificate (RSA 2048)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
-subj "/C=$COUNTRY/ST=$STATE/L=$LOCALITY/O=$ORGANIZATION/OU=$ORGANIZATIONAL_UNIT/CN=$CLIENT_COMMON_NAME/emailAddress=$SERVER_EMAIL"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
echo -e "\e[32mGenerated certificate: Client\e[0m\n"
# Copy files to Mosquitto
sudo mkdir -p /etc/mosquitto/certs
sudo cp ca.crt server.* /etc/mosquitto/certs/
sudo chown -R mosquitto:mosquitto /etc/mosquitto/certs
echo -e "\e[32mCertificates copied to Mosquitto\e[0m\n"
# Convert for ESP8266 (RSA version)
openssl x509 -in ca.crt -out ca.der -outform DER
openssl x509 -in client.crt -out client.der -outform DER
openssl rsa -in client.key -out client_key.der -outform DER # <- Changed from 'ec' to 'rsa'
xxd -i ca.der > ca_cert.h
xxd -i client.der > client.h
xxd -i client_key.der > client_key.h
echo -e "\e[32mESP8266 files generated\e[0m\n"
# Verify
echo "Server Certificate SAN:"
openssl x509 -in server.crt -noout -text | grep -A1 "Subject Alternative Name"
# Restart Mosquitto
sudo systemctl restart mosquitto
echo -e "\n\e[32mMosquitto service restarted!\e[0m"
Note: The "*****" above are just for privacy concerns.
I am running out of patience. I checked hundreds of websites and tutorials, but still doesn't work. It might be something minor that I am missing, but considering that it's working on other devices, it might just be due to the lack of resources like SRAM.
Any advice / recommendations ?