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信任链与扩展项约束关系 CA私钥 ca.key RSA 4096 / AES-256加密 权限600,离线保管 自签名 自签名根证书 ca.crt Issuer = Subject 有效期约10年 SHA-256签名 根证书扩展项 basicConstraints: critical, CA:TRUE keyUsage: critical, keyCertSign, cRLSign 签发 MQTT Broker 服务器证书 basicConstraints: CA:FALSE keyUsage: digitalSignature, keyEncipherment MQTT 客户端证书 basicConstraints: CA:FALSE keyUsage: digitalSignature extendedKeyUsage: clientAuth ✕ 不能签发下级证书 ✕ 不能签发下级证书 信任链验证方向:客户端证书 → 根证书(用CA公钥验证签名) 根证书必须包含 keyCertSign,否则签发的证书无法通过验证 终端证书 CA:FALSE,只能用于TLS握手,不能继续签发

五、验证根证书是否具备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指定了该配置文件。

建议在实验环境中把根证书的CN、有效期、扩展项输出保存为一份“CA档案”,与ca.key分开存放。后续签发服务器/客户端证书时,所有证书的Issuer字段都应与该根证书Subject一致,这是排查信任链问题的最快切入点。

完成以上步骤后,你就拥有了一个可用的私有CA:ca.key负责签名,ca.crt作为信任锚分发给Broker和客户端,openssl.cnf定义了签发策略,basicConstraints和keyUsage则划定了这张根证书的权力边界。下一步才是用这个CA去签发服务器证书和客户端证书,并在MQTT Broker上配置双向验证。但请记住,CA一旦投入使用,它的私钥和根证书就应当保持稳定;任何更换都意味着所有已签发证书需要重新签发和分发。