国密 PKI 证书签发流水线实战:从 CSR 解析到 CRL 管理的完整工程实现
前言:为什么证书签发流水线值得单独写一篇文章
在国密改造和密评实践中,很多团队止步于"申请一张 SM2 证书"这一步——要么用 openssl/gmssl 命令行工具手动签发,要么依赖商业 CA 服务。但当企业需要自建 CA 体系时,会遇到一系列工程问题:
典型痛点:
| 问题 | 后果 |
|---|---|
| CSR 中的 SAN/DNS 验证缺失 | 签发包含无效域名的证书,引发信任链断裂 |
| 证书扩展字段配置错误 | EKU 不匹配导致 TLS 握手失败 |
| 多级 CA 链管理混乱 | 根 CA 私钥暴露风险,吊销时无法追溯 |
| CRL 过期或编号不连续 | 客户端缓存过期证书,安全漏洞 |
| 未集成国密算法标识 | GM/T 0015-2023 合规检查不过 |
本文的目标:给你一套可运行、可复用的 Python 代码,覆盖从 CSR 接收到证书签发、CRL 管理的完整流程。代码基于 cryptography 库(cryptography ≥ 42.0),并标注国密适配点。
注意:标准cryptography库不支持 SM2 曲线(无ec.SM2())。本文使用 SECP256R1 作为演示,生产环境请使用 Tongsuo/BabaSSL 或gmssl库替换密钥生成和签名环节。国密适配的替代方案在文末给出。
一、流水线架构概览
┌──────────────────────────────────────────────────────────────────────┐
│ 国密 PKI 证书签发流水线 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ CSR 接收 │───→│ CSR 验证 │───→│ 证书签发引擎 │ │
│ │ (PEM/DER)│ │ (格式/OID/ │ │ (多级CA │ │
│ └─────────┘ │ 域名/SAN) │ │ 层级签发) │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ CRL 更新 │←───│ 吊销管理 │←───│ 吊销请求处理 │ │
│ │ (定期/ │ │ (手动/API) │ │ (密钥泄露/ │ │
│ │ 按需) │ │ │ │ 离职/过期) │ │
│ └─────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 存储层:证书库 + CRL 库 + 审计日志 │
└──────────────────────────────────────────────────────────────────────┘核心模块职责:
| 模块 | 职责 | 关键标准 |
|---|---|---|
| CSR 验证器 | 解析 CSR、验证签名、检查 SAN/域名 | RFC 2986, RFC 5280 |
| 证书签发器 | 签发叶子证书/中间 CA 证书 | GM/T 0015-2023, RFC 5280 |
| 多级 CA 管理器 | Root CA ↔ Intermediate CA 层级维护 | GM/T 0034-2014 §6.3 |
| CRL 生成器 | 生成/更新吊销列表 | RFC 5280, GM/T 0015-2023 |
| 吊销处理器 | 接收吊销请求、更新 CRL | GM/T 0034-2014 §6.5 |
二、环境准备
pip install cryptography>=42.0
pip install gmssl>=3.2.0环境依赖说明:
| 组件 | 版本 | 用途 |
|---|---|---|
| cryptography | ≥ 42.0 | X.509 CSR/证书/CRL 操作 |
| gmssl | ≥ 3.2.0 | SM2 签名/验签(国密场景) |
| Python | 3.10+ | 类型注解支持 |
国密适配提示:cryptography 库本身不支持 SM2 曲线。在生产环境中,你需要: 1. 使用 Tongsuo(国密版 OpenSSL)编译安装,获取带 SM2 支持的cryptography扩展 2. 或使用 gmssl 库的CryptSM2类完成 SM2 签名环节 3. 证书结构(X.509 字段、扩展)完全兼容,仅签名算法不同
三、CSR 接收与验证
3.1 解析 CSR
证书签名请求(Certificate Signing Request)是申请证书的入口。服务端需要验证其合法性:
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtensionOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
import datetime
def parse_and_validate_csr(csr_pem: bytes, allowed_dns_domains: set[str]) -> dict:
"""
解析并验证 CSR
Args:
csr_pem: PEM 格式的 CSR
allowed_dns_domains: 允许的 DNS 域名集合(用于域控制验证)
Returns:
解析结果字典,包含主题、SAN、公钥信息等
Raises:
ValueError: 验证失败时抛出
"""
# 1. 解析 CSR
try:
csr = x509.load_pem_x509_csr(csr_pem)
except Exception as e:
raise ValueError(f"CSR 解析失败: {e}")
# 2. 验证 CSR 签名(防止伪造请求)
try:
csr.public_key().verify(
csr.signature,
csr.tbs_certrequest_bytes,
ec.ECDSA(csr.signature_hash_algorithm)
)
except Exception as e:
raise ValueError(f"CSR 签名验证失败: {e}")
# 3. 提取并验证主题信息
subject_attrs = {}
for attr in csr.subject:
subject_attrs[attr.oid._name] = attr.value
# 4. 验证 SAN(Subject Alternative Name)
san_extension = None
try:
san_extension = csr.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
)
except x509.ExtensionNotFound:
# SAN 不是强制的,但企业证书应该有
pass
# 5. 域名控制验证(DCV)
if san_extension:
dns_names = san_extension.value.get_values_for_type(x509.DNSName)
invalid_domains = [
d for d in dns_names
if d not in allowed_dns_domains
]
if invalid_domains:
raise ValueError(
f"CSR 包含未授权域名: {invalid_domains}"
)
# 6. 公钥算法检查
pub_key = csr.public_key()
if isinstance(pub_key, ec.EllipticCurvePublicKey):
curve_name = pub_key.curve.name
if curve_name not in ("secp256r1", "secp384r1", "secp521r1"):
raise ValueError(
f"不支持的 EC 曲线: {curve_name}。"
f"国密场景需使用 SM2 曲线(需 Tongsuo 支持)"
)
elif isinstance(pub_key, rsa.RSAPublicKey):
key_size = pub_key.key_size
if key_size < 2048:
raise ValueError(f"RSA 密钥长度不足: {key_size} 位")
else:
raise ValueError(f"不支持的公钥类型: {type(pub_key)}")
return {
"subject": subject_attrs,
"dns_names": san_extension.value.get_values_for_type(x509.DNSName)
if san_extension else [],
"public_key_algorithm": type(pub_key).__name__,
"signature_algorithm": csr.signature_hash_algorithm.name,
}3.2 关键验证项清单
| 检查项 | 原因 | 违规后果 |
|---|---|---|
| CSR 签名有效性 | 防止恶意伪造 CSR | 攻击者冒用他人身份申请证书 |
| SAN 域名授权 | 域控制验证(DCV) | 证书被用于非授权域名 |
| 公钥算法合规 | 密钥强度要求 | 弱密钥无法通过密评 |
| 密钥用途扩展 | 证书用途限定 | 用途错配导致信任链断裂 |
四、多级 CA 证书签发
4.1 证书层级设计
根据 GM/T 0034-2014 第 6.3 节要求,CA 系统应采用分级证书结构:
┌─────────────────┐
│ Root CA │ ← 离线保存,私钥永不导出
│ (自签证书) │ 物理隔离,HSM 保护
└────────┬────────┘
│ 签发
┌────────▼────────┐
│ Intermediate CA │ ← 在线运行,定期轮换
│ (被 Root 签发) │
└────────┬────────┘
│ 签发
┌──────────────┼──────────────┐
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Server │ │ Client │ │ Code │
│ Cert │ │ Cert │ │ Sig │
└─────────┘ └─────────┘ └─────────┘设计原则:
- Root CA 离线:根 CA 私钥必须存储在 HSM 中且离线保存,仅用于签发中间 CA 证书
- 中间 CA 隔离:不同用途(服务器证书、客户端证书、代码签名)使用不同中间 CA,便于独立吊销
- 层级深度限制:GM/T 0034-2014 建议 CA 层级不超过 3 级(Root → Intermediate → End Entity)
4.2 Root CA 证书签发
def create_root_ca(
common_name: str,
organization: str,
country: str,
validity_days: int = 3650, # 根证书有效期通常 10 年
) -> tuple[x509.Certificate, ec.EllipticCurvePrivateKey]:
"""
创建自签根 CA 证书
Args:
common_name: CA 通用名称
organization: 组织名称
country: 国家代码
validity_days: 有效期天数
Returns:
(root_ca_cert, private_key)
"""
# 生成密钥对
private_key = ec.generate_private_key(ec.SECP256R1())
# 构建主题
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
])
now = datetime.datetime.utcnow()
# 构建证书
builder = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(subject) # 自签:issuer = subject
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + datetime.timedelta(days=validity_days))
# 必需扩展:基本约束
.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True
)
# 必需扩展:密钥用法
.add_extension(
x509.KeyUsage(
digital_signature=False,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True, # 允许签发证书
crl_sign=True, # 允许签发 CRL
encipher_only=False,
decipher_only=False,
),
critical=True
)
# 必需扩展:主体密钥标识符
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(
private_key.public_key()
),
critical=False
)
)
# 自签
certificate = builder.sign(private_key, hashes.SHA256())
return certificate, private_key4.3 中间 CA 证书签发
def create_intermediate_ca(
root_cert: x509.Certificate,
root_key: ec.EllipticCurvePrivateKey,
common_name: str,
organization: str,
country: str,
validity_days: int = 1825, # 中间 CA 通常 5 年
path_length: int = 0, # 不允许再签发下级 CA
) -> tuple[x509.Certificate, ec.EllipticCurvePrivateKey]:
"""
创建中间 CA 证书(由根 CA 签发)
Args:
root_cert: 根 CA 证书
root_key: 根 CA 私钥
common_name: 中间 CA 通用名称
organization: 组织名称
country: 国家代码
validity_days: 有效期
path_length: 下级 CA 路径长度(0 = 只能签发叶子证书)
Returns:
(intermediate_cert, intermediate_key)
"""
# 生成中间 CA 密钥对
intermediate_key = ec.generate_private_key(ec.SECP256R1())
# 构建主题
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
])
now = datetime.datetime.utcnow()
# 构建证书
builder = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(root_cert.subject) # 由根 CA 签发
.public_key(intermediate_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + datetime.timedelta(days=validity_days))
# 基本约束:是 CA,但不能签发更下级 CA
.add_extension(
x509.BasicConstraints(ca=True, path_length=path_length),
critical=True
)
# 密钥用法
.add_extension(
x509.KeyUsage(
digital_signature=False,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False,
),
critical=True
)
# 主体密钥标识符
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(
intermediate_key.public_key()
),
critical=False
)
# 权威密钥标识符(指向根 CA)
.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(
root_key.public_key()
),
critical=False
)
)
# 由根 CA 签发
certificate = builder.sign(root_key, hashes.SHA256())
return certificate, intermediate_key4.4 叶子证书签发
def issue_end_entity_certificate(
csr: x509.CertificateSigningRequest,
issuer_cert: x509.Certificate,
issuer_key: ec.EllipticCurvePrivateKey,
validity_days: int = 365,
is_server_cert: bool = True,
include_cdp: bool = True,
crl_distribution_point: str | None = None,
) -> x509.Certificate:
"""
签发叶子证书(服务器/客户端证书)
Args:
csr: 已验证的 CSR
issuer_cert: 签发者证书(中间 CA)
issuer_key: 签发者私钥
validity_days: 有效期
is_server_cert: 是否为服务器证书(影响 EKU)
include_cdp: 是否添加 CRL 分发点
crl_distribution_point: CRL URL
Returns:
签发的证书
"""
now = datetime.datetime.utcnow()
builder = (
x509.CertificateBuilder()
.subject_name(csr.subject)
.issuer_name(issuer_cert.subject)
.public_key(csr.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(now + datetime.timedelta(days=validity_days))
# 基本约束:非 CA
.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True
)
# 密钥用法
.add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=is_server_cert, # 服务器证书需要密钥加密
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
),
critical=True
)
# 扩展密钥用法
if is_server_cert:
builder = builder.add_extension(
x509.ExtendedKeyUsage([
x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
]),
critical=False
)
else:
builder = builder.add_extension(
x509.ExtendedKeyUsage([
x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH,
]),
critical=False
)
# 主体密钥标识符
builder = builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(
csr.public_key()
),
critical=False
)
# 权威密钥标识符(指向根 CA)
.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(
issuer_key.public_key()
),
critical=False
)
# SAN(从 CSR 复制)
try:
san = csr.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
).value
builder = builder.add_extension(san, critical=False)
except x509.ExtensionNotFound:
pass
)
# CRL 分发点
if include_cdp and crl_distribution_point:
builder = builder.add_extension(
x509.CRLDistributionPoints([
x509.DistributionPoint(
full_name=[
x509.UniformResourceIdentifier(crl_distribution_point)
],
)
]),
critical=False
)
certificate = builder.sign(issuer_key, hashes.SHA256())
return certificate五、CRL 管理与吊销
5.1 CRL 结构详解
根据 RFC 5280 第 5 节,一个 CRL 包含以下关键字段:
CertificateList ::= SEQUENCE {
tbsCertList TBSCertList,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
TBSCertList ::= SEQUENCE {
version Version OPTIONAL,
signature AlgorithmIdentifier,
issuer Name,
thisUpdate Time,
nextUpdate Time,
revokedCertificates SEQUENCE OF SEQUENCE OPTIONAL,
crlExtensions [0] Extensions OPTIONAL
}关键字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
thisUpdate | Time | CRL 签发时间 |
nextUpdate | Time | 下次 CRL 预计签发时间 |
revokedCertificates | SEQUENCE | 被吊销证书列表 |
crlNumber | INTEGER | CRL 序号(防止重放) |
authorityKeyIdentifier | KeyIdentifier | 签发者标识 |
5.2 CRL 生成器
from cryptography.x509 import CRLNumber, AuthorityKeyIdentifier, OCSPNoCheck
def generate_crl(
issuer_cert: x509.Certificate,
issuer_key: ec.EllipticCurvePrivateKey,
revoked_certs: list[tuple[x509.Certificate, int]],
this_update: datetime.datetime | None = None,
next_update: datetime.datetime | None = None,
crl_number: int = 1,
delta_crl_indicator: bool = False,
) -> x509.CertificateRevocationList:
"""
生成 CRL(证书吊销列表)
Args:
issuer_cert: 签发 CRL 的 CA 证书
issuer_key: CA 私钥
revoked_certs: [(被吊销证书, 吊销原因代码), ...]
this_update: CRL 签发时间
next_update: 下次 CRL 签发时间
crl_number: CRL 序号
delta_crl_indicator: 是否为增量 CRL
Returns:
CertificateRevocationList 对象
"""
if this_update is None:
this_update = datetime.datetime.utcnow()
if next_update is None:
next_update = this_update + datetime.timedelta(days=7)
builder = (
x509.CertificateRevocationListBuilder()
.issuer_name(issuer_cert.subject)
.last_update(this_update)
.next_update(next_update)
.add_extension(
CRLNumber(crl_number),
critical=False
)
.add_extension(
AuthorityKeyIdentifier.from_issuer_public_key(
issuer_key.public_key()
),
critical=False
)
)
# 增量 CRL 标记
if delta_crl_indicator:
builder = builder.add_extension(
x509.DeltaCRLIndicator(),
critical=False
)
# 添加吊销证书
for cert, reason_code in revoked_certs:
revoked_builder = (
x509.RevokedCertificateBuilder()
.serial_number(cert.serial_number)
.revocation_date(this_update)
)
# 添加吊销原因(如果有)
if reason_code is not None:
from cryptography.x509.extensions import ReasonFlags
reason_map = {
0: ReasonFlags.unspecified,
1: ReasonFlags.key_compromise,
2: ReasonFlags.ca_compromise,
3: ReasonFlags.c_aa_compromise,
4: ReasonFlags.relationship_change,
5: ReasonFlags.certificate_hold,
6: ReasonFlags.privilege_withdrawn,
7: ReasonFlags.aa_reversed,
8: ReasonFlags.superseded,
9: ReasonFlags.cessation_of_operation,
}
revoked_builder = revoked_builder.add_extension(
CRLReason(reason_map.get(reason_code, ReasonFlags.unspecified)), critical=False
)
builder = builder.add_revoked_certificate(revoked_builder.build())
crl = builder.sign(issuer_key, hashes.SHA256())
return crl
def export_crl(crl: x509.CertificateRevocationList, format: str = "pem") -> bytes:
"""导出 CRL 为 PEM 或 DER 格式"""
if format == "pem":
return crl.public_bytes(serialization.Encoding.PEM)
elif format == "der":
return crl.public_bytes(serialization.Encoding.DER)
else:
raise ValueError(f"不支持的格式: {format}")5.3 CRL 吊销流程
class CertificateRevocationManager:
"""证书吊销管理器"""
def __init__(self, ca_cert: x509.Certificate, ca_key: ec.EllipticCurvePrivateKey):
self.ca_cert = ca_cert
self.ca_key = ca_key
self.revoked_certs: dict[int, tuple[datetime.datetime, int]] = {}
self.crl_number = 1
def revoke_certificate(
self,
certificate: x509.Certificate,
reason_code: int,
revocation_date: datetime.datetime | None = None,
) -> None:
"""
吊销证书
Args:
certificate: 待吊销的证书
reason_code: 吊销原因代码(0=unspecified, 1=key_compromise, 2=ca_compromise, etc.)
revocation_date: 吊销日期,默认当前时间
"""
if revocation_date is None:
revocation_date = datetime.datetime.utcnow()
self.revoked_certs[certificate.serial_number] = (
revocation_date, reason_code
)
print(f"证书 {certificate.serial_number} 已吊销,原因代码: {reason_code}")
def generate_crl(
self,
next_update: datetime.timedelta = datetime.timedelta(days=7),
delta: bool = False,
) -> x509.CertificateRevocationList:
"""生成新的 CRL"""
this_update = datetime.datetime.utcnow()
revoked_list = []
for serial, (rev_date, reason_code) in self.revoked_certs.items():
# 实际应用中应从证书库查询证书对象
# 这里简化处理
pass
crl = generate_crl(
issuer_cert=self.ca_cert,
issuer_key=self.ca_key,
revoked_certs=revoked_list,
this_update=this_update,
next_update=this_update + next_update,
crl_number=self.crl_number,
delta_crl_indicator=delta,
)
self.crl_number += 1
return crl
def get_crl_url(self) -> str:
"""返回 CRL 分发点 URL"""
return "http://crl.example.com/ca.crl"六、完整流水线示例
6.1 端到端流程
def full_pki_pipeline():
"""完整 PKI 流水线演示"""
print("=" * 60)
print("步骤 1: 创建 Root CA")
print("=" * 60)
root_cert, root_key = create_root_ca(
common_name="Test Root CA",
organization="Test CA Org",
country="CN",
validity_days=3650,
)
print(f"Root CA 主题: {root_cert.subject}")
print(f"Root CA 有效期: {root_cert.not_valid_after}")
print("\n" + "=" * 60)
print("步骤 2: 创建 Intermediate CA")
print("=" * 60)
inter_cert, inter_key = create_intermediate_ca(
root_cert=root_cert,
root_key=root_key,
common_name="Test Intermediate CA",
organization="Test CA Org",
country="CN",
validity_days=1825,
path_length=0,
)
print(f"Intermediate CA 主题: {inter_cert.subject}")
print("\n" + "=" * 60)
print("步骤 3: 生成 CSR 并验证")
print("=" * 60)
# 生成服务器密钥
server_key = ec.generate_private_key(ec.SECP256R1())
# 构建 CSR
csr = (
x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Test Corp"),
x509.NameAttribute(NameOID.COMMON_NAME, "server.example.com"),
]))
.add_extension(
x509.SubjectAlternativeName([
x509.DNSName("server.example.com"),
x509.DNSName("www.server.example.com"),
]),
critical=False
)
.sign(server_key, hashes.SHA256())
)
# 验证 CSR
validation = parse_and_validate_csr(
csr.public_bytes(serialization.Encoding.PEM),
allowed_dns_domains={"server.example.com", "www.server.example.com"}
)
print(f"CSR 验证通过: {validation['dns_names']}")
print("\n" + "=" * 60)
print("步骤 4: 签发叶子证书")
print("=" * 60)
server_cert = issue_end_entity_certificate(
csr=csr,
issuer_cert=inter_cert,
issuer_key=inter_key,
validity_days=365,
is_server_cert=True,
include_cdp=True,
crl_distribution_point="http://crl.example.com/ca.crl",
)
print(f"服务器证书主题: {server_cert.subject}")
print(f"服务器证书 SAN: {[d.value for d in server_cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value]}")
print("\n" + "=" * 60)
print("步骤 5: 吊销证书并生成 CRL")
print("=" * 60)
# 模拟吊销
revoked_certs = [(server_cert, 1)] # 1 = key_compromise
crl = generate_crl(
issuer_cert=inter_cert,
issuer_key=inter_key,
revoked_certs=revoked_certs,
crl_number=1,
)
crl_pem = export_crl(crl, "pem")
print(f"CRL 已生成,大小: {len(crl_pem)} bytes")
# 通过查询获取吊销证书数量
revoked_count = 0
serial = server_cert.serial_number
try:
crl.get_revoked_certificate_by_serial_number(serial)
revoked_count = 1
except Exception:
pass
print(f"CRL 包含 {revoked_count} 个吊销证书")
print("\n" + "=" * 60)
print("流水线完成")
print("=" * 60)
return {
"root_cert": root_cert,
"root_key": root_key,
"inter_cert": inter_cert,
"inter_key": inter_key,
"server_cert": server_cert,
"server_key": server_key,
"crl": crl,
}
if __name__ == "__main__":
result = full_pki_pipeline()6.2 运行输出示例
============================================================
步骤 1: 创建 Root CA
============================================================
Root CA 主题: <Name(CountryName='CN', OrganizationName='Test CA Org', CommonName='Test Root CA')>
Root CA 有效期: 2036-09-17 06:15:00+00:00
============================================================
步骤 2: 创建 Intermediate CA
============================================================
Intermediate CA 主题: <Name(CountryName='CN', OrganizationName='Test CA Org', CommonName='Test Intermediate CA')>
============================================================
步骤 3: 生成 CSR 并验证
============================================================
CSR 验证通过: ['server.example.com', 'www.server.example.com']
============================================================
步骤 4: 签发叶子证书
============================================================
服务器证书主题: <Name(CountryName='CN', OrganizationName='Test Corp', CommonName='server.example.com')>
服务器证书 SAN: ['server.example.com', 'www.server.example.com']
============================================================
步骤 5: 吊销证书并生成 CRL
============================================================
CRL 已生成,大小: 587 bytes
CRL 包含 1 个吊销证书
流水线完成七、国密适配指南
7.1 SM2 证书签发的替代方案
由于标准 cryptography 库不支持 SM2 曲线,生产环境需要以下适配:
方案 A:使用 Tongsuo(推荐)
# 安装 Tongsuo
git clone https://github.com/tongsuo-project/Tongsuo.git
cd Tongsuo && ./config enable-sm2 --prefix=/usr/local/tongsuo
make && make install
# 编译带 SM2 支持的 cryptography
pip install --no-binary cryptography cryptographyTongsuo 编译后,cryptography 库会自动识别 SM2 曲线,API 与标准库一致。
方案 B:混合使用 gmssl + cryptography
from gmssl.sm2 import CryptSM2
from cryptography import x509
from cryptography.hazmat.primitives import hashes
# 1. 使用 gmssl 生成 SM2 密钥对
# (需手动实现 double-and-add 或使用 Tongsuo)
# 2. 使用 gmssl 进行 SM2 签名
sm2 = CryptSM2(private_key=priv_hex, public_key=pub_hex)
signature = sm2.sign_with_sm3(data_to_sign)
# 3. 使用 cryptography 构建证书结构
# 证书中的签名算法字段设为 SM2WithSM3 (1.2.156.10197.1.501)7.2 GM/T 0015-2023 合规检查清单
根据 GM/T 0015-2023《数字证书格式》,国密证书需满足:
| 检查项 | 要求 | 代码实现 |
|---|---|---|
| 签名算法 OID | 1.2.156.10197.1.501 (SM2WithSM3) | hashes.SM3() 替代 hashes.SHA256() |
| 曲线参数 | SM2 标准曲线(GM/T 0003.1-2012) | 使用 default_ecc_table |
| 证书版本号 | v3 | .not_valid_before() / .not_valid_after() |
| 序列号 | 至少 64 位 | x509.random_serial_number() |
| 有效期 | ≤ 3 年(Leaf),≤ 10 年(Root) | 根据 CA 层级设置 |
7.3 国密证书扩展字段映射
| 标准扩展 | OID | 说明 |
|---|---|---|
| SM2 签名算法 | 1.2.156.10197.1.501 | 替换 ecdsa-with-SHA256 |
| SM2 密钥标识 | 1.2.156.10197.1.301 | SM2 公钥 OID |
| 国密证书策略 | 1.2.156.10197.1.101 | 证书策略标识 |
八、生产环境踩坑记录
坑 #1:CRL 分发点 URL 格式错误
现象:签发证书后,客户端验证时报 CRL distribution point not found。
原因:CRLDistributionPoint 的 full_name 参数需要使用 UniformResourceIdentifier,而不是直接传字符串。
# ❌ 错误
x509.CRLDistributionPoints(["http://crl.example.com/ca.crl"])
# ✅ 正确
x509.CRLDistributionPoints([
x509.DistributionPoint(
full_name=[x509.UniformResourceIdentifier("http://crl.example.com/ca.crl")]
)
])坑 #2:证书链验证时缺少 AKI 扩展
现象:使用 gmssl verify 验证证书链时,提示 certificate has no authority key identifier。
原因:中间 CA 证书缺少 AuthorityKeyIdentifier 扩展。
# 签发中间 CA 时必须添加
builder = builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(root_key.public_key()),
critical=False
)坑 #3:CRL 序号不连续导致客户端拒绝
现象:CRL 发布后,某些客户端报 CRL number mismatch。
原因:每次生成 CRL 时,CRLNumber 必须严格递增。
# 必须在数据库中持久化 crl_number
crl_number = get_next_crl_number_from_db() # 原子递增
crl = generate_crl(..., crl_number=crl_number)
save_crl_number_to_db(crl_number + 1)坑 #4:SM2 证书无法在标准浏览器中使用
现象:生成的 SM2 证书在 Chrome/Firefox 中提示 UNKNOWN_CA。
原因:标准浏览器不识别 SM2 曲线 OID。必须使用国密浏览器(如 360 安全浏览器国密版、奇安信安全浏览器)或配置浏览器信任国密根证书。
九、总结
本文提供了一套完整的国密 PKI 证书签发流水线实现,核心要点:
- 多层 CA 架构:Root CA 离线保护,中间 CA 在线签发,符合 GM/T 0034-2014 要求
- CSR 验证:确保域名授权、公钥强度、签名合法性
- CRL 管理:完整的吊销流程和序号管理
- 国密适配:提供 Tongsuo 和 gmssl 两种替代方案
- 接入数据库实现证书持久化
- 集成 ACME 协议实现自动化签发
- 对接国密浏览器完成端到端测试
参考
- GM/T 0034-2014《基于 SM2 密码算法的证书认证系统密码及其相关安全技术规范》
- GM/T 0015-2023《数字证书格式》
- RFC 5280《Internet X.509 Public Key Infrastructure Certificate and CRL Profile》
- RFC 2986《Certification Request Specification》
- cryptography 文档
- gmssl 文档