第四十九章 数字证书与 PKI
公钥的身份证:如何证明“我是我“
在上一章我们学习了非对称加密和数字签名,但还有一个关键问题没有解决:当你收到一个公钥时,如何确定它真的属于声称的那个人?如果攻击者替换了你手中的公钥,整个加密通信就会形同虚设。数字证书与公钥基础设施(PKI)正是为了解决“公钥分发与身份绑定“这一核心问题而诞生的。
49.1 为什么需要数字证书
信任问题的本质
假设 Alice 想给 Bob 发送加密消息,她需要 Bob 的公钥。但如果中间人 Mallory 将 Bob 的公钥替换为自己的公钥,Alice 就会用 Mallory 的公钥加密,Mallory 可以解密、篡改、重新加密后转发给 Bob。这就是公钥分发问题。
解决思路:引入一个双方都信任的第三方,由这个第三方为公钥“背书“——这就是数字证书的核心思想。
数字证书的作用
数字证书(Digital Certificate)是由证书颁发机构(CA)签名的数据结构,它将以下信息绑定在一起:
- 主体身份:个人、服务器或组织的标识信息
- 公钥:主体的公钥
- 有效期:证书的有效时间范围
- 颁发者:签发该证书的 CA 信息
- 数字签名:CA 对该证书内容的签名
任何人都可以用 CA 的公钥验证证书签名的真实性,从而确认证书中的公钥确实属于证书声明的主体。
49.2 X.509 证书格式
X.509 是国际电信联盟(ITU-T)制定的数字证书标准,也是目前最广泛使用的证书格式。TLS/SSL、代码签名、电子邮件加密等场景都基于 X.509。
X.509 v3 证书结构
一个 X.509 证书包含以下核心字段:
Certificate
├── tbsCertificate (待签名内容)
│ ├── version (版本号,v3 为 2)
│ ├── serialNumber (证书序列号)
│ ├── signature (签名算法标识)
│ ├── issuer (颁发者名称)
│ ├── validity (有效期)
│ │ ├── notBefore (生效时间)
│ │ └── notAfter (过期时间)
│ ├── subject (主体名称)
│ ├── subjectPublicKeyInfo (主体公钥信息)
│ │ ├── algorithm (公钥算法)
│ │ └── subjectPublicKey (公钥值)
│ └── extensions (扩展字段,v3 特有)
│ ├── subjectKeyIdentifier (主体密钥标识符)
│ ├── authorityKeyIdentifier (颁发者密钥标识符)
│ ├── keyUsage (密钥用途)
│ ├── extendedKeyUsage (扩展密钥用途)
│ ├── subjectAltName (主体备用名称,如 DNS 名、IP 地址)
│ └── basicConstraints (基本约束,如是否为 CA)
├── signatureAlgorithm (签名算法)
└── signatureValue (签名值)
证书编码格式
X.509 证书有两种常见的编码表示:
| 格式 | 扩展名 | 编码方式 | 特点 |
|---|---|---|---|
| DER | .der, .cer | 二进制 ASN.1 DER | 紧凑、不可读 |
| PEM | .pem, .crt | Base64 编码的 DER | 文本格式,以 -----BEGIN CERTIFICATE----- 开头 |
PEM 格式更常见,可以用文本编辑器查看,也方便在邮件和配置文件中传递。
查看证书内容
使用 OpenSSL 可以查看证书详情:
# 查看 PEM 证书内容
openssl x509 -in server.crt -text -noout
# 查看证书主题和颁发者
openssl x509 -in server.crt -subject -issuer -noout
输出示例(部分):
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
04:7d:3c:...
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Let's Encrypt, CN = R3
Validity
Not Before: Jan 1 00:00:00 2024 GMT
Not After : Apr 1 00:00:00 2024 GMT
Subject: CN = example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
49.3 证书链与信任锚
证书链(Certificate Chain)
CA 通常采用层级结构。根 CA 直接签发证书的情况很少见,更多时候是:
- 根 CA(Root CA):自签名证书,预装在操作系统和浏览器中
- 中间 CA(Intermediate CA):由根 CA 签发,负责实际签发终端实体证书
- 终端实体(End Entity):服务器、用户等持有的证书,由中间 CA 签发
这样就形成了一条证书链:
终端实体证书(example.com)
↑ 由 Intermediate CA 签名
中间 CA 证书(Let's Encrypt R3)
↑ 由 Root CA 签名
根 CA 证书(ISRG Root X1)
↑ 自签名
验证证书时,验证方从终端实体证书开始,沿着证书链逐级验证每个证书的签名,直到到达一个受信任的根证书。
信任锚(Trust Anchor)
信任锚是证书验证的起点,通常是自签名的根 CA 证书。操作系统和浏览器预装了数百个受信任的根证书,构成了信任库(Trust Store)。
#![allow(unused)]
fn main() {
// 概念示意:信任库中的根证书
let trust_anchors = vec![
"DigiCert Global Root CA",
"ISRG Root X1", // Let's Encrypt
"GlobalSign Root CA",
// ... 数百个根证书
];
}
自签名证书:根 CA 的证书是自己签名的,没有更上层的颁发者。这意味着信任根 CA 是一种人为约定,基于其声誉、审计和安全实践。
证书链验证过程
1. 获取服务器证书
2. 检查有效期(notBefore <= 当前时间 <= notAfter)
3. 检查证书吊销状态(CRL 或 OCSP)
4. 验证签名:用中间 CA 的公钥验证服务器证书签名
5. 获取中间 CA 证书,验证其签名:用根 CA 公钥验证
6. 根 CA 证书是否在信任库中?
7. 全部通过 → 证书可信
49.4 证书颁发机构(CA)
CA 的角色与职责
证书颁发机构(Certificate Authority)是 PKI 的核心组件,主要职责包括:
- 身份验证:在签发证书前验证申请者的身份
- 证书签发:使用 CA 的私钥对证书进行数字签名
- 证书更新:在证书过期前重新签发
- 证书吊销:在私钥泄露或身份变更时宣布证书失效
- 发布 CRL 和 OCSP:提供证书状态查询服务
域名验证(DV)、组织验证(OV)与扩展验证(EV)
| 类型 | 验证内容 | 签发时间 | 浏览器显示 |
|---|---|---|---|
| DV | 仅验证域名所有权 | 分钟级 | 普通锁图标 |
| OV | 验证域名 + 组织身份 | 小时到几天 | 普通锁图标 |
| EV | 严格验证组织法律身份 | 几天到几周 | 曾显示绿色组织名(现大多浏览器已取消) |
私有 CA 与自签名证书
除了公共 CA(如 Let’s Encrypt、DigiCert),组织也可以搭建私有 CA用于内部系统。开发测试环境中,自签名证书(自己签发自己的证书)也很常见。
#![allow(unused)]
fn main() {
// 自签名证书 vs CA 签发证书的区别
// 自签名证书:issuer == subject
let self_signed = Certificate {
subject: "My Server",
issuer: "My Server", // 相同!
public_key: server_pub_key,
signature: sign_with(server_priv_key, ...),
};
// CA 签发证书:issuer 是 CA
let ca_signed = Certificate {
subject: "My Server",
issuer: "My CA", // 不同
public_key: server_pub_key,
signature: sign_with(ca_priv_key, ...),
};
}
49.5 证书生命周期
证书申请流程
1. 生成密钥对(申请者本地完成)
2. 创建证书签名请求(CSR)
3. 提交 CSR 和身份证明材料给 CA
4. CA 验证身份
5. CA 签发证书
6. 下载并安装证书
证书签名请求(CSR)
CSR(Certificate Signing Request)是向 CA 申请证书时提交的文件,包含公钥和主体信息,用申请者的私钥签名。
# 生成私钥和 CSR
openssl req -new -newkey rsa:2048 -nodes \
-keyout server.key -out server.csr \
-subj "/C=CN/O=MyOrg/CN=example.com"
# 查看 CSR 内容
openssl req -in server.csr -text -noout
证书吊销
证书可能在过期前就需要失效,常见原因:
- 私钥泄露或丢失
- 域名所有权变更
- 组织信息变更
- 证书签发错误
吊销机制:
- CRL(Certificate Revocation List):CA 定期发布的已吊销证书序列号列表
- OCSP(Online Certificate Status Protocol):实时在线查询证书状态
- OCSP Stapling:服务器预先获取 OCSP 响应,在 TLS 握手时附带发送
49.6 Rust 实现:生成与验证证书
使用 rcgen 生成自签名证书
rcgen 是一个纯 Rust 的证书生成库,适合在测试环境或内部工具中生成证书。
# Cargo.toml
[dependencies]
rcgen = "0.12"
use rcgen::{Certificate, CertificateParams, KeyPair, SanType};
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建证书参数
let mut params = CertificateParams::new(vec![
"localhost".to_string(),
"127.0.0.1".to_string(),
]);
// 添加 Subject Alternative Name(对于 TLS 很重要)
params.subject_alt_names = vec![
SanType::DnsName("localhost".to_string()),
SanType::IpAddress(std::net::IpAddr::V4("127.0.0.1".parse()?)),
];
// 生成自签名证书
let cert = Certificate::from_params(params)?;
// 获取 PEM 格式的证书和私钥
let cert_pem = cert.serialize_pem()?;
let key_pem = cert.serialize_private_key_pem();
// 保存到文件
fs::write("server.crt", cert_pem)?;
fs::write("server.key", key_pem)?;
println!("自签名证书已生成:");
println!(" 证书: server.crt");
println!(" 私钥: server.key");
Ok(())
}
生成 CA 并签发终端证书
#![allow(unused)]
fn main() {
use rcgen::{Certificate, CertificateParams, KeyUsagePurpose};
fn create_ca() -> Result<Certificate, Box<dyn std::error::Error>> {
let mut params = CertificateParams::new(vec!["My Test CA".to_string()]);
// 标记为基本约束:这是 CA 证书
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
// 设置密钥用途
params.key_usages = vec![
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::CrlSign,
];
let ca_cert = Certificate::from_params(params)?;
Ok(ca_cert)
}
fn sign_server_cert(ca: &Certificate) -> Result<Certificate, Box<dyn std::error::Error>> {
let mut params = CertificateParams::new(vec!["example.com".to_string()]);
params.subject_alt_names = vec![
rcgen::SanType::DnsName("example.com".to_string()),
rcgen::SanType::DnsName("www.example.com".to_string()),
];
// 设置扩展密钥用途:TLS 服务器认证
params.extended_key_usages = vec![
rcgen::ExtendedKeyUsagePurpose::ServerAuth,
];
let server_cert = Certificate::from_params(params)?;
// 用 CA 的私钥签名服务器证书
let signed_pem = server_cert.serialize_pem_with_signer(ca)?;
// 返回签名后的证书(实际使用时需要重新构造或保存 PEM)
Ok(server_cert)
}
}
使用 rustls 验证证书链
rustls 是 Rust 生态中最流行的 TLS 库,它内置了证书验证功能。
#![allow(unused)]
fn main() {
use rustls::{ClientConfig, RootCertStore};
use rustls_pemfile;
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;
fn create_tls_client() -> Result<Arc<ClientConfig>, Box<dyn std::error::Error>> {
// 加载系统根证书
let mut root_store = RootCertStore::empty();
// 从文件加载自定义 CA 证书(用于测试环境)
let ca_file = File::open("ca.crt")?;
let mut ca_reader = BufReader::new(ca_file);
let certs: Vec<_> = rustls_pemfile::certs(&mut ca_reader)
.filter_map(|r| r.ok())
.collect();
for cert in certs {
root_store.add(cert)?;
}
// 创建 TLS 客户端配置
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
Ok(Arc::new(config))
}
}
解析和检查 X.509 证书
#![allow(unused)]
fn main() {
use x509_parser::pem::parse_x509_pem;
use x509_parser::prelude::*;
fn inspect_certificate(pem_data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let (_, pem) = parse_x509_pem(pem_data)?;
let cert = pem.parse_x509()?;
// 基本信息
println!("版本: {:?}", cert.version());
println!("序列号: {}", cert.tbs_certificate.raw_serial_as_string());
// 颁发者和主体
println!("颁发者: {}", cert.issuer());
println!("主体: {}", cert.subject());
// 有效期
let validity = cert.validity();
println!("生效时间: {}", validity.not_before);
println!("过期时间: {}", validity.not_after);
// 公钥信息
let pubkey = cert.tbs_certificate.subject_pki;
println!("公钥算法: {:?}", pubkey.algorithm.algorithm);
// 扩展字段
for ext in cert.extensions() {
println!("扩展: {:?}", ext.oid);
}
// 验证证书是否过期
let now = x509_parser::time::ASN1Time::now();
if now < validity.not_before || now > validity.not_after {
println!("警告:证书已过期或尚未生效!");
} else {
println!("证书在有效期内");
}
Ok(())
}
}
完整的证书生成与验证示例
use rcgen::{Certificate, CertificateParams, SanType};
use ring::signature;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. 创建 CA
let mut ca_params = CertificateParams::new(vec!["Test CA".to_string()]);
ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let ca_cert = Certificate::from_params(ca_params)?;
// 2. 创建服务器证书
let mut server_params = CertificateParams::new(vec!["myapp.local".to_string()]);
server_params.subject_alt_names = vec![
SanType::DnsName("myapp.local".to_string()),
SanType::IpAddress("192.168.1.1".parse()?),
];
let server_cert = Certificate::from_params(server_params)?;
// 3. 用 CA 签名服务器证书
let signed_cert_pem = server_cert.serialize_pem_with_signer(&ca_cert)?;
let ca_cert_pem = ca_cert.serialize_pem()?;
println!("=== CA 证书 ===");
println!("{}", ca_cert_pem);
println!("=== 服务器证书 ===");
println!("{}", signed_cert_pem);
// 4. 验证证书链(概念演示)
// 实际验证应使用 rustls 或 webpki
println!("\n证书链验证:");
println!(" 服务器证书由 CA 签名 ✓");
println!(" CA 证书自签名 ✓");
println!(" 信任链完整 ✓");
Ok(())
}
49.7 本章总结
| 概念 | 说明 | Rust 生态 |
|---|---|---|
| X.509 | 数字证书国际标准格式 | x509-parser 用于解析 |
| 证书链 | 从终端实体到根 CA 的层级信任链 | rustls 自动验证 |
| CA | 证书颁发机构,负责身份验证和签名 | rcgen 可用于构建测试 CA |
| 信任锚 | 预装的受信任根证书 | webpki-roots 提供 Mozilla 根证书 |
| 证书吊销 | CRL 和 OCSP 机制 | rustls 支持 OCSP Stapling |
| 自签名证书 | 自己签名的证书,适合测试 | rcgen 生成 |
练习建议
-
基础练习:使用
rcgen生成一个自签名证书,用 OpenSSL 查看其内容,确认包含正确的 SAN 扩展。 -
中级练习:构建一个两层 PKI(根 CA + 中间 CA + 服务器证书),验证完整证书链。
-
高级练习:实现一个简易的证书验证工具,解析 PEM 证书文件,检查有效期、密钥用途和签名算法强度。
-
实践项目:为你的 Rust Web 应用(如 Axum 或 Actix-web)配置 TLS,使用
rustls和自签名证书启用 HTTPS。
密码学箴言:证书解决的不是加密问题,而是信任问题。再强的加密算法,如果信任根基被攻破,也只是空中楼阁。