2. X.509证书签发与设备端TLS双向认证
为什么用 X.509 双向认证替代 PSK
PSK 方案中,每台设备与 Broker 共享同一把对称密钥,密钥一旦泄露,攻击者即可冒充设备;且密钥分发、轮换和吊销都依赖人工。X.509 双向认证(mTLS)把信任锚定在 CA 根证书上:设备持有由内部 CA 签发的证书与私钥,Broker 持有 CA 根证书用于验证设备证书链,设备同时验证 Broker 证书。这样每台设备拥有独立密钥对,单台设备泄露不影响其他设备,且可通过 CRL/OCSP 或短有效期实现吊销。
内部 CA 与设备证书签发
使用 OpenSSL 搭建两级结构:根 CA(离线保存)签发中间 CA,中间 CA 签发设备证书。设备证书的 CN 使用设备唯一 ID,SAN 中写入设备 ID,便于 Broker 在认证后提取身份。以下命令在 Linux 上执行,目录结构为 ca/(根)、ca/intermediate/(中间)、certs/(设备证书)。
# 1. 生成根 CA 私钥与自签名证书(有效期 10 年)
openssl genrsa -out ca/rootCA.key 4096
openssl req -x509 -new -nodes -key ca/rootCA.key -sha256 -days 3650 \
-subj "/C=CN/O=IoT Internal/CN=IoT Root CA" \
-out ca/rootCA.crt
# 2. 生成中间 CA 私钥与 CSR
openssl genrsa -out ca/intermediate/intermediate.key 4096
openssl req -new -key ca/intermediate/intermediate.key \
-subj "/C=CN/O=IoT Internal/CN=IoT Intermediate CA" \
-out ca/intermediate/intermediate.csr
# 3. 用根 CA 签发中间 CA 证书(有效期 5 年,pathlen:0 限制不能再签下级 CA)
openssl x509 -req -in ca/intermediate/intermediate.csr \
-CA ca/rootCA.crt -CAkey ca/rootCA.key -CAcreateserial \
-days 1825 -sha256 \
-extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign\n") \
-out ca/intermediate/intermediate.crt
# 4. 为设备 device-001 生成私钥与 CSR
openssl genrsa -out certs/device-001.key 2048
openssl req -new -key certs/device-001.key \
-subj "/C=CN/O=IoT Devices/CN=device-001" \
-out certs/device-001.csr
# 5. 用中间 CA 签发设备证书(有效期 365 天,含 SAN 与 clientAuth 用途)
openssl x509 -req -in certs/device-001.csr \
-CA ca/intermediate/intermediate.crt -CAkey ca/intermediate/intermediate.key \
-CAcreateserial -days 365 -sha256 \
-extfile <(printf "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth\nsubjectAltName=DNS:device-001\n") \
-out certs/device-001.crt
# 6. 生成设备证书链(设备证书 + 中间 CA),供 Broker 验证
cat certs/device-001.crt ca/intermediate/intermediate.crt > certs/device-001-chain.crt
device-001.crt(或链文件)、device-001.key 和 rootCA.crt。私钥必须保存在设备安全存储中,权限设为 600,禁止通过网络传输。证书链与信任关系
下图展示根 CA、中间 CA、设备证书与 Broker 之间的签发与验证关系。Broker 用根 CA 验证设备证书链,设备用根 CA 验证 Broker 证书,形成双向信任。
Broker 端配置 mTLS
以 Mosquitto 2.x 为例,在 mosquitto.conf 中启用 TLS 并要求客户端证书。关键参数:require_certificate true 强制双向认证,use_identity_as_username true 将证书 CN 作为 MQTT 用户名,便于 ACL 按设备 ID 授权。
listener 8883
cafile /etc/mosquitto/ca/rootCA.crt
certfile /etc/mosquitto/certs/broker.crt
keyfile /etc/mosquitto/certs/broker.key
require_certificate true
use_identity_as_username true
tls_version tlsv1.2
cafile 只需根 CA 证书即可验证设备证书链,无需中间 CA 证书——但前提是设备在握手时提交了完整链(设备证书 + 中间 CA)。若设备只提交叶子证书,Broker 将因缺少中间证书而验证失败。设备端 Python 客户端实现
使用 paho-mqtt 连接,通过 tls_set 加载 CA、设备证书和私钥。以下代码可直接运行,连接成功后订阅主题并发布一条消息。
import ssl
import paho.mqtt.client as mqtt
BROKER = "broker.iot.local"
PORT = 8883
CA_CERT = "/etc/iot/certs/rootCA.crt"
DEVICE_CERT = "/etc/iot/certs/device-001-chain.crt"
DEVICE_KEY = "/etc/iot/certs/device-001.key"
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("mTLS 认证成功,已连接")
client.subscribe("devices/device-001/cmd")
else:
print(f"连接被拒绝,rc={rc}")
def on_message(client, userdata, msg):
print(f"收到指令: {msg.topic} -> {msg.payload.decode()}")
client = mqtt.Client(client_id="device-001")
client.tls_set(
ca_certs=CA_CERT,
certfile=DEVICE_CERT,
keyfile=DEVICE_KEY,
tls_version=ssl.PROTOCOL_TLSv1_2,
)
client.tls_insecure_set(False) # 严格校验 Broker 主机名
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, keepalive=60)
client.publish("devices/device-001/status", "online", qos=1)
client.loop_forever()
验证服务端拒绝行为
需要验证两类拒绝场景:无证书连接、证书过期连接。使用 openssl s_client 可快速模拟。
| 场景 | 操作 | 预期结果 |
|---|---|---|
| 无客户端证书 | openssl s_client -connect broker.iot.local:8883 -CAfile rootCA.crt |
握手失败,Broker 返回 certificate required,连接被拒绝 |
| 证书过期 | 将设备证书有效期改为 -1 天重新签发,再用该证书连接 | Broker 验证链时返回 certificate has expired,连接被拒绝 |
| 证书链不完整 | 仅提交 device-001.crt(不含中间 CA) |
Broker 无法构建到根 CA 的链,返回 unable to get local issuer certificate |
| 正常连接 | 提交完整链 + 有效私钥 | 握手成功,MQTT CONNACK 返回 rc=0,可正常收发消息 |
New client connected from ... as device-001 (p2, c1, k60, u'device-001'),其中 u'device-001' 来自证书 CN,证明 use_identity_as_username 生效。若日志显示 OpenSSL Error: ... certificate verify failed,则需检查设备是否提交了完整证书链。证书轮换与吊销
设备证书有效期建议不超过 365 天,到期前 30 天通过 MQTT 下发新证书。吊销采用 CRL:在 mosquitto.conf 中增加 crlfile /etc/mosquitto/ca/crl.pem,Broker 每次握手时检查 CRL。生成 CRL 的命令如下:
# 在中间 CA 目录下吊销 device-001 并生成 CRL
openssl ca -config openssl.cnf -revoke certs/device-001.crt
openssl ca -config openssl.cnf -gencrl -out ca/intermediate/crl.pem
systemctl reload mosquitto)。若设备数量大,建议改用 OCSP 或短有效期证书 + 自动轮换,避免 CRL 文件过大导致握手延迟。