Introduction
In the world of Internet of Things, secure and reliable communication is of paramount importance. MQTT (Message Queuing Telemetry Transport) is a lightweight messaging protocol widely used in IoT applications. MQTT is designed to be lightweight and efficient, making it ideal for resource-constrained devices like the ESP32. The protocol uses a publish-subscribe model, allowing devices to efficiently send and receive messages with minimal overhead.
And if we want to transmit sensitive data over MQTT, using a secure connection becomes crucial. Luckily, MQTT supports various security features, including authentication, access control, and encryption. This ensures secure communication and protects devices and data from unauthorized access or tampering.
In this guide, we will explore how to establish an MQTT over TLS/SSL (MQTTS) connection between an ESP32 PLC and an MQTTS server.
Requirements
In the sketch example we have used an ESP32 PLC 21+, but it will work with any other ESP32 PLC model.
You will also need to install the SSLClient library, and PubSubClient library, the MQTT library recommended for Industrial Shields PLCs. You can install it through the Arduino IDE Library manager.
And finally, the examples provided connect to test.mosquitto.org, an MQTT/S online broker used for testing purposes. In order to be able to connect to it, you will need to generate your own TLS certificate, send it to the broker and generate some files for the ESP32 program. If you don't know how to do this, read the next section to learn about it.
Prepare the TLS files
In order to establish a secure connection with test.mosquitto.org, we will need the following:
The Certificate Authority (CA) certificate of test.mosquitto.org, available in this link. It will allow the ESP32 to verify that the server being connected to is the one that we want, and not someone malicious. The SSLClient library does so with trust anchors, hard-coded arrays that represents the CA certificate of the server.
A pair of asymmetric keys to establish the encryption between the server and the client. The public key will need to be signed by the test.mosquitto.org. This is necessary in order to evade any type of tampering in the public key.
First of all, we will generate the pair of asymmetric keys for the encryption. To do so, you will need to execute the following commands:
openssl genrsa -aes256 -out client.key
openssl req -new -out client.csr -key client.key
The first command will ask you for a passphrase to encrypt the private key, and the second command will ask that passphrase, and that you enter some information for the certificate. Do not leave the default values in "Common Name", "State or Province Name" and "Organization Name" fields, as test.mosquitto.org will not sign your certificate.
After this two commands, you will have two files: "client.csr" and "client.key". You will then need to copy "client.csr" in this web to get your certificate signed by the CA, a file called "client.crt".
Second of all, you will need to generate the trust anchors file for the SSLClient library. In this case, the CA certificate is not directly available online, so you cannot use the BearSSL Certificate online utilty, which works by specifying the domains you want the certificate of. You will have to use other tools, like pycert_bearssl.py, an script that converts the certificate to a BearSSL compatible format. To use it, download the CA certificate and use the script with the command "python pycert_bearssl.py convert --no-search mosquitto.org.crt -o trust_anchors.h".
For simplicity we provide you the trust_anchors.h file for the test.mosquitto.org CA certificate , which is the following:
#ifndef _CERTIFICATES_H_
#define _CERTIFICATES_H_
#ifdef __cplusplus
extern "C"
{
#endif
/* This file is auto-generated by the pycert_bearssl tool. Do not change it manually.
* Certificates are BearSSL br_x509_trust_anchor format. Included certs:
*
* Index: 0
* Label: mosquitto.org
* Subject: [email protected],CN=mosquitto.org,OU=CA,O=Mosquitto,L=Derby,ST=United Kingdom,C=GB
* Type: Certificate Authority
*/
#define TAs_NUM 1
static const unsigned char TA_DN0[] = {
0x30, 0x81, 0x90, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
0x13, 0x02, 0x47, 0x42, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
0x08, 0x0c, 0x0e, 0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, 0x4b, 0x69,
0x6e, 0x67, 0x64, 0x6f, 0x6d, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55,
0x04, 0x07, 0x0c, 0x05, 0x44, 0x65, 0x72, 0x62, 0x79, 0x31, 0x12, 0x30,
0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x4d, 0x6f, 0x73, 0x71,
0x75, 0x69, 0x74, 0x74, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
0x04, 0x0b, 0x0c, 0x02, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03,
0x55, 0x04, 0x03, 0x0c, 0x0d, 0x6d, 0x6f, 0x73, 0x71, 0x75, 0x69, 0x74,
0x74, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x72,
0x6f, 0x67, 0x65, 0x72, 0x40, 0x61, 0x74, 0x63, 0x68, 0x6f, 0x6f, 0x2e,
0x6f, 0x72, 0x67,
};
static const unsigned char TA_RSA_N0[] = {
0xc1, 0x34, 0x1c, 0xa9, 0x88, 0xcd, 0xf4, 0xce, 0xc2, 0x42, 0x8b, 0x4f,
0x74, 0xc7, 0x1d, 0xef, 0x8e, 0x6d, 0xd8, 0xb3, 0x6a, 0x63, 0xe0, 0x51,
0x99, 0x83, 0xeb, 0x84, 0xdf, 0xdf, 0x32, 0x5d, 0x35, 0xe6, 0x06, 0x62,
0x7e, 0x02, 0x11, 0x76, 0xf2, 0x3f, 0xa7, 0xf2, 0xde, 0xd5, 0x9c, 0xf1,
0x2d, 0x9b, 0xa1, 0x6e, 0x9d, 0xce, 0xb1, 0xfc, 0x49, 0xd1, 0x5f, 0xf6,
0xea, 0x37, 0xdb, 0x41, 0x89, 0x03, 0xd0, 0x7b, 0x53, 0x51, 0x56, 0x4d,
0xed, 0xf1, 0x75, 0xaf, 0xcb, 0x9b, 0x72, 0x45, 0x7d, 0xa1, 0xe3, 0x91,
0x6c, 0x3b, 0x8c, 0x1c, 0x1c, 0x6a, 0xe4, 0x19, 0x8e, 0x91, 0x88, 0x34,
0x76, 0xa9, 0x1d, 0x19, 0x69, 0x88, 0x26, 0x6c, 0xaa, 0xe0, 0x2d, 0x84,
0xe8, 0x31, 0x5b, 0xd4, 0xa0, 0x0e, 0x06, 0x25, 0x1b, 0x31, 0x00, 0xb3,
0x4e, 0xa9, 0x90, 0x41, 0x62, 0x33, 0x0f, 0xaa, 0x0d, 0xf2, 0xe8, 0xfe,
0xcc, 0x45, 0x28, 0x1e, 0xaf, 0x42, 0x51, 0x5e, 0x90, 0xc7, 0x82, 0xca,
0x68, 0xcb, 0x09, 0xb3, 0x70, 0x3c, 0x9c, 0xaa, 0xca, 0x11, 0x66, 0x3d,
0x6c, 0x22, 0xa3, 0xf3, 0xc3, 0x32, 0xbb, 0x81, 0x4f, 0x33, 0xc7, 0xdd,
0xc8, 0xa8, 0x06, 0x7a, 0xc9, 0x58, 0xa5, 0xdc, 0xdc, 0xe8, 0xd7, 0x74,
0xb1, 0x85, 0x24, 0xe7, 0xe3, 0xee, 0x93, 0xf4, 0x8f, 0xf7, 0x6b, 0xd8,
0xb1, 0xfb, 0xd9, 0xe4, 0xaf, 0xbf, 0x73, 0xd0, 0x40, 0x59, 0x7d, 0xd0,
0x26, 0x4f, 0x16, 0x1a, 0xc2, 0x51, 0xc4, 0x47, 0x49, 0x2c, 0x68, 0x13,
0xac, 0xa3, 0x18, 0xe7, 0x67, 0xcf, 0xb7, 0xfa, 0x3e, 0xf7, 0x8b, 0x20,
0x1e, 0x7b, 0xe2, 0x44, 0x0e, 0x47, 0x0b, 0x7c, 0x78, 0xf9, 0xf4, 0xca,
0x27, 0x6b, 0x4c, 0x2d, 0x62, 0x72, 0xd8, 0xa4, 0x10, 0x3d, 0xe7, 0x1d,
0x88, 0x4c, 0x50, 0xe5,
};
static const unsigned char TA_RSA_E0[] = {
0x01, 0x00, 0x01,
};
static const br_x509_trust_anchor TAs[] = {
{
{ (unsigned char *)TA_DN0, sizeof TA_DN0 },
BR_X509_TA_CA,
{
BR_KEYTYPE_RSA,
{ .rsa = {
(unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
(unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
} }
}
},
};
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* ifndef _CERTIFICATES_H_ */
Finally, you will need to embed your "client.crt" and "client.key" files in the ESP32 PLC program. You can use the following Python script to automate the process:
import sys
import subprocess
def process_file(lines):
processed_lines = []
for line in lines:
processed_line = "\"{}\\n\"\n".format(line[:len(line)-1])
processed_lines.append(processed_line)
return processed_lines;
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Incorrect number of args, please supply the name of the certificate file and the private key")
sys.exit(1)
input_file1 = sys.argv[1]
input_file2 = sys.argv[2]
lines_cert = ""
with open(input_file1, 'r') as file:
lines_cert = process_file(file.readlines())
private_key = subprocess.check_output(["openssl", "rsa", "-in", input_file2]).decode("utf-8").split('\n')
lines_key = process_file(private_key)
with open("ssl_client.h", 'w') as file:
file.write("#pragma once\n\nconst char my_cert[] =\n")
file.writelines(lines_cert)
file.write(";\n\nconst char my_key[] =\n")
file.writelines(lines_key[:len(lines_key)-1])
file.write(";\n")
This script has to be called with the names of the certificate and the private key respectively. For example, in this case the script should be called "python script_name.py client.crt client.key".
Example code
This example program establishes a secure connection with the broker, publishes "hello industrial world" at the "outTopic" topic, and subscribes and prints the messages received in "inTopic". It is compatible both with with WiFi and Ethernet, you just need to uncomment line 4.
#define ID_CLIENT "ESP32IndustrialShieldsClient"
#define BROKER_ADDRESS "test.mosquitto.org"
#define USE_ETHERNET_MQTT // Uncomment if you want to use Ethernet
#ifdef USE_ETHERNET_MQTT
#include <Ethernet.h>
byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED};
#else
#include <WiFi.h>
#define WIFI_SSID "ISHIELDS"
#define WIFI_PASS "boot2015SHIELDS*"
#endif
#include <SSLClient.h>
#include <PubSubClient.h>
#include "trust_anchors.h"
#include "ssl_client.h"
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();
}
// Define Client class to be used
#ifdef USE_ETHERNET_MQTT
EthernetClient netClient;
#else
WiFiClient netClient;
#endif
// Define MQTT/S parameters
SSLClientParameters mTLS = SSLClientParameters::fromPEM(my_cert, sizeof my_cert, my_key, sizeof my_key);
const char* mqttServer = BROKER_ADDRESS;
SSLClient netClientSSL(netClient, TAs, (size_t)TAs_NUM, Q0_7);
PubSubClient mqtt(mqttServer, 8884, callback, netClientSSL);
void reconnect() {
// Loop until we're reconnected
while (!mqtt.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (mqtt.connect(ID_CLIENT)) {
Serial.println("connected");
// Once connected, publish an announcement...
mqtt.publish("outTopic","hello world industrial");
// ... and resubscribe
mqtt.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(mqtt.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup(){
// Start Serial
Serial.begin(115200);
while(!Serial);
// Enable mutual TLS with SSLClient
netClientSSL.setMutualAuthParams(mTLS);
#ifdef USE_ETHERNET_MQTT
// Initialize the ethernet device
Ethernet.begin(mac);
// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet hardware not found. Sorry, can't run without hardware. :(");
while (true) {
delay(1000); // do nothing, no point running without Ethernet hardware
}
}
while (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
delay(5000);
}
#else
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
#endif
}
void loop(){
if (!mqtt.connected()) {
reconnect();
}
mqtt.loop();
}
Building an MQTT over TLS Connection: A Guide for ESP32 PLC