4、CA搭建与自签名证书:搭建私有CA、生成CA根证书、配置CA策略、管理证书吊销列表(CRL)

说到MQTT通信的安全,证书是绕不开的一环。很多团队在开发阶段或者内部系统里,并不想花钱买商业CA的证书。这时候,自己搭一个私有CA,自己签证书,就成了最实在的选择。

我个人习惯把私有CA叫做“内部证书作坊”。它不对外公开,只给自己人用。你想想看,几十台物联网设备,每台都要配证书,如果全买商业的,成本扛不住。自己签,省钱又灵活。

4.1 搭建私有CA的核心思路

私有CA说白了就是三件事:生成根证书、签发中间证书或设备证书、管理吊销。我见过不少团队,根证书和服务器证书混在一起用,这是个大坑。根证书一旦泄露,整个信任链就崩了。

搭建之前,先理清角色:

  • 根CA:最顶层的信任锚,离线保存,平时不碰它。
  • 中间CA(可选):用来签发设备证书,万一泄露,可以单独吊销,不影响根。
  • 终端实体证书:MQTT Broker、设备客户端用的就是它。

核心原则:根CA必须离线。我在项目中见过有人把根CA放在生产服务器上,结果被扫到私钥文件,整个PKI重建了一次。别犯同样的错。

私有CA信任链结构 根CA(离线) 自签名 · 最高信任锚 中间CA 由根CA签发 · 日常使用 MQTT Broker 服务器证书 设备客户端 设备证书 其他服务 客户端证书 根CA离线存储,中间CA在线签发,终端证书按需生成

4.2 生成CA根证书

我习惯用OpenSSL来干这个活。它虽然命令行参数多,但胜在稳定、可控。生成根证书分三步:

  1. 生成根CA私钥——建议用4096位RSA,别省这点性能。
  2. 生成根证书请求——其实根证书是自签名的,不需要请求,但OpenSSL流程上会走一遍。
  3. 自签名根证书——设置10年有效期,够用。
# 1. 生成根CA私钥(4096位,带加密)
openssl genrsa -aes256 -out ca.key 4096

# 2. 生成自签名根证书(有效期3650天)
openssl req -x509 -new -nodes -key ca.key \
  -sha256 -days 3650 \
  -out ca.crt \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyIoT/CN=MyRootCA"

小技巧:我习惯把根证书的有效期设到10年,中间证书5年,设备证书1-2年。这样根证书不用频繁更换,设备证书可以滚动更新。

这里有个细节:-nodes 参数表示私钥不加密。生成根证书时我建议去掉 -nodes,加上 -aes256 加密私钥。每次签证书都要输密码,虽然麻烦,但安全。

4.3 配置CA策略

CA策略说白了就是“什么证书能签,什么不能签”。OpenSSL通过一个配置文件来控制。我见过最惨的案例,就是没配策略,结果签出来的证书CN乱填,导致TLS握手验证失败。

下面是我常用的策略模板:

# openssl-ca.cnf
[ ca ]
default_ca = CA_default

[ CA_default ]
database    = index.txt          # 已签发证书数据库
serial      = serial             # 序列号文件
new_certs_dir = ./newcerts       # 新证书存放目录
default_md  = sha256
policy      = policy_loose       # 宽松策略,适合内部

[ policy_loose ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied   # CN必须提供
emailAddress            = optional

[ req ]
distinguished_name = req_distinguished_name
prompt = no

[ req_distinguished_name ]
C  = CN
ST = Beijing
L  = Beijing
O  = MyIoT
CN = MyIntermediateCA

为什么用 policy_loose?因为物联网设备种类多,有些设备证书的字段填不全。如果策略太严,签证书时老报错。内部环境,宽松点反而省事。

注意:生产环境建议用 policy_match 严格模式。我曾经在客户现场吃过亏——宽松策略签出来的证书,被安全审计判定为“证书身份不可追溯”。

4.4 签发中间CA证书

根证书生成后,我建议再签一个中间CA。中间CA的私钥可以放在在线服务器上,根CA的私钥继续离线。

# 1. 生成中间CA私钥
openssl genrsa -out intermediate.key 2048

# 2. 生成中间CA证书请求
openssl req -new -key intermediate.key \
  -out intermediate.csr \
  -subj "/C=CN/ST=Beijing/O=MyIoT/CN=MyIntermediateCA"

# 3. 用根CA签发中间CA证书
openssl ca -config openssl-ca.cnf \
  -extensions v3_intermediate_ca \
  -days 1825 -notext -md sha256 \
  -in intermediate.csr \
  -out intermediate.crt

这里要注意 v3_intermediate_ca 扩展。不加这个扩展,中间CA签出来的证书浏览器不认。扩展内容如下:

[ v3_intermediate_ca ]
basicConstraints       = critical, CA:TRUE, pathlen:0
keyUsage               = critical, digitalSignature, keyCertSign, cRLSign
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer

pathlen:0 表示中间CA下面不能再有CA了。这样即使中间CA被攻破,攻击者也签不出新的CA证书,风险可控。

4.5 管理证书吊销列表(CRL)

证书不是签出来就万事大吉了。设备报废、私钥泄露、员工离职,这些场景都需要吊销证书。CRL就是干这个的。

我刚开始做物联网时,觉得CRL可有可无。直到有一次,一批设备流出到非受控环境,私钥可能泄露。当时没有CRL,只能把所有证书重新换一遍,折腾了整整两天。从那以后,CRL成了我的标配。

4.5.1 初始化CRL环境

OpenSSL管理CRL需要几个文件:

# 创建必要的文件
touch index.txt          # 证书数据库
echo 01 > serial         # 序列号起始值
mkdir -p newcerts crl    # 存放新证书和CRL的目录

4.5.2 生成初始CRL

# 生成空CRL(由根CA签发)
openssl ca -config openssl-ca.cnf \
  -gencrl -out crl/ca.crl

# 查看CRL内容
openssl crl -in crl/ca.crl -text -noout

4.5.3 吊销证书

吊销证书时,需要知道证书的序列号或CN:

# 吊销证书(通过序列号)
openssl ca -config openssl-ca.cnf \
  -revoke newcerts/01.pem \
  -crl_reason keyCompromise

# 重新生成CRL(吊销后必须更新CRL)
openssl ca -config openssl-ca.cnf \
  -gencrl -out crl/ca.crl

关键点:吊销证书后,必须重新生成CRL并分发到MQTT Broker。Broker在TLS握手时,会检查客户端证书是否在CRL中。如果在,直接拒绝连接。

4.5.4 CRL分发与更新

CRL不能只生成一次就不管了。我建议:

  • 定期更新:每周生成一次CRL,即使没有新吊销的证书。
  • 有效期设置:CRL有效期建议30天,过期后Broker会拒绝所有证书。
  • 分发方式:通过HTTP、FTP或MQTT本身推送CRL到各节点。
参数 建议值 说明
CRL有效期 30天 过期后Broker拒绝所有连接
更新频率 每周一次 即使无吊销,也要刷新
CRL大小 建议<1MB 物联网设备内存有限
签名算法 SHA256 SHA1已不安全

经验之谈:对于资源受限的物联网设备,CRL可能太大。我一般配合OCSP(在线证书状态协议)使用。设备只查单个证书状态,不用下载整个CRL。不过OCSP需要额外搭建响应服务器,看项目预算决定。

4.6 避坑指南

做了这么多年PKI,踩过的坑不少。挑几个典型的说说:

  • 私钥权限:根CA私钥建议用HSM或加密U盘存储。我曾经见过有人把ca.key放在Git仓库里……嗯,那场面。
  • 序列号管理:序列号文件serial必须手动维护。多线程签发时可能冲突,建议用脚本加锁。
  • 时间同步:证书有效期依赖系统时间。设备时间不准,证书验证会失败。NTP一定要配好。
  • CRL缓存:Broker会缓存CRL。更新CRL后,可能需要重启Broker或清缓存才能生效。

严重警告:不要在MQTT Broker上直接存储根CA私钥。Broker只需要根证书(ca.crt)来验证客户端,不需要私钥。私钥泄露等于整个信任链崩塌。

好了,私有CA的搭建就聊到这儿。这套流程我在多个物联网项目里验证过,稳定可靠。你按这个步骤走,内部证书管理基本不会出大问题。