feat: initial OpenCode configuration with custom commands and skills
This commit is contained in:
596
skill/mqtts-developer/mqtts-client-config.md
Normal file
596
skill/mqtts-developer/mqtts-client-config.md
Normal file
@@ -0,0 +1,596 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user