企业内部 PKI 建设实战:从根 CA 到证书自动化管理
前言
几乎所有中大型企业都需要内部 PKI(公钥基础设施),但大多数团队的 PKI 建设都停留在"用 OpenSSL 随便生成几张证书"的水平。一个生产级的内部 PKI 需要考虑:根 CA 的安全隔离、中间 CA 的层级设计、证书生命周期管理、吊销机制、以及与现有身份系统的集成。
本文从零开始,搭建一个完整的内部 PKI 体系,涵盖:
- 根 CA 的创建与离线保护
- 中间 CA 的签发与策略配置
- 证书模板设计(TLS 服务器、TLS 客户端、代码签名)
- 自动化证书签发(基于 ACME 协议或自建 API)
- CRL 和 OCSP 吊销服务
- 与 LDAP/AD 的集成
一、架构设计
1.1 推荐的两层 CA 架构
CODE
根 CA(Root CA)
├── 中间 CA - 生产环境(Prod Issuing CA)
│ ├── TLS 服务器证书
│ ├── TLS 客户端证书(mTLS)
│ └── 代码签名证书
├── 中间 CA - 测试环境(Test Issuing CA)
│ └── 测试用证书
└── 中间 CA - 代码签名(Code Signing CA)
└── 内部软件签名证书为什么用两层而不是直接由根 CA 签发?
- 安全性:根 CA 的私钥一旦泄露,整个 PKI 体系崩溃。根 CA 应离线保存,仅用于签发中间 CA 证书。
- 灵活性:不同用途的证书由不同的中间 CA 签发,可以设置不同的策略和有效期。
- 合规性:GB/T 39786-2021《信息安全技术 信息系统密码应用基本要求》建议 CA 分层管理。
1.2 根 CA 的安全要求
根 CA 是整个 PKI 的信任锚,必须满足:
- 物理隔离:根 CA 服务器不接入生产网络,最好离线保存
- HSM 保护:根 CA 私钥存储在硬件安全模块(HSM)中
- 多人控制:使用 M-of-N 分割知识方案,需要多个管理员同时操作才能使用根 CA
- 审计日志:所有根 CA 操作必须有完整的审计日志
二、根 CA 创建
2.1 环境准备
根 CA 的操作应在离线环境中完成(断开网络的虚拟机或物理机)。
BASH
# 创建工作目录
mkdir -p /root/ca/{root-ca,intermediate-ca,certs,crl,newcerts,private}
chmod 700 /root/ca/private
# 创建必要的文件
touch /root/ca/index.txt
echo 1000 > /root/ca/serial
echo 1000 > /root/ca/crlnumber2.2 根 CA 配置文件
INI
# /root/ca/root-ca.cnf
[ca]
default_ca = CA_default
[CA_default]
dir = /root/ca
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
crlnumber = $dir/crlnumber
private_key = $dir/private/root-ca.key
certificate = $dir/certs/root-ca.crt
default_md = sha256
default_days = 3650
default_crl_days = 365
policy = policy_loose
x509_extensions = v3_intermediate_ca
[policy_loose]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[req]
default_bits = 4096
default_md = sha256
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no
[req_distinguished_name]
CN = Internal Root CA
O = Your Company
C = CN
[v3_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:TRUE
keyUsage = critical,digitalSignature,cRLSign,keyCertSign
[v3_intermediate_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:TRUE,pathlen:0
keyUsage = critical,digitalSignature,cRLSign,keyCertSign2.3 生成根 CA 密钥和证书
BASH
# 生成根 CA 私钥(RSA-4096)
openssl genrsa -aes256 -out /root/ca/private/root-ca.key 4096
chmod 400 /root/ca/private/root-ca.key
# 生成根 CA 自签名证书(有效期 10 年)
openssl req -config /root/ca/root-ca.cnf \
-key /root/ca/private/root-ca.key \
-new -x509 -days 3650 -sha256 \
-extensions v3_ca \
-out /root/ca/certs/root-ca.crt
# 验证
openssl x509 -in /root/ca/certs/root-ca.crt -text -noout | head -20三、中间 CA 签发
3.1 创建中间 CA
BASH
# 创建中间 CA 目录
mkdir -p /root/ca/intermediate-ca/{certs,crl,newcerts,private,csr}
chmod 700 /root/ca/intermediate-ca/private
touch /root/ca/intermediate-ca/index.txt
echo 2000 > /root/ca/intermediate-ca/serial
echo 2000 > /root/ca/intermediate-ca/crlnumber
# 生成中间 CA 私钥
openssl genrsa -aes256 -out /root/ca/intermediate-ca/private/intermediate-ca.key 4096
chmod 400 /root/ca/intermediate-ca/private/intermediate-ca.key
# 生成 CSR
openssl req -config /root/ca/intermediate-ca.cnf \
-new -sha256 \
-key /root/ca/intermediate-ca/private/intermediate-ca.key \
-out /root/ca/intermediate-ca/csr/intermediate-ca.csr3.2 用根 CA 签发中间 CA 证书
BASH
# 在根 CA 环境中执行
openssl ca -config /root/ca/root-ca.cnf \
-extensions v3_intermediate_ca \
-days 3650 -notext -md sha256 \
-in /root/ca/intermediate-ca/csr/intermediate-ca.csr \
-out /root/ca/intermediate-ca/certs/intermediate-ca.crt
# 验证
openssl verify -CAfile /root/ca/certs/root-ca.crt \
/root/ca/intermediate-ca/certs/intermediate-ca.crt3.3 创建证书链文件
BASH
cat /root/ca/intermediate-ca/certs/intermediate-ca.crt \
/root/ca/certs/root-ca.crt > /root/ca/intermediate-ca/certs/ca-chain.crt四、证书模板设计
4.1 TLS 服务器证书模板
INI
# /root/ca/intermediate-ca/templates/server-cert.cnf
[req]
default_bits = 2048
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = server.example.com
[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = server.example.com
DNS.2 = *.example.com
IP.1 = 192.168.1.1004.2 TLS 客户端证书模板(mTLS)
INI
# /root/ca/intermediate-ca/templates/client-cert.cnf
[req]
default_bits = 2048
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = user@example.com
emailAddress = user@example.com
[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = clientAuth4.3 代码签名证书模板
INI
# /root/ca/intermediate-ca/templates/code-signing.cnf
[req]
default_bits = 2048
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = Your Company Code Signing
O = Your Company
[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = codeSigning五、自动化证书签发
5.1 签发脚本
BASH
#!/bin/bash
set -euo pipefail
CA_DIR="/root/ca/intermediate-ca"
TEMPLATE_DIR="$CA_DIR/templates"
DAYS=366
usage() {
echo "Usage: $0 <server|client|codesign> <CN> [SAN...]"
exit 1
}
[ $# -lt 2 ] && usage
TYPE=$1
CN=$2
shift 2
EXTRA_SANS=("$@")
case $TYPE in
server) TEMPLATE="$TEMPLATE_DIR/server-cert.cnf" ;;
client) TEMPLATE="$TEMPLATE_DIR/client-cert.cnf" ;;
codesign) TEMPLATE="$TEMPLATE_DIR/code-signing.cnf" ;;
*) usage ;;
esac
# 生成密钥
openssl genrsa -out "$CA_DIR/newcerts/${CN}.key" 2048
# 生成 CSR
openssl req -new \
-key "$CA_DIR/newcerts/${CN}.key" \
-out "$CA_DIR/csr/${CN}.csr" \
-subj "/CN=${CN}"
# 签发证书
openssl ca -config "$CA_DIR/intermediate-ca.cnf" \
-extensions v3_req \
-extfile "$TEMPLATE" \
-days $DAYS -notext -md sha256 \
-in "$CA_DIR/csr/${CN}.csr" \
-out "$CA_DIR/newcerts/${CN}.crt" \
-batch
echo "✅ 证书已签发: $CA_DIR/newcerts/${CN}.crt"
echo "✅ 私钥位置: $CA_DIR/newcerts/${CN}.key"5.2 证书续期脚本
BASH
#!/bin/bash
# renew-cert.sh - 证书续期脚本
set -euo pipefail
CA_DIR="/root/ca/intermediate-ca"
DAYS=366
for cert in "$CA_DIR"/newcerts/*.crt; do
CN=$(basename "$cert" .crt)
# 检查证书是否在未来 30 天内过期
if ! openssl x509 -checkend 2592000 -noout -in "$cert"; then
echo "🔄 续期证书: $CN"
# 使用原有密钥重新签发
openssl ca -config "$CA_DIR/intermediate-ca.cnf" \
-days $DAYS -notext -md sha256 \
-in "$CA_DIR/csr/${CN}.csr" \
-out "$CA_DIR/newcerts/${CN}.crt" \
-batch
echo "✅ 已续期: $CN"
fi
done六、CRL 和 OCSP 吊销服务
6.1 证书吊销
BASH
# 吊销证书
openssl ca -config /root/ca/intermediate-ca/intermediate-ca.cnf \
-revoke /root/ca/intermediate-ca/newcerts/bad-cert.crt
# 生成 CRL
openssl ca -config /root/ca/intermediate-ca/intermediate-ca.cnf \
-gencrl -out /root/ca/intermediate-ca/crl/intermediate-ca.crl
# 查看 CRL
openssl crl -in /root/ca/intermediate-ca/crl/intermediate-ca.crl -text -noout6.2 OCSP 响应器
BASH
# 启动 OCSP 响应器
openssl ocsp -port 8888 \
-index /root/ca/intermediate-ca/index.txt \
-rsigner /root/ca/intermediate-ca/certs/ocsp-signer.crt \
-rkey /root/ca/intermediate-ca/private/ocsp-signer.key \
-CA /root/ca/intermediate-ca/certs/intermediate-ca.crt \
-text &
# 测试 OCSP 查询
openssl ocsp \
-url http://localhost:8888 \
-CAfile /root/ca/intermediate-ca/certs/ca-chain.crt \
-issuer /root/ca/intermediate-ca/certs/intermediate-ca.crt \
-cert /root/ca/intermediate-ca/newcerts/server-cert.crt七、与 LDAP/AD 集成
7.1 将证书发布到 LDAP
PYTHON
#!/usr/bin/env python3
"""将证书发布到 LDAP 目录服务"""
import ssl
import ldap3
from pathlib import Path
LDAP_SERVER = "ldap://ldap.example.com"
LDAP_BIND_DN = "cn=admin,dc=example,dc=com"
LDAP_BIND_PW = "password"
LDAP_BASE_DN = "ou=certificates,dc=example,dc=com"
def publish_cert(cn: str, cert_path: str, ldap_conn):
"""将证书的 DER 格式发布到 LDAP"""
# 转换 PEM 到 DER
cert_pem = Path(cert_path).read_text()
cert_der = ssl.PEM_cert_to_DN(cert_pem)
# 添加到 LDAP
dn = f"cn={cn},{LDAP_BASE_DN}"
ldap_conn.add(dn, ['top', 'person', {
'cn': cn,
'userCertificate;binary': cert_der,
}])
# 使用示例
server = ldap3.Server(LDAP_SERVER)
conn = ldap3.Connection(server, LDAP_BIND_DN, LDAP_BIND_PW, auto_bind=True)
publish_cert("server.example.com", "/path/to/cert.pem", conn)
conn.unbind()7.2 从 LDAP 验证证书
BASH
# 从 LDAP 获取证书
ldapsearch -H ldap://ldap.example.com \
-D "cn=admin,dc=example,dc=com" -w password \
-b "cn=server.example.com,ou=certificates,dc=example,dc=com" \
userCertificate;binary
# 将 LDAP 中的证书与本地证书对比
openssl x509 -in /path/to/local.crt -noout -modulus | md5sum八、生产环境注意事项
8.1 根 CA 的离线保护
- 根 CA 私钥应存储在 HSM 或加密的 USB 设备中
- 根 CA 服务器在完成中间 CA 签发后应关机离线保存
- 恢复根 CA 需要至少 M 个管理员中的 N 个同时在场
8.2 中间 CA 的高可用
- 中间 CA 数据库(
index.txt)应定期备份 - 考虑部署 CA 集群(主从模式),避免单点故障
- 证书签发 API 应有认证和审计
8.3 证书生命周期管理
| 证书类型 | 有效期 | 续期阈值 | 密钥长度 |
|---|---|---|---|
| 根 CA | 10-20 年 | 不适用 | RSA-4096 / SM2 |
| 中间 CA | 5-10 年 | 到期前 6 个月 | RSA-4096 / SM2 |
| TLS 服务器 | 1 年 | 到期前 30 天 | RSA-2048 / SM2 |
| TLS 客户端 | 1 年 | 到期前 30 天 | RSA-2048 / SM2 |
| 代码签名 | 1-3 年 | 到期前 60 天 | RSA-2048 / SM2 |
8.4 监控和告警
BASH
#!/bin/bash
# cert-monitor.sh - 证书过期监控
EXPIRY_THRESHOLD=2592000 # 30 天
for cert in /root/ca/intermediate-ca/newcerts/*.crt; do
CN=$(basename "$cert" .crt)
if ! openssl x509 -checkend $EXPIRY_THRESHOLD -noout -in "$cert"; then
EXPIRY=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)
echo "⚠️ 证书即将过期: $CN (过期时间: $EXPIRY)"
# 发送告警(邮件、钉钉、企业微信等)
fi
done总结
内部 PKI 建设是一个系统工程,不仅仅是"生成几张证书"。一个生产级的 PKI 需要考虑:
- 安全架构:根 CA 离线保护,中间 CA 分层管理
- 证书策略:不同类型证书的模板和生命周期
- 自动化:签发、续期、吊销的自动化流程
- 集成:与 LDAP/AD、监控系统、告警系统的集成
- 合规:满足 GB/T 39786-2021 等标准的要求
参考来源
- 国家密码管理局. GM/T 0034-2014《基于 SM2 密码算法的证书认证系统密码及其相关安全技术规范》. 2014.
- 国家密码管理局. GM/T 0037-2014《证书认证系统检测规范》. 2014.
- 国家密码管理局. GM/T 0038-2014《证书认证密钥管理系统检测规范》. 2014.
- GB/T 39786-2021《信息安全技术 信息系统密码应用基本要求》. 2021.
- RFC 5280: Internet X.509 Public Key Infrastructure Certificate and CRL Profile.