NodeMCU (ESP8266) MQTT TLS

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:

  1. Using "espClient.setInsecure()" works fine, but this skips the certificate full validation.
  2. Using only the certificate authority certificate (ca.crt) and not the client certificate and key, it still doesn't connect to the MQTT server.
  3. Using "espClient.allowSelfSignedCerts();" gives another error: SSL error: Chain could not be linked to a trust anchor.
  4. Time synchronization in function "setClock()" is working fine, so no issues there.
  5. I even tried to regenerate the certificates using ECC not RSA for smaller sized certificates, but still won't work.
  6. 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 ?

I don't have an 8266, but poking around

It's possible BearSSL does not support the IP address in the SAN. It's included in the 8266 core as libbearssl.a; a (relatively) recent change in its history indicates the git submodule pointing to the 8266 port. Apparently the C code there is generated from a custom language called T0. (One of the TODOs for the project:

The T0 language shall be thoroughly explained and documented: syntax, compilation, structure, and rationale.

That was seven years ago.) Anyway, the interesting stuff is in the .t0 files. In particular, x509_minimal.t0 says

 *        -- Subject Alt Name: for the EE, dNSName names are matched
 *        against the server name. Ignored for non-EE.

EE stands for "end-entity", the actual server (not the CA). Later

	\ For the EE certificate, verify that the intended server name
	\ was matched.
	ee if
		eename zero-server-name or ifnot
			ERR_X509_BAD_SERVER_NAME fail
		then
	then

Tack a BR_ in front, and that's the error you're getting

/** \brief X.509 status: expected server name was not found in the chain. */
#define BR_ERR_X509_BAD_SERVER_NAME       56

To confirm this, you can try securely connecting to an MQTT server using a DNS name instead of an IP, like test.mosquitto.org

To actually fix/workaround the problem, maybe the RPi can be reachable via DNS, even mDNS as raspberry.local ?

The board platform also contains bearssl_x509.h, copied over from the fork, which says

 *   - The Subject Alt Name extension is parsed for names of type `dNSName`
 *     when decoding the end-entity certificate, and only if there is a
 *     server name to match. If there is no SAN extension, then the
 *     Common Name from the subjectDN is used.

Maybe create the server.crt with the IP address as the CN?

1 Like

That was also what I found, but was confused why it worked on other clients (like Raspberry Pi, python code running on PC) and not on the ESP8266.

That is what I tried before and again this morning, but still not working.
However, I remembered some post somewhere explaining that the BEAR SSL library for Arduinos was meant to be as simple as possible. So, I generated again the keys and certificates and kept everything to a minimum. So just the initial subject information, but no additional info like SAN or other parameters. And, voila, IT WORKS!!!!
I tried this before at some point, but the mosquitto configuration was wrong and ended up including the SAN in every key and certificate generation.

Now I generated 2 more client certificates for other Raspberry Pis to connect to the MQTT server. And of course they don't work because they require the SAN field with IP address and DNS in the server certificate.
So it's mutually exclusive:

  • Server certificate includes SAN: ESP8266 can't connect, other clients work
  • Server certificate does not include SAN: ESP8266 works, other client won't connect (certificate error)

As a workaround, I connected through the unsecured 1883 port with a python client to further process the data coming from the ESP8266.

@Kenb4 Thank you! I owe you a beer :slight_smile:

I know it was in the sketch and we are encouraged to post the complete sketch, but its probably not a good idea to post a private key in a public place....

Its probably not big deal since its on your internal network and you did obfuscate the other details. Its also easy enough to generate another cert and key.

1 Like

@BitSeeker I know, the certificates and keys were random. I have regenerated them a couple of times since the last post. Thanks for noticing!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.