2. X.509证书签发与设备端TLS双向认证

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 证书,形成双向信任。

X.509 证书链与 mTLS 双向验证关系 根 CA (rootCA.crt) 自签名 · 有效期 10 年 · 离线保存 中间 CA (intermediate.crt) pathlen:0 · 有效期 5 年 设备证书 device-001.crt CN=device-001 · SAN=DNS:device-001 EKU=clientAuth · 有效期 365 天 Broker 证书 broker.crt CN=broker.iot.local EKU=serverAuth 签发 签发 签发 mTLS 握手:设备提交证书链 → Broker 用 rootCA 验证;Broker 提交证书 → 设备用 rootCA 验证 设备证书链验证 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
Broker 的 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,可正常收发消息
验证要点:Broker 日志中应出现 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
生产环境中,CRL 更新后需重载 Broker 配置(systemctl reload mosquitto)。若设备数量大,建议改用 OCSP 或短有效期证书 + 自动轮换,避免 CRL 文件过大导致握手延迟。