Chapter 105: MQTT TLS Security Implementation

Chapter Objectives

After completing this chapter, you will be able to:

  • Understand the critical role of Transport Layer Security (TLS) in securing MQTT communications.
  • Explain the fundamental concepts of TLS, including the handshake process, X.509 certificates (CA, Server, Client), and private keys.
  • Implement MQTT over TLS (MQTTS) for server authentication (ESP32 verifying the broker).
  • Implement MQTTS with mutual authentication (broker also verifying the ESP32 using a client certificate).
  • Manage and embed TLS certificates within your ESP-IDF projects.
  • Configure an MQTT broker (like Mosquitto) to support TLS connections.
  • Identify and troubleshoot common issues related to MQTT TLS setup on ESP32.

Introduction

In the previous chapter, we discussed various methods to authenticate MQTT clients, ensuring that only authorized devices can connect to your broker. However, authentication alone doesn’t protect the content of your MQTT messages. Without encryption, sensitive data transmitted between your ESP32 devices and the MQTT broker (including credentials if using username/password authentication without TLS) can be intercepted and read by malicious actors on the network (eavesdropping). Furthermore, an attacker could potentially impersonate your legitimate MQTT broker (Man-in-the-Middle attack).

Transport Layer Security (TLS) is the standard cryptographic protocol designed to provide secure communication over a computer network. By layering MQTT on top of TLS (often referred to as MQTTS), we can achieve:

Benefit Description Importance for ESP32 MQTT
Confidentiality Ensures that MQTT messages are encrypted during transit, making them unreadable to unauthorized parties who might intercept the network traffic. Protects sensitive data (e.g., sensor readings, commands, credentials) from eavesdropping.
Integrity Guarantees that MQTT messages have not been altered or tampered with between the ESP32 client and the broker. Prevents malicious modification of data, ensuring commands and telemetry are genuine.
Authentication Allows the ESP32 client to verify the identity of the MQTT broker (server authentication) and optionally allows the broker to verify the ESP32’s identity (client authentication). Prevents Man-in-the-Middle (MITM) attacks by ensuring the ESP32 connects to the legitimate broker. Client authentication ensures only authorized devices connect.

This chapter will guide you through the theory and practical implementation of securing your ESP32’s MQTT communications using TLS with the ESP-IDF.

Theory

What is TLS?

Transport Layer Security (TLS) is the cryptographic protocol that provides end-to-end security of data sent between applications over the Internet. It is the successor to Secure Sockets Layer (SSL). When MQTT runs over TLS, it’s often denoted as MQTTS. The primary goals of TLS are:

  1. Encryption (Confidentiality): To ensure that the data transmitted cannot be understood by anyone other than the intended recipient.
  2. Authentication: To verify the identity of the communicating parties (typically the server, and optionally the client).
  3. Integrity: To ensure that the data has not been altered during transmission.

The TLS Handshake

Before any MQTT data can be exchanged securely, the client (ESP32) and the server (MQTT broker) must perform a TLS handshake. This handshake is a sequence of messages that allows them to:

  1. Agree on the TLS protocol version and cryptographic algorithms (cipher suites) to be used.
  2. Authenticate each other (server authentication is mandatory for the client; client authentication is optional for the server).
  3. Establish shared secret keys (session keys) that will be used for encrypting the MQTT traffic.

A simplified TLS handshake (with server authentication) looks like this:

  1. ClientHello: The ESP32 initiates the handshake by sending a ClientHello message to the broker. This message includes the TLS versions the client supports, a list of supported cipher suites, and a random number (client random).
  2. ServerHello: The broker responds with a ServerHello message, selecting a TLS version and cipher suite from the client’s list, and providing its own random number (server random).
  3. Server Certificate: The broker sends its X.509 certificate (and potentially an intermediate certificate chain) to the ESP32. This certificate contains the broker’s public key and identity information (e.g., its domain name).
  4. ServerKeyExchange (optional): Depending on the cipher suite, the server might send additional information needed for key exchange.
  5. ServerHelloDone: The broker indicates it has finished sending its initial handshake messages.
  6. Client Verification: The ESP32 verifies the broker’s certificate:
    • Is it signed by a Certificate Authority (CA) that the ESP32 trusts?
    • Does the name in the certificate match the broker’s address?
    • Is the certificate currently valid (not expired or revoked)?
  7. (If Client Authentication is required by the broker):
    • CertificateRequest: The broker requests the client’s certificate.
    • Client Certificate: The ESP32 sends its X.509 certificate.
    • ClientKeyExchange: The ESP32 generates a premaster secret, encrypts it with the broker’s public key (from the broker’s certificate), and sends it to the broker.
    • CertificateVerify: The ESP32 signs a hash of the handshake messages with its private key and sends the signature to prove it owns the private key corresponding to its client certificate.
  8. (If Client Authentication is NOT required):
    • ClientKeyExchange: The ESP32 generates a premaster secret, encrypts it with the broker’s public key, and sends it.
  9. ChangeCipherSpec: Both client and server send messages indicating they will switch to encrypted communication using the newly negotiated session keys.
  10. Finished: Both client and server send an encrypted Finished message (a hash of the entire handshake) to verify that the key exchange and authentication were successful.

Once the handshake is complete, all subsequent MQTT traffic is encrypted using the established session keys.

sequenceDiagram
    participant Client
    participant Broker

    Client->>Broker: 1. ClientHello <br>(TLS Version, Cipher Suites, Client Random)
    Broker-->>Client: 2. ServerHello <br>(Selected TLS Version, Cipher Suite, Server Random)
    Broker-->>Client: 3. Server Certificate <br>(Broker's X.509 Cert, Public Key)
    Broker-->>Client: 4. ServerKeyExchange (Optional)
    Broker-->>Client: 5. ServerHelloDone

    Note over Client: Verify Broker's Certificate <br>(Trusted CA? Name Match? Valid?)

    alt Client Authentication Required
        Broker-->>Client: 6a. CertificateRequest
        Client->>Broker: 7a. Client Certificate <br>(ESP32's X.509 Cert)
        Client->>Broker: 8a. ClientKeyExchange <br>(Premaster Secret encrypted with Broker's Public Key)
        Client->>Broker: 9a. CertificateVerify <br>(Signature to prove private key possession)
    else Client Authentication NOT Required
        Client->>Broker: 6b. ClientKeyExchange <br>(Premaster Secret encrypted with Broker's Public Key)
    end

    Client->>Broker: 10. ChangeCipherSpec
    Client->>Broker: 11. Finished (Encrypted Handshake Hash)

    Broker-->>Client: 12. ChangeCipherSpec
    Broker-->>Client: 13. Finished (Encrypted Handshake Hash)

    Note over Client,Broker: Secure Encrypted MQTT Communication Established


X.509 Certificates and Public Key Infrastructure (PKI)

TLS relies heavily on X.509 certificates and a Public Key Infrastructure (PKI).

  • Certificate Authority (CA):A CA is a trusted third party that issues digital certificates. The CA verifies the identity of an entity (like a server or a client) before issuing a certificate. Devices and applications maintain a list of trusted Root CA certificates.
    • Root CA: A CA whose certificate is self-signed or trusted implicitly by an operating system or application.
    • Intermediate CA: A CA whose certificate is signed by a Root CA or another Intermediate CA, forming a chain. This helps manage and compartmentalize certificate issuance.
  • Server Certificate:This certificate is used by the MQTT broker to prove its identity to connecting clients (like your ESP32). It contains:
    • The server’s domain name (Common Name – CN or Subject Alternative Name – SAN).
    • The server’s public key.
    • A validity period (start and end dates).
    • Information about the issuing CA.
    • The CA’s digital signature.The ESP32 needs to trust the CA that signed the server’s certificate to validate it. This is typically done by providing the ESP32 with the Root CA certificate (or an intermediate CA certificate) that forms the top of the chain for the server’s certificate.
  • Client Certificate:This certificate is used by the MQTT client (ESP32) to prove its identity to the MQTT broker if mutual authentication is enabled. It’s structured similarly to a server certificate but identifies the client. The broker needs to trust the CA that signed the client’s certificate.
  • Private Keys:Associated with every certificate is a private key. This key is kept secret by the owner of the certificate.
    • The server uses its private key to decrypt information sent by clients (like the premaster secret during key exchange) and to sign certain handshake messages.
    • The client (if using client certificate authentication) uses its private key to sign data (like the CertificateVerify message) to prove possession of the private key corresponding to its public certificate.Warning: The security of TLS heavily relies on keeping private keys secret. If a private key is compromised, an attacker can impersonate the legitimate owner or decrypt communications.
  • Certificate Chain of Trust:When an ESP32 receives a server certificate, it may also receive one or more intermediate CA certificates. The ESP32 validates each certificate in this chain by checking its signature against the public key of the certificate above it in the chain, until it reaches a Root CA certificate that the ESP32 already trusts (this trusted Root CA certificate must be pre-loaded onto the ESP32).
Component Description Role in ESP32 MQTT TLS
Certificate Authority (CA) A trusted entity that issues, signs, and manages digital certificates. Root CAs are implicitly trusted; Intermediate CAs form a chain of trust back to a Root CA. The ESP32 must trust the CA that signed the MQTT broker’s server certificate. For mutual auth, the broker must trust the CA that signed the ESP32’s client certificate. The ca.pem file is typically the Root CA’s certificate.
Server Certificate An X.509 certificate used by the MQTT broker to prove its identity to clients. Contains the broker’s public key, domain name/IP (CN/SAN), and is signed by a CA. The ESP32 receives and validates this certificate to ensure it’s connecting to the legitimate broker. The server.pem file on the broker.
Client Certificate An X.509 certificate used by the ESP32 client to prove its identity to the MQTT broker (in mutual authentication). Contains the client’s public key, identifier (e.g., device ID), and is signed by a CA. The ESP32 sends this to the broker if requested. The broker validates it to ensure only authorized devices connect. The client.pem file on the ESP32.
Private Key A secret cryptographic key paired with the public key in a certificate. The owner of the certificate keeps the private key confidential. The MQTT broker uses its private key (server.key) for TLS operations (e.g., decrypting premaster secret). The ESP32 uses its private key (client.key) to prove its identity during mutual authentication (e.g., signing the CertificateVerify message). Must be kept secret.
Public Key Infrastructure (PKI) A set of roles, policies, hardware, software, and procedures needed to create, manage, distribute, use, store, and revoke digital certificates and manage public-key encryption. The entire system of CAs, certificates, and keys that enables secure TLS communication.
graph TD
    subgraph ESP32 Device Trust Store
        RootCA["Trusted Root CA Certificate <br> (e.g., ca.pem on ESP32) <br> Self-Signed or Pre-trusted"]
    end

    subgraph Certificate Chain Provided by Server
        IntermediateCA["Intermediate CA Certificate <br> (Optional, can be multiple)"]
        ServerCert["Server End-Entity Certificate <br> (e.g., server.pem on Broker)"]
    end

    RootCA -- "Signs" --> IntermediateCA;
    IntermediateCA -- "Signs" --> ServerCert;

    ServerCert -- "Presented to ESP32" --> ValidationProcess["ESP32 Validates Chain"];
    ValidationProcess -- "Checks Signature Against" --> IntermediateCA;
    IntermediateCA -- "Checks Signature Against" --> RootCA;
    ValidationProcess -- "Verifies Trust Anchor" --> RootCA;
    ValidationProcess -- "If all valid & trusted" --> ConnectionEstablished["Secure Connection (MQTTS)"];

    classDef root fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    classDef intermediate fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef entity fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef process fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46

    class RootCA root;
    class IntermediateCA intermediate;
    class ServerCert entity;
    class ValidationProcess process;
    class ConnectionEstablished success;

Common TLS Ports for MQTT

Port Number Protocol Scheme Common Name Security Usage Recommendation
1883 mqtt:// MQTT Plaintext (Unencrypted) Avoid for production or sensitive data. Suitable for isolated, trusted networks or development where security is not a primary concern.
8883 mqtts:// MQTTS (MQTT over TLS) Encrypted (TLS Secured) Recommended for most applications, especially production environments or when transmitting sensitive data. Provides confidentiality, integrity, and authentication.

Server Authentication vs. Mutual Authentication

  • Server Authentication (One-Way TLS):
    • The client (ESP32) authenticates the server (MQTT broker).
    • The ESP32 needs the CA certificate that signed the broker’s server certificate.
    • This ensures the ESP32 is talking to the legitimate broker and not an impostor.
    • This is the most common form of TLS authentication for many web services and is a baseline for MQTTS.

  • Mutual Authentication (Two-Way TLS):
    • The client (ESP32) authenticates the server (MQTT broker), AND
    • The server (MQTT broker) authenticates the client (ESP32).
    • The ESP32 needs:
      1. The CA certificate that signed the broker’s server certificate.
      2. Its own client certificate.
      3. Its own private key corresponding to the client certificate.
    • The broker needs:
      1. The CA certificate that signed the ESP32’s client certificate.
      2. Its own server certificate and private key.
    • This provides a higher level of security as both parties verify each other’s identity. It’s commonly used in IoT to ensure only authorized devices connect to the broker.
Feature Server Authentication (One-Way TLS) Mutual Authentication (Two-Way TLS)
Who Authenticates Whom? Client (ESP32) authenticates the Server (MQTT Broker). Client (ESP32) authenticates Server (Broker), AND
Server (Broker) authenticates Client (ESP32).
ESP32 Needs:
  • CA certificate (ca.pem) that signed the broker’s server certificate.
  • CA certificate (ca.pem) that signed the broker’s server certificate.
  • ESP32’s own client certificate (client.pem).
  • ESP32’s own private key (client.key).
Broker Needs:
  • Its own server certificate (server.pem).
  • Its own private key (server.key).
  • (CA certificate that signed its server certificate is implicitly part of its setup).
  • Its own server certificate (server.pem) and private key (server.key).
  • CA certificate (ca.pem or similar) that signed the ESP32’s client certificate (for verifying clients).
  • Configuration to require and verify client certificates.
Primary Goal Ensures ESP32 connects to the legitimate broker, preventing MITM attacks against the broker. Ensures ESP32 connects to legitimate broker, AND ensures only authorized ESP32 devices connect to the broker.
Security Level Good (Baseline for secure MQTTS) Excellent (Stronger, as both parties are verified)
Common Use Cases General secure communication where client identity is managed by other means (e.g., username/password over TLS) or not strictly required at TLS layer. IoT deployments requiring strict device identity verification, preventing unauthorized devices from accessing the MQTT broker.

Embedding Certificates in ESP-IDF

Step Action Example Notes
1. Create Directory Create a directory (e.g., certs) inside your project’s main directory. my_project/ └── main/ ├── certs/ │ ├── ca.pem │ ├── client.pem │ └── client.key ├── CMakeLists.txt └── main.c This keeps your certificate files organized.
2. Add Certificates Copy your .pem and .key files into this directory. Ensure files are in the correct PEM format (ASCII text, starting with -----BEGIN...). File names must match those used in CMakeLists.txt.
3. Modify CMakeLists.txt Add the certificate files to the COMPONENT_EMBED_TXTFILES variable (older IDF) or EMBED_TXTFILES argument of idf_component_register (newer IDF). Using idf_component_register (recommended):
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS "."
                    EMBED_TXTFILES "certs/ca.pem"
                                   "certs/client.pem"
                                   "certs/client.key")
Alternative (older syntax):
set(COMPONENT_EMBED_TXTFILES "certs/ca.pem" "certs/client.pem" "certs/client.key")
Adjust file paths and names as needed. For server authentication only, you’d typically just embed ca.pem.
4. Access in C Code Use extern const uint8_t ..._start[] asm("_binary_filename_ext_start"); to declare pointers to the embedded file content. extern const uint8_t ca_pem_start[] asm(“_binary_ca_pem_start”); extern const uint8_t client_pem_start[] asm(“_binary_client_pem_start”); extern const uint8_t client_key_start[] asm(“_binary_client_key_start”); // In your MQTT config: // mqtt_cfg.broker.verification.certificate = (const char *)ca_pem_start; // mqtt_cfg.credentials.authentication.certificate = (const char *)client_pem_start; // mqtt_cfg.credentials.authentication.key = (const char *)client_key_start; The build system replaces dots in filenames with underscores for the symbol names (e.g., ca.pem becomes _binary_ca_pem_start). These symbols point to null-terminated strings.

Practical Examples

Let’s implement MQTTS on the ESP32. This involves configuring the ESP32 MQTT client and potentially your MQTT broker.

Prerequisites

1. Generating Certificates (Example using OpenSSL)

For testing, you can generate your own self-signed certificates. For production, you’d typically use certificates from a commercial CA or your organization’s internal PKI.

Install OpenSSL: If you don’t have it, install OpenSSL for your operating system.

A. Create a Root CA:

Bash
# Generate CA private key
openssl genrsa -out ca.key 4096
# Generate CA certificate (self-signed)
openssl req -x509 -new -nodes -key ca.key -sha256 -days 1024 -out ca.pem
# Answer the prompts (e.g., Country, Organization, Common Name for CA)

You now have ca.key (CA private key – keep safe) and ca.pem (CA public certificate).

B. Create a Server Certificate (signed by your CA):

Bash
# Generate server private key
openssl genrsa -out server.key 2048
# Create a server certificate signing request (CSR)
openssl req -new -key server.key -out server.csr
# Answer prompts. IMPORTANT: For Common Name (CN), use the domain name or IP address of your MQTT broker.
# If using IP, you might need to configure Subject Alternative Name (SAN) for modern clients.
# Example with SAN for IP 192.168.1.100:
# Create openssl_server.cnf:
# [req]
# distinguished_name = req_distinguished_name
# req_extensions = v3_req
# [req_distinguished_name]
# countryName_default = US
# stateOrProvinceName_default = CA
# localityName_default = San Francisco
# organizationName_default = MyOrg
# commonName_default = 192.168.1.100
# [v3_req]
# basicConstraints = CA:FALSE
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# subjectAltName = @alt_names
# [alt_names]
# IP.1 = 192.168.1.100
# DNS.1 = mybroker.local # Optional
# openssl req -new -key server.key -out server.csr -config openssl_server.cnf

# Sign the server CSR with your CA
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 500 -sha256 \
-extfile <(printf "subjectAltName=DNS:your.broker.domain,IP:your.broker.ip") # Or use a v3_ca extension file

You now have server.key (server private key) and server.pem (server public certificate).

C. Create a Client Certificate (signed by your CA – for mutual authentication):

Bash
# Generate client private key
openssl genrsa -out client.key 2048
# Create a client certificate signing request (CSR)
openssl req -new -key client.key -out client.csr
# Answer prompts. Common Name can be a device ID, e.g., "esp32-device-01".
# Sign the client CSR with your CA
openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem -days 500 -sha256

You now have client.key (client private key) and client.pem (client public certificate).

Tip: For the Common Name in the server certificate, it must match the address your ESP32 uses to connect to the broker. If using an IP address, ensure it’s in the CN or, preferably, as a Subject Alternative Name (SAN).

2. Embedding Certificates in ESP-IDF Firmware

You need to make the CA certificate (and client certificate/key if used) available to your ESP32 firmware. The easiest way is to embed them as text files.

  1. Create a certs directory in your project’s main directory (e.g., my_project/main/certs/).
  2. Copy the required .pem files into this directory:
    • For server authentication: ca.pem
    • For mutual authentication: ca.pem, client.pem, client.key
  3. Modify your component’s CMakeLists.txt (usually my_project/main/CMakeLists.txt) to embed these files:
    # ... other cmake content ...
    # For server authentication only
    # COMPONENT_EMBED_TXTFILES := certs/ca.pem

    # For mutual authentication COMPONENT_EMBED_TXTFILES := certs/ca.pem certs/client.pem certs/client.key

    # ... other cmake content ...
    Alternatively, using idf_component_register:
    idf_component_register(SRCS "main.c" INCLUDE_DIRS "." EMBED_TXTFILES "certs/ca.pem" "certs/client.pem" "certs/client.key") # Adjust as needed
  4. In your C code, you can access them using asm labels:
    extern const uint8_t ca_pem_start[] asm("_binary_ca_pem_start");
    // extern const uint8_t ca_pem_end[] asm("_binary_ca_pem_end"); // Not usually needed for null-terminated strings
    extern const uint8_t client_pem_start[] asm("_binary_client_pem_start");
    // extern const uint8_t client_pem_end[] asm("_binary_client_pem_end");
    extern const uint8_t client_key_start[] asm("_binary_client_key_start");
    // extern const uint8_t client_key_end[] asm("_binary_client_key_end");
    These symbols point to the beginning of the null-terminated string content of the embedded files.

Example 1: MQTT with Server Authentication (ESP32 Verifies Broker)

The ESP32 needs the CA certificate (ca.pem) that was used to sign the broker’s server certificate.

ESP32 Code (esp_mqtt_client_config_t):

C
#include "esp_log.h"
#include "mqtt_client.h"
#include <string.h> // For strerror

static const char *TAG = "MQTTS_SERVER_AUTH";

// Assume wifi_init_sta() and event handler for Wi-Fi connection are set up
// Assume mqtt_event_handler_cb is defined as in previous chapters

// Pointers to embedded certificate data
extern const uint8_t server_root_ca_pem_start[] asm("_binary_ca_pem_start"); // Renamed for clarity

static void mqtt_app_start_server_auth(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = CONFIG_BROKER_URL_MQTTS, // e.g., "mqtts://your.broker.com:8883"
        .broker.verification.certificate = (const char *)server_root_ca_pem_start,
        // .credentials.username = "user", // Optional, can be used with TLS
        // .credentials.authentication.password = "pass",
        .credentials.client_id = "esp32-serverauth-client",
    };

    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler_cb, NULL);
    esp_mqtt_client_start(client);
}

Configuration (via idf.py menuconfig):

  1. Navigate to Component config -> ESP MQTT Client.
  2. Set CONFIG_BROKER_URL_MQTTS to your MQTT broker’s URI, e.g., mqtts://your.broker.domain:8883. Note mqtts scheme.

Broker Configuration (Mosquitto Example):

Edit /etc/mosquitto/mosquitto.conf (or a file in conf.d/):

Bash
listener 8883
protocol mqtt

# Path to your CA certificate (the one ESP32 trusts)
cafile /path/to/your/certs/ca.pem

# Path to your server certificate
certfile /path/to/your/certs/server.pem

# Path to your server private key
keyfile /path/to/your/certs/server.key

# Optional: if you still want username/password auth over TLS
# allow_anonymous false
# password_file /etc/mosquitto/passwd

Restart Mosquitto.

Example 2: MQTT with Mutual Authentication (Client Certificate)

The ESP32 needs the CA certificate (ca.pem), its own client certificate (client.pem), and its client private key (client.key).

ESP32 Code (esp_mqtt_client_config_t):

C
#include "esp_log.h"
#include "mqtt_client.h"
#include <string.h> // For strerror

static const char *TAG = "MQTTS_MUTUAL_AUTH";

// Assume wifi_init_sta() and event handler for Wi-Fi connection are set up
// Assume mqtt_event_handler_cb is defined as in previous chapters

// Pointers to embedded certificate data
extern const uint8_t server_root_ca_pem_start[] asm("_binary_ca_pem_start");
extern const uint8_t client_crt_pem_start[]     asm("_binary_client_pem_start");
extern const uint8_t client_key_pem_start[]     asm("_binary_client_key_start");

static void mqtt_app_start_mutual_auth(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = CONFIG_BROKER_URL_MQTTS, // e.g., "mqtts://your.broker.com:8883"
        .broker.verification.certificate = (const char *)server_root_ca_pem_start,
        .credentials.authentication.certificate = (const char *)client_crt_pem_start,
        .credentials.authentication.key = (const char *)client_key_pem_start,
        // .credentials.authentication.key_password = CONFIG_CLIENT_KEY_PASSWORD, // If client key is encrypted
        .credentials.client_id = "esp32-mutualauth-client-01", // Can be anything, or derived from cert CN
    };
    // Note: Username/password fields in .credentials are often omitted when using client cert auth,
    // unless the broker uses them for an additional authorization layer after TLS.

    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler_cb, NULL);
    esp_mqtt_client_start(client);
}

Broker Configuration (Mosquitto Example for Mutual Authentication):

Update /etc/mosquitto/mosquitto.conf:

Bash
listener 8883
protocol mqtt

# CA used to sign server cert (ESP32 needs this to verify server)
# AND CA used to sign client certs (Broker needs this to verify clients)
# If same CA signs both, then one cafile entry is enough.
# If different CAs, you might need capath or multiple cafile entries if supported,
# or ensure the client's CA is also in the cafile used for client verification.
cafile /path/to/your/certs/ca.pem # This CA must be able to verify client certificates

# Server's certificate and private key
certfile /path/to/your/certs/server.pem
keyfile /path/to/your/certs/server.key

# Require clients to present a certificate
require_certificate true

# Optional: Use an attribute from the client cert (e.g., Common Name) as the MQTT username
# This is useful for ACLs.
use_identity_as_username true # If true, client CN becomes the username

# If use_identity_as_username is false, you might still need ACLs based on client cert attributes,
# or fall back to username/password if allow_anonymous is false and no password_file is set.
# For strict client cert auth without username/password:
allow_anonymous false # Deny if not authenticated by cert
# No password_file needed if use_identity_as_username is true and ACLs are based on that.

Restart Mosquitto.

Build, Flash, Run, Observe

  1. Configure: Set Wi-Fi credentials. Set CONFIG_BROKER_URL_MQTTS.
  2. Embed Certs: Ensure CMakeLists.txt is updated and cert files are in the correct path.
  3. Build: idf.py build
  4. Flash: idf.py -p /dev/YOUR_SERIAL_PORT flash
  5. Monitor:idf.py -p /dev/YOUR_SERIAL_PORT monitor
    • Look for logs from esp-tls and MQTT_CLIENT regarding the handshake.
    • Successful connection: MQTT_EVENT_CONNECTED.
    • Failures: MQTT_EVENT_ERROR, often with esp_tls_last_esp_err or esp_tls_stack_err providing mbedTLS error codes. (e.g., X509 - Certificate verification failed).
  6. Broker Logs: Check your MQTT broker’s logs for TLS handshake errors and connection attempts. For Mosquitto, run with -v for verbose logging.
  7. External Tool: Use openssl s_client to test TLS connectivity to your broker from your PC:
    • Server Auth Test: openssl s_client -connect your.broker.com:8883 -CAfile /path/to/ca.pem
    • Mutual Auth Test: openssl s_client -connect your.broker.com:8883 -CAfile /path/to/ca.pem -cert /path/to/client.pem -key /path/to/client.key

Variant Notes

Implementing MQTT over TLS is generally consistent across ESP32 variants, but performance and security aspects can differ:

  • Hardware Cryptographic Acceleration:
    • Most ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2) include hardware accelerators for cryptographic operations like AES, SHA, RSA, and ECC.
    • esp-tls (which uses mbedTLS underneath) leverages these accelerators by default. This significantly speeds up the TLS handshake (especially RSA/ECC operations) and reduces CPU load compared to purely software-based cryptography.
    • This makes TLS feasible even on resource-constrained devices.
  • RAM Usage:
    • The TLS stack (mbedTLS) requires a considerable amount of RAM for handshake buffers, session state, and certificate parsing (especially large CA bundles or long certificate chains). This can be in the range of 30-60KB or more during the handshake.
    • While all ESP32 variants can generally handle this for typical IoT use cases, it’s a key factor to monitor, especially if your application has other significant RAM requirements.
    • CONFIG_MBEDTLS_DYNAMIC_BUFFER in menuconfig can allow mbedTLS to allocate some buffers dynamically from the heap, which can be more flexible but requires careful heap management.
    • Variants with PSRAM (ESP32, ESP32-S3) can offload some heap to PSRAM, potentially easing RAM pressure, but access to PSRAM is slower.
  • Secure Storage of Client Private Key:
    • Flash Encryption: Enabling ESP-IDF’s flash encryption feature encrypts the entire flash content, including embedded client private keys, protecting them if the physical flash chip is accessed. This is a critical security measure.
    • Digital Signature (DS) Peripheral:
      • Available on ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2.
      • The DS peripheral can securely store a private key (derived from eFuses and protected by hardware) and perform cryptographic signing operations without software ever accessing the raw private key.
      • esp-tls can be configured to use the DS peripheral for client private key operations in a TLS handshake (CONFIG_ESP_TLS_USE_DS_PERIPHERAL). This provides a very high level of private key protection. See ESP-IDF documentation for esp_ds component.
    • Secure Element: For utmost security, an external Secure Element chip (like ATECC608A) can be used to store the client private key and perform crypto operations. Interfacing this with esp-tls for client authentication requires more custom integration, potentially by providing a custom private key context to mbedTLS.
  • Power Consumption:
    • Cryptographic operations, especially during the TLS handshake, are computationally intensive and can temporarily increase power consumption. For battery-powered devices, this should be factored into the power budget, especially if connections are frequent or re-handshakes occur.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Incorrect/Missing Root CA on ESP32 ESP32 logs show X509 - Certificate verification failed, esp_tls_handshake errors, or mbedTLS errors like MBEDTLS_ERR_X509_CERT_VERIFY_FAILED. MQTT client error MQTT_EVENT_ERROR with ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME or ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST.
  • Ensure the correct Root CA certificate (ca.pem) that signed the broker’s server certificate is embedded in the ESP32 firmware.
  • Verify the broker.verification.certificate field in esp_mqtt_client_config_t points to the correct embedded CA cert string (e.g., (const char *)server_root_ca_pem_start).
  • Check that the CA certificate file is in valid PEM format.
Server Cert CN/SAN Mismatch TLS handshake fails. ESP32 logs might show certificate validation errors related to hostname mismatch (e.g., MBEDTLS_ERR_X509_CERT_VERIFY_FAILED with reason “CN/SAN mismatch”).
  • The Common Name (CN) or Subject Alternative Name (SAN) in the broker’s server certificate must match the broker address URI used in esp_mqtt_client_config_t.broker.address.uri.
  • If using an IP address, ensure it’s listed as a SAN (IP Address type) in the server certificate. Using domain names is generally preferred.
  • Regenerate server certificate with correct CN/SAN if necessary.
Expired or Not Yet Valid Certificate TLS handshake fails. Error messages may indicate certificate validity period issues (MBEDTLS_ERR_X509_CERT_VERIFY_FAILED with reason “certificate expired” or “not yet valid”).
  • Check the validity period of all certificates in the chain (CA, server, client).
  • Ensure the ESP32’s system time is accurate. Implement SNTP time synchronization (see Chapter 98). An incorrect device time can make valid certificates appear invalid.
Client Auth Failure (Mutual Auth) Broker rejects connection. Broker logs may show “client certificate verification failed,” “unknown CA,” or “no certificate presented.” ESP32 may receive TLS alert from broker.
  • Broker: Ensure its cafile (for client verification) contains the CA that signed the ESP32’s client.pem.
  • Broker: Verify require_certificate true (or equivalent) is set.
  • ESP32: Ensure client.pem and client.key are correctly embedded and pointed to in credentials.authentication.certificate and .key.
  • ESP32: Verify the client.key actually corresponds to the client.pem.
Incorrect Certificate/Key Format or Embedding Handshake failures, mbedTLS parsing errors (e.g., MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT, MBEDTLS_ERR_X509_INVALID_FORMAT).
  • Verify all .pem and .key files are valid PEM format (ASCII, begin/end markers).
  • Ensure COMPONENT_EMBED_TXTFILES in CMakeLists.txt correctly lists the files.
  • Double-check asm labels in C code (e.g., _binary_ca_pem_start). Embedded files are null-terminated.
TLS Handshake Timeouts / mbedTLS Errors Connection times out. ESP32 logs show mbedTLS errors (negative hex values like -0x7200). Symptoms like MQTT_EVENT_ERROR.
  • Check Free Heap: Use esp_get_free_heap_size() before and during connection attempts. mbedTLS needs significant RAM (30-60KB+). Insufficient RAM is a common cause.
  • Increase stack size for MQTT task if stack overflow is suspected.
  • Network: Verify Wi-Fi stability, signal strength, and IP connectivity to the broker.
  • Firewall: Ensure port 8883 (or custom TLS port) is open on the broker machine and any intermediate firewalls.
  • Look up mbedTLS error codes in mbedTLS documentation or mbedtls/error.h.
Broker TLS Configuration Errors Broker fails to start, logs TLS errors, or connections are refused. openssl s_client tests fail.
  • Verify paths to cafile, certfile, keyfile in broker config (e.g., mosquitto.conf).
  • Check file permissions for these certificate files (broker process must be able to read them).
  • Ensure broker is configured to listen on the correct TLS port (e.g., 8883).
  • Check broker logs for detailed error messages during startup or connection attempts.
Client Private Key Issues (DS Peripheral) If using Digital Signature (DS) peripheral: Handshake fails, errors related to private key operations.
  • Ensure DS peripheral is correctly initialized and the private key is properly provisioned.
  • Verify CONFIG_ESP_TLS_USE_DS_PERIPHERAL is enabled in menuconfig.
  • The client certificate (client.pem) provided to esp-tls must correspond to the private key secured by the DS peripheral.
  • Consult ESP-IDF documentation for esp_ds component.

Exercises

  1. Setup Mosquitto with TLS (Server Authentication):
    • Generate your own CA certificate and key (ca.pem, ca.key).
    • Generate a server certificate and key (server.pem, server.key), signed by your CA. Ensure the Common Name or SAN matches your broker’s IP/hostname.
    • Configure Mosquitto to use these certificates for a listener on port 8883 (server authentication only).
    • Modify the ESP32 “Example 1” code: embed ca.pem, configure the esp_mqtt_client_config_t to use it and connect to your Mosquitto instance over mqtts://.
    • Verify successful connection and try publishing/subscribing.
  2. Extend to Mutual TLS Authentication:
    • Using the CA from Exercise 1, generate a client certificate and key (client.pem, client.key).
    • Modify your Mosquitto configuration from Exercise 1 to also require client certificates (require_certificate true) and to use your ca.pem to verify them. Optionally, set use_identity_as_username true.
    • Modify the ESP32 “Example 2” code: embed ca.pem, client.pem, and client.key. Configure the esp_mqtt_client_config_t for mutual authentication.
    • Verify successful connection. If use_identity_as_username true was set, check broker logs to see if the client’s certificate CN is used as the username.
  3. Troubleshooting: Incorrect CA Certificate:
    • Using the setup from Exercise 1 (Server Authentication), replace the ca.pem embedded in the ESP32 firmware with a different, unrelated CA certificate (or just an empty file).
    • Attempt to connect. Observe the TLS handshake failure in the ESP32 serial monitor logs. Note the error codes reported by esp-tls or mbedTLS.
    • Explain why the connection failed based on the TLS theory.
  4. Time Synchronization Impact (Conceptual & Research):
    • Research how SNTP (Simple Network Time Protocol) is implemented on ESP32 using ESP-IDF (refer to Chapter 98 if available, or ESP-IDF documentation).
    • Explain why having accurate system time is crucial for TLS certificate validation.
    • Modify one of your working TLS examples to include basic SNTP time synchronization before mqtt_app_start_...() is called. (You don’t need to test certificate expiry directly, but implement time sync).

Summary

  • TLS is essential for MQTT security, providing confidentiality, integrity, and authentication.
  • The TLS handshake establishes a secure session using X.509 certificates and cryptographic algorithms.
  • Server Authentication: ESP32 verifies the broker’s identity using the broker’s server certificate and a trusted CA certificate.
  • Mutual Authentication: The broker also verifies the ESP32‘s identity using the ESP32’s client certificate and private key, and a CA certificate trusted by the broker.
  • Certificates (CA, client cert, client key) must be embedded into the ESP32 firmware, typically using COMPONENT_EMBED_TXTFILES.
  • The esp-mqtt client is configured for TLS via the broker.verification.certificate, credentials.authentication.certificate, and credentials.authentication.key fields in esp_mqtt_client_config_t.
  • ESP32 variants offer hardware crypto acceleration, aiding TLS performance, and features like flash encryption and the DS peripheral can enhance private key security.
  • Accurate system time (via SNTP) is important for certificate validation.
  • Thorough error checking and consulting broker/client logs are key to troubleshooting TLS issues.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top