4. 使用OpenSSL创建私有CA
在MQTT mTLS体系中,设备、网关、Broker之间之所以能互相信任,根源并不在于某一张证书本身,而在于它们共同信任同一个“签发者”——私有CA。私有CA是整条信任链的起点:它用自己的私钥为服务器证书和客户端证书签名,通信双方在握手时通过验证证书上的签名是否由可信CA签发,来判断对方身份是否合法。因此,搭建私有CA是mTLS接入工程的第一步,也是最不能出错的一步。本节围绕三件事展开:生成CA私钥、生成自签名根证书、以及通过openssl.cnf和扩展项控制这张根证书的“权力边界”。
一、CA私钥:信任链的根密钥
CA私钥是整个PKI体系中最敏感的材料。它一旦泄露,攻击者就可以伪造任意服务器或客户端证书,mTLS的信任基础将彻底崩塌。因此CA私钥通常要求长度不低于4096位,并且必须加密存储。生成命令如下:
# 生成4096位RSA私钥,并用AES-256加密保护
openssl genrsa -aes256 -out ca.key 4096
# 查看私钥结构(不显示私钥内容)
openssl rsa -in ca.key -noout -text | head -n 5
执行第一条命令时会提示输入PEM pass phrase,这个口令用于加密私钥文件。后续每次使用该私钥签名证书时都需要输入。生产环境中,CA私钥应存放在离线或受严格访问控制的机器上,权限建议设置为600,属主为专用账户。如果为了实验方便生成不加密的私钥,可以去掉-aes256参数,但绝不应把这种私钥用于真实业务。
二、自签名根证书:用私钥给自己签发
根证书是自签名的,也就是“签发者”和“主体”是同一个人。它不需要别人签名,因为它就是信任的源头。生成根证书时,我们通常直接基于私钥创建证书请求并自签名,或者一步完成。下面给出一个典型命令:
openssl req -new -x509 -days 3650 -sha256 \
-key ca.key -out ca.crt \
-subj "/C=CN/ST=Zhejiang/L=Hangzhou/O=IoT Lab/OU=MQTT PKI/CN=MQTT Private Root CA"
其中-x509表示直接输出自签名证书,-days 3650表示有效期约10年。根证书有效期通常设置得较长,以减少更换根证书带来的全量设备重签成本。但也要注意,根证书一旦过期,所有由它签发的证书都会失效,因此必须建立到期提醒机制。-subj中的CN字段是根证书的可读名称,建议使用能明确标识用途和组织的名称,例如“MQTT Private Root CA”,避免与公共CA混淆。
生成后可以用以下命令检查证书内容:
openssl x509 -in ca.crt -noout -text
输出中会包含Version、Serial Number、Signature Algorithm、Issuer、Validity、Subject、Public Key Algorithm以及X509v3 extensions。其中Issuer和Subject应当完全一致,这是自签名证书的典型特征。
三、openssl.cnf:让CA具备签发能力
仅仅有一张自签名根证书还不够。当我们要用这个CA去签发服务器或客户端证书时,OpenSSL需要知道这个CA的默认策略,例如证书默认有效期、默认扩展项、签发时使用哪些字段。这些策略由openssl.cnf配置文件定义。一个精简但可用的CA配置片段如下:
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = ./demoCA
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
private_key = $dir/private/ca.key
certificate = $dir/cacert.pem
default_md = sha256
default_days = 825
default_crl_days = 30
policy = policy_loose
copy_extensions = copy
[ policy_loose ]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
[ v3_ca ]
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
这里有几个关键点。第一,database和serial是CA签发证书时的“账本”,每签发一张证书,serial递增,index.txt记录已签发证书的状态。第二,policy_loose定义了签发时对主体字段的要求,其中commonName = supplied表示CN必须提供,其余字段可选,适合内部IoT场景。第三,copy_extensions = copy允许在签发时把证书请求中的扩展项复制到最终证书中,这对后续为服务器证书添加SAN非常关键。
四、basicConstraints与keyUsage:根证书的权力边界
扩展项是X.509 v3证书的核心安全机制。对于CA证书,最重要的两个扩展项是basicConstraints和keyUsage。
basicConstraints用于声明这张证书是不是CA证书,以及它下面还能不能再有子CA。CA:TRUE表示该证书可以用于签发其他证书;CA:FALSE表示它是终端实体证书,不能用于签发。对于根证书,必须设置为critical, CA:TRUE。critical表示这个扩展项必须被理解,如果验证方不支持该扩展,就必须拒绝该证书。还可以加上pathlen:0,表示该CA下面只能签发终端证书,不能再签发中间CA,这对简化信任链、降低风险很有帮助。
keyUsage用于限定证书中公钥的用途。对于CA证书,必须包含keyCertSign,表示该公钥可以用于验证证书签名;还应包含cRLSign,表示可以用于签发证书吊销列表。如果缺少keyCertSign,那么用这张CA签发的证书在验证时会直接失败。对于服务器证书,keyUsage通常包含digitalSignature, keyEncipherment;对于客户端证书,通常包含digitalSignature。这些扩展项共同构成了证书的“权力边界”,防止一张证书被挪作他用。
下面这张图展示了私有CA从密钥生成到根证书扩展项约束,再到后续签发服务器/客户端证书的信任链关系。图中重点标出了根证书的basicConstraints和keyUsage如何决定它能否继续签发,以及终端证书为何不能再次签发。
五、验证根证书是否具备CA能力
生成根证书后,不要急着去签发服务器证书,先做一次自检。可以用以下命令确认扩展项是否正确写入:
openssl x509 -in ca.crt -noout -text | grep -A2 "Basic Constraints"
openssl x509 -in ca.crt -noout -text | grep -A2 "Key Usage"
预期输出中应当看到CA:TRUE以及Certificate Sign, CRL Sign。如果看到的是CA:FALSE,说明生成根证书时没有加载v3_ca扩展,这张证书不能作为CA使用,必须重新生成。另一个常见问题是keyUsage缺少keyCertSign,此时用该CA签发证书会报“key usage does not include certificate signing”错误。排查时优先检查openssl.cnf中x509_extensions是否指向了正确的段,以及生成命令是否通过-config指定了该配置文件。
完成以上步骤后,你就拥有了一个可用的私有CA:ca.key负责签名,ca.crt作为信任锚分发给Broker和客户端,openssl.cnf定义了签发策略,basicConstraints和keyUsage则划定了这张根证书的权力边界。下一步才是用这个CA去签发服务器证书和客户端证书,并在MQTT Broker上配置双向验证。但请记住,CA一旦投入使用,它的私钥和根证书就应当保持稳定;任何更换都意味着所有已签发证书需要重新签发和分发。