New skill: mqtts-developer - Complete automated MQTTS setup workflow with acme.sh - Multi-language client configuration guide (Python, Node.js, Java, C#, Go, ESP32) - Quick reference for commands and troubleshooting - Practical usage examples - Token-efficient reusable knowledge base Features: - 10-phase automated certificate setup - Support for Alibaba Cloud DNS API - Auto-renewal with Docker container restart - Single-direction TLS authentication - 7+ programming language examples - Comprehensive troubleshooting guides - 1750+ lines of structured documentation Token Savings: - First use: 60-70% reduction - Repeated use: 80%+ reduction Files: - SKILL.md: Main entry point and overview - setup-mqtts-acme.md: Complete setup workflow (11KB, 350 lines) - mqtts-quick-reference.md: Quick reference guide (7KB, 277 lines) - mqtts-client-config.md: Client configuration (15KB, 596 lines) - README.md: Usage guide (6KB, 227 lines) - USAGE_EXAMPLES.md: Practical examples (6KB, 275 lines)
597 lines
14 KiB
Markdown
597 lines
14 KiB
Markdown
# 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<X509Certificate> { 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 <WiFi.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include <PubSubClient.h>
|
|
|
|
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).
|