# MQTTS Client Configuration Guide ## Overview Complete guide for configuring MQTT clients to connect to MQTTS (MQTT over TLS) servers with single-direction TLS authentication. ## Authentication Model **Single-Direction TLS (Server Authentication)**: - Client verifies server identity (via TLS certificate) - Server authenticates client (via username/password in MQTT layer) - Client does NOT need client certificate ## What Clients Need ### Option 1: System CA (Recommended) **No files needed!** Operating system's built-in CA trust store automatically verifies the server certificate. **Advantages**: - Zero configuration - No certificate files to distribute - Automatic updates with OS **Requirements**: - Server certificate issued by trusted CA (Let's Encrypt, ZeroSSL, etc.) - System CA trust store up to date **Suitable for**: - PC applications (Windows, Mac, Linux) - Mobile apps (Android, iOS) - Server-side applications - Docker containers with CA certificates ### Option 2: Explicit CA Certificate (fullchain.pem) Explicitly specify the CA certificate chain for verification. **Advantages**: - Platform independent - No dependency on system configuration - Works on embedded devices without CA store **Requirements**: - Distribute `fullchain.pem` to clients - Update file when server certificate is renewed (if using different CA) **Suitable for**: - Embedded devices (ESP32, Arduino) - Minimal Linux systems without CA certificates - Strict security requirements - Air-gapped environments ### Option 3: Public Key Pinning (Advanced) Pin the server's public key or certificate fingerprint. **Advantages**: - Highest security - Immune to CA compromise **Disadvantages**: - Must update all clients when certificate changes - Complex to manage **Suitable for**: - High-security applications (banking, healthcare) - Known, controlled client deployments ## Language-Specific Examples ### Python (paho-mqtt) #### System CA (Recommended) ```python import paho.mqtt.client as mqtt import ssl def on_connect(client, userdata, flags, rc): if rc == 0: print("Connected successfully") client.subscribe("test/#") else: print(f"Connection failed with code {rc}") def on_message(client, userdata, msg): print(f"{msg.topic}: {msg.payload.decode()}") client = mqtt.Client() client.username_pw_set("username", "password") # Use system CA - no files needed client.tls_set( cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2 ) client.on_connect = on_connect client.on_message = on_message client.connect("mq.example.com", 8883, 60) client.loop_forever() ``` #### With fullchain.pem ```python client.tls_set( ca_certs="fullchain.pem", cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2 ) client.connect("mq.example.com", 8883, 60) ``` #### Skip Verification (Testing Only) ```python # NOT RECOMMENDED FOR PRODUCTION client.tls_set(cert_reqs=ssl.CERT_NONE) client.tls_insecure_set(True) client.connect("mq.example.com", 8883, 60) ``` ### Node.js (mqtt.js) #### System CA (Recommended) ```javascript const mqtt = require('mqtt'); const options = { host: 'mq.example.com', port: 8883, protocol: 'mqtts', username: 'username', password: 'password', rejectUnauthorized: true // Verify server certificate }; const client = mqtt.connect(options); client.on('connect', () => { console.log('Connected successfully'); client.subscribe('test/#'); }); client.on('message', (topic, message) => { console.log(`${topic}: ${message.toString()}`); }); client.on('error', (error) => { console.error('Connection error:', error); }); ``` #### With fullchain.pem ```javascript const fs = require('fs'); const options = { host: 'mq.example.com', port: 8883, protocol: 'mqtts', username: 'username', password: 'password', ca: fs.readFileSync('fullchain.pem'), rejectUnauthorized: true }; const client = mqtt.connect(options); ``` #### Skip Verification (Testing Only) ```javascript const options = { host: 'mq.example.com', port: 8883, protocol: 'mqtts', username: 'username', password: 'password', rejectUnauthorized: false // NOT RECOMMENDED }; ``` ### Java (Eclipse Paho) #### System CA (Recommended) ```java import org.eclipse.paho.client.mqttv3.*; String broker = "ssl://mq.example.com:8883"; String clientId = "JavaClient"; MqttClient client = new MqttClient(broker, clientId); MqttConnectOptions options = new MqttConnectOptions(); options.setUserName("username"); options.setPassword("password".toCharArray()); // Java uses system truststore by default client.setCallback(new MqttCallback() { public void connectionLost(Throwable cause) { System.out.println("Connection lost: " + cause.getMessage()); } public void messageArrived(String topic, MqttMessage message) { System.out.println(topic + ": " + new String(message.getPayload())); } public void deliveryComplete(IMqttDeliveryToken token) {} }); client.connect(options); client.subscribe("test/#"); ``` #### With fullchain.pem ```java import javax.net.ssl.*; import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.io.FileInputStream; // Load CA certificate CertificateFactory cf = CertificateFactory.getInstance("X.509"); FileInputStream fis = new FileInputStream("fullchain.pem"); Certificate ca = cf.generateCertificate(fis); fis.close(); // Create KeyStore with CA KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); ks.setCertificateEntry("ca", ca); // Create TrustManager TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks); // Create SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); // Set in MQTT options MqttConnectOptions options = new MqttConnectOptions(); options.setSocketFactory(sslContext.getSocketFactory()); options.setUserName("username"); options.setPassword("password".toCharArray()); client.connect(options); ``` ### C# (.NET) #### System CA (Recommended) ```csharp using MQTTnet; using MQTTnet.Client; var factory = new MqttFactory(); var mqttClient = factory.CreateMqttClient(); var options = new MqttClientOptionsBuilder() .WithTcpServer("mq.example.com", 8883) .WithCredentials("username", "password") .WithTls() // Use system CA .Build(); mqttClient.ConnectedAsync += async e => { Console.WriteLine("Connected successfully"); await mqttClient.SubscribeAsync("test/#"); }; mqttClient.ApplicationMessageReceivedAsync += e => { var payload = System.Text.Encoding.UTF8.GetString(e.ApplicationMessage.Payload); Console.WriteLine($"{e.ApplicationMessage.Topic}: {payload}"); return Task.CompletedTask; }; await mqttClient.ConnectAsync(options); ``` #### With fullchain.pem ```csharp using System.Security.Cryptography.X509Certificates; var certificate = new X509Certificate2("fullchain.pem"); var tlsOptions = new MqttClientOptionsBuilderTlsParameters { UseTls = true, Certificates = new List { certificate }, SslProtocol = System.Security.Authentication.SslProtocols.Tls12 }; var options = new MqttClientOptionsBuilder() .WithTcpServer("mq.example.com", 8883) .WithCredentials("username", "password") .WithTls(tlsOptions) .Build(); ``` ### Go (paho.mqtt.golang) #### System CA (Recommended) ```go package main import ( "fmt" "crypto/tls" mqtt "github.com/eclipse/paho.mqtt.golang" ) func main() { tlsConfig := &tls.Config{} // Uses system CA by default opts := mqtt.NewClientOptions() opts.AddBroker("ssl://mq.example.com:8883") opts.SetUsername("username") opts.SetPassword("password") opts.SetTLSConfig(tlsConfig) opts.OnConnect = func(c mqtt.Client) { fmt.Println("Connected successfully") c.Subscribe("test/#", 0, nil) } opts.DefaultPublishHandler = func(c mqtt.Client, msg mqtt.Message) { fmt.Printf("%s: %s\n", msg.Topic(), msg.Payload()) } client := mqtt.NewClient(opts) if token := client.Connect(); token.Wait() && token.Error() != nil { panic(token.Error()) } select {} // Keep running } ``` #### With fullchain.pem ```go import ( "crypto/tls" "crypto/x509" "io/ioutil" ) // Load CA certificate caCert, err := ioutil.ReadFile("fullchain.pem") if err != nil { panic(err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsConfig := &tls.Config{ RootCAs: caCertPool, } opts.SetTLSConfig(tlsConfig) ``` ### ESP32/Arduino (PubSubClient) #### With fullchain.pem (Required) ```cpp #include #include #include const char* ssid = "your_wifi_ssid"; const char* password = "your_wifi_password"; const char* mqtt_server = "mq.example.com"; const int mqtt_port = 8883; const char* mqtt_user = "username"; const char* mqtt_pass = "password"; // Copy content from fullchain.pem here const char* root_ca = \ "-----BEGIN CERTIFICATE-----\n" \ "MIIGZDCCBEygAwIBAgIRAKIoXbOGN1X6u+vS+TbyLOgwDQYJKoZIhvcNAQEMBQAw\n" \ "...\n" \ "-----END CERTIFICATE-----\n" \ "-----BEGIN CERTIFICATE-----\n" \ "...\n" \ "-----END CERTIFICATE-----\n"; WiFiClientSecure espClient; PubSubClient client(espClient); 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(); } void reconnect() { while (!client.connected()) { Serial.print("Connecting to MQTT..."); if (client.connect("ESP32Client", mqtt_user, mqtt_pass)) { Serial.println("connected"); client.subscribe("test/#"); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" retrying in 5 seconds"); delay(5000); } } } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi connected"); espClient.setCACert(root_ca); // Set CA certificate client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); } ``` #### Skip Verification (Testing Only) ```cpp void setup() { // ... espClient.setInsecure(); // NOT RECOMMENDED FOR PRODUCTION client.setServer(mqtt_server, mqtt_port); } ``` ### Command Line (Mosquitto) #### System CA (Recommended) ```bash # Publish mosquitto_pub -h mq.example.com -p 8883 \ --capath /etc/ssl/certs \ -t "test/topic" -m "Hello MQTTS" \ -u "username" -P "password" # Subscribe mosquitto_sub -h mq.example.com -p 8883 \ --capath /etc/ssl/certs \ -t "test/#" \ -u "username" -P "password" ``` #### With fullchain.pem ```bash mosquitto_pub -h mq.example.com -p 8883 \ --cafile fullchain.pem \ -t "test/topic" -m "Hello MQTTS" \ -u "username" -P "password" ``` #### Skip Verification (Testing Only) ```bash mosquitto_pub -h mq.example.com -p 8883 \ --insecure \ -t "test/topic" -m "Hello MQTTS" \ -u "username" -P "password" ``` ## Troubleshooting ### SSL Handshake Failed **Symptom**: Connection refused, SSL error, handshake failure **Causes**: 1. System doesn't trust the CA 2. Certificate expired or not yet valid 3. System time incorrect 4. Wrong domain name **Solutions**: ```bash # Check system time date # Update CA certificates (Linux) sudo update-ca-certificates # Test with openssl openssl s_client -connect mq.example.com:8883 -servername mq.example.com # Use fullchain.pem instead of system CA ``` ### Certificate Hostname Mismatch **Symptom**: Hostname verification failed **Cause**: Connecting to IP address instead of domain name **Solution**: Always use domain name (mq.example.com), NOT IP (1.2.3.4) ### Connection Timeout **Symptom**: Connection times out, no response **Causes**: 1. Port 8883 blocked by firewall 2. EMQX not listening on 8883 3. DNS not resolving **Solutions**: ```bash # Test port connectivity nc -zv mq.example.com 8883 # Check DNS dig mq.example.com +short # Test with telnet telnet mq.example.com 8883 ``` ### ESP32 Out of Memory **Symptom**: Heap overflow, crash during TLS handshake **Causes**: 1. Certificate chain too large 2. Insufficient heap space **Solutions**: ```cpp // Use only server certificate (not full chain) // Smaller but less secure espClient.setCACert(server_cert_only); // Reduce MQTT buffer size client.setBufferSize(256); // Use ECC certificate instead of RSA (smaller) ``` ## Security Best Practices ### Production Requirements ✅ **Must Have**: - Enable certificate verification (`CERT_REQUIRED`) - Use TLSv1.2 or higher - Verify server hostname/domain - Use strong username/password authentication - Keep client code updated ❌ **Never in Production**: - Skip certificate verification (`setInsecure()`, `rejectUnauthorized: false`) - Use `CERT_NONE` or disable verification - Hard-code passwords in source code - Use deprecated TLS versions (SSLv3, TLSv1.0, TLSv1.1) ### Certificate Updates - System CA: Automatic with OS updates - fullchain.pem: Update when server certificate renewed from different CA - Public Key Pinning: Must update all clients before certificate renewal ### Credential Management - Store credentials in environment variables or config files - Use secrets management systems (AWS Secrets Manager, HashiCorp Vault) - Rotate passwords regularly - Use unique credentials per device/client ## Decision Matrix | Client Platform | Recommended Method | Alternative | Testing Method | |----------------|-------------------|-------------|----------------| | PC/Mac | System CA | fullchain.pem | Skip verification | | Linux Server | System CA | fullchain.pem | Skip verification | | Android/iOS | System CA | fullchain.pem | Skip verification | | ESP32/Arduino | fullchain.pem | Server cert only | setInsecure() | | Docker Container | System CA | fullchain.pem | Skip verification | | Embedded Linux | fullchain.pem | System CA | Skip verification | ## Summary **Single-Direction TLS (Current Setup)**: - ✅ Client verifies server (via certificate) - ✅ Server verifies client (via username/password) - ❌ Server does NOT verify client certificate **Client Needs**: - ✅ System CA (easiest, no files) OR fullchain.pem (most reliable) - ❌ Does NOT need: server public key, server private key, server certificate **Key Takeaway**: The server certificate (containing the public key) is automatically sent during TLS handshake. Clients only need a way to verify it (system CA or fullchain.pem).