4. 证书签发与模板管理:CSR生成、模板定制与SAN配置

证书签发这件事,说白了就是回答三个问题:你是谁?谁给你担保?担保多久?但大规模环境下,这三个问题会变成:几千个设备分别是谁?怎么批量担保?怎么保证每个设备的SAN(Subject Alternative Name)都不写错?

我个人习惯把这一章拆成三个实操环节:CSR生成、模板定制、SAN与扩展属性。咱们一个一个来。

4.1 证书签名请求(CSR)生成

CSR是证书的“出生证明”。你向CA提交CSR,CA审核后签发证书。CSR里包含你的公钥和身份信息,私钥必须留在本地,绝不能随CSR一起发送。

⚠️ 我曾经见过一个团队,把私钥和CSR打包成tar.gz发给CA……嗯,那批证书全部作废重签。私钥就是你的命根子,永远不要离开生成它的设备。

4.1.1 使用OpenSSL生成CSR

最常用的工具就是OpenSSL。下面这个命令,我几乎每天都会敲几遍:

# 生成私钥(RSA 2048位)
openssl genrsa -out device.key 2048

# 生成CSR
openssl req -new \
  -key device.key \
  -out device.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IoT/CN=device-001.example.com"

这里有个细节:-subj 里的CN(Common Name)最好和设备的主标识一致。我建议用设备的唯一标识符,比如序列号或FQDN。为什么?因为后面SAN配置会用到,保持一致性可以减少很多麻烦。

4.1.2 批量CSR生成策略

大规模环境下,一台台生成CSR会疯掉。我一般用两种方式:

策略 适用场景 优点 缺点
集中生成 设备有HSM或安全元件 私钥统一管理,审计方便 单点故障,网络开销大
边缘生成 设备本地生成CSR 私钥不离开设备,安全性高 设备性能要求高,管理分散

我个人偏向边缘生成。你想想看,私钥如果集中存放,一旦被攻破,所有设备都得轮换。边缘生成虽然管理麻烦点,但安全边界更清晰。

💡 批量生成脚本示例:
#!/bin/bash
# 从设备清单文件读取,批量生成CSR
while IFS=',' read -r device_id fqdn; do
  openssl req -new \
    -newkey rsa:2048 -nodes \
    -keyout "keys/${device_id}.key" \
    -out "csrs/${device_id}.csr" \
    -subj "/CN=${fqdn}/OU=IoT/O=MyCompany"
done < device_list.csv

4.2 证书模板定制

证书模板就是CA签发证书时的“配方”。你告诉CA:这个证书的有效期多长、密钥用途是什么、能不能做CA、SAN怎么填。模板定得好,后面轮换就省心。

4.2.1 模板的核心字段

我习惯把模板字段分成三类:身份类、用途类、约束类。

类别 字段 说明 我的建议值
身份类 Subject 证书主体的DN CN=设备FQDN,O=公司名
用途类 Key Usage 密钥用途 digitalSignature, keyEncipherment
用途类 Extended Key Usage 扩展密钥用途 serverAuth, clientAuth
约束类 Basic Constraints 是否CA证书 CA:FALSE(设备证书)
约束类 Validity 有效期 90天(轮换周期短)
🔑 关键点: 设备证书的Key Usage不要加keyCertSign和cRLSign。这两个是CA证书才用的。我见过有人把设备证书配成CA,结果设备被攻破后,攻击者用这个证书签了更多假证书……那场面,惨不忍睹。

4.2.2 使用OpenSSL配置文件定制模板

OpenSSL的配置文件是模板的载体。下面是我常用的设备证书模板:

[ req ]
default_bits        = 2048
distinguished_name  = req_distinguished_name
req_extensions      = v3_req
prompt              = no

[ req_distinguished_name ]
CN = device.example.com
O  = MyCompany
OU = IoT

[ v3_req ]
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
extendedKeyUsage       = serverAuth, clientAuth
subjectAltName         = @alt_names

[ alt_names ]
DNS.1 = device.example.com
DNS.2 = device.local
IP.1  = 192.168.1.100

生成时指定配置文件:

openssl req -new -config device_template.conf -key device.key -out device.csr

嗯,这里要注意:prompt = no 表示不交互式输入,适合批量自动化。如果你需要调试,可以改成 prompt = yes。

4.3 SAN与扩展属性配置

SAN(Subject Alternative Name)是现代PKI的标配。从Chrome 58开始,浏览器已经不再信任仅依赖CN的证书了。SAN才是真正的“身份证明”。

4.3.1 SAN的类型与选择

SAN类型 前缀 示例 适用场景
DNS DNS: DNS:device-001.example.com 域名访问的设备
IP IP: IP:192.168.1.100 纯IP访问的设备
URI URI: URI:spiffe://domain/device/001 SPIFFE身份体系

我在项目中遇到过一个问题:设备同时有IPv4和IPv6地址,但只配了DNS SAN。结果IPv6链路通信时证书校验失败。从那以后,我要求所有设备证书至少包含一个DNS SAN和一个IP SAN。

4.3.2 扩展属性配置

除了SAN,还有一些扩展属性在大规模场景下非常有用:

  • Authority Key Identifier:标识签发CA,方便证书链构建。我建议所有证书都带上。
  • Subject Key Identifier:标识证书主体公钥,用于快速匹配。这个也建议必配。
  • Certificate Policies:策略OID,用于标识证书用途。比如设备证书用1.3.6.1.4.1.12345.1.1。
  • CRL Distribution Points:CRL分发点,告诉客户端去哪下载吊销列表。大规模环境必须配。
💡 扩展属性配置示例(OpenSSL):
[ v3_ext ]
authorityKeyIdentifier = keyid:always,issuer
subjectKeyIdentifier   = hash
certificatePolicies    = @policy_section
crlDistributionPoints  = @crl_section

[ policy_section ]
policyIdentifier = 1.3.6.1.4.1.12345.1.1

[ crl_section ]
fullname = URI:http://crl.example.com/device.crl

4.4 本章知识体系

下面这张图,是我自己梳理的CSR与模板管理流程。每次做新项目,我都会拿它来对一遍,防止漏掉环节。

证书签发与模板管理流程 证书签发与模板管理核心流程 设备身份信息 CSR生成(私钥本地保留) 证书模板定制(有效期、密钥用途、扩展属性) SAN与扩展属性配置(DNS/IP/URI) 签发正式证书 ⚠️ 关键原则 私钥不离开设备 SAN优先于CN 💡 避坑提示 模板统一管理 有效期不宜过长

这张图把整个流程串起来了。从设备身份信息输入,到CSR生成、模板定制、SAN配置,最后签发证书。每一步都有坑,但只要你按这个流程走,基本不会出大问题。

📌 总结一下我的经验:
  • CSR生成时,私钥必须留在设备本地,这是安全底线。
  • 模板定制时,设备证书不要配CA权限,密钥用途要精确。
  • SAN配置时,至少包含一个DNS和一个IP,别偷懒。
  • 扩展属性里,AKI、SKI、CDP是必选项,别省略。