Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第四十九章 数字证书与 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, .crtBase64 编码的 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 的核心组件,主要职责包括:

  1. 身份验证:在签发证书前验证申请者的身份
  2. 证书签发:使用 CA 的私钥对证书进行数字签名
  3. 证书更新:在证书过期前重新签发
  4. 证书吊销:在私钥泄露或身份变更时宣布证书失效
  5. 发布 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 生成

练习建议

  1. 基础练习:使用 rcgen 生成一个自签名证书,用 OpenSSL 查看其内容,确认包含正确的 SAN 扩展。

  2. 中级练习:构建一个两层 PKI(根 CA + 中间 CA + 服务器证书),验证完整证书链。

  3. 高级练习:实现一个简易的证书验证工具,解析 PEM 证书文件,检查有效期、密钥用途和签名算法强度。

  4. 实践项目:为你的 Rust Web 应用(如 Axum 或 Actix-web)配置 TLS,使用 rustls 和自签名证书启用 HTTPS。


密码学箴言:证书解决的不是加密问题,而是信任问题。再强的加密算法,如果信任根基被攻破,也只是空中楼阁。