使用 Rust 实现自动证书签发 —— 基于 lynx-cert 的实战解读

453 阅读3分钟

使用 Rust 实现自动证书签发 —— 基于 lynx-cert 的实战解读

数字证书是 HTTPS 加密通信的基石。Rust 生态在证书自动签发与管理方面发展迅速,为代理、网关等场景提供安全保障。本文基于 lynx-server 项目的 lynx-cert 模块,详解如何用 Rust 自动生成 CA 证书、自签名证书、为指定域名/IP 颁发服务证书,并实现 PEM 文件的读写。


1. 依赖库与准备

lynx-cert 主要用到了以下核心 crates:

  • rcgen:证书生成与签名
  • rsa:RSA 密钥生成
  • tokio-rustls/rustls:TLS 配置与证书加载
  • anyhow:统一错误处理

Cargo.toml 中添加相关依赖:

[dependencies]
rcgen = "0.11"
rsa = "0.9"
tokio-rustls = "0.25"
anyhow = "1.0"
rand = "0.8"
time = "0.3"

2. 生成自签名证书(Self-signed Certificate)

最基础的证书是自签名证书,常用于本地开发、内网测试等场景。

pub fn get_self_signed_cert(
    subject_alt_names: Option<Vec<String>>,
) -> Result<(Certificate, KeyPair)> {
    let subject_alt_names = subject_alt_names.unwrap_or_else(|| vec![
        "localhost".to_string(),
        "127.0.0.1".to_string()
    ]);
    let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names)?;
    Ok((cert, key_pair))
}
  • 支持自定义或默认 SAN(Subject Alternative Name),如 localhost、127.0.0.1。
  • 返回证书对象和密钥对,可直接用于 TLS 启动。

3. 生成 CA 根证书及密钥

要为其他域名签发证书,需先生成 CA 根证书和密钥:

pub fn gen_ca_private_key() -> Result<KeyPair> {
    let mut rng = OsRng;
    let bits = 2048;
    let private_key = RsaPrivateKey::new(&mut rng, bits)?;
    let private_key_der = private_key.to_pkcs8_der()?;
    let private_key = KeyPair::try_from(private_key_der.as_bytes())?;
    Ok(private_key)
}

fn gen_ca_cert(key: &KeyPair) -> Result<Certificate> {
    let mut params = CertificateParams::default();
    let (yesterday, tomorrow) = validity_period(); // 证书有效期
    params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
    params.distinguished_name.push(DnType::CommonName, "lynxProxy");
    params.distinguished_name.push(DnType::OrganizationName, "lynxProxy");
    params.not_before = yesterday;
    params.not_after = tomorrow;
    params.extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth);
    params.key_usages.push(KeyUsagePurpose::KeyCertSign);
    params.key_usages.push(KeyUsagePurpose::CrlSign);
    let ca_cert = params.self_signed(key)?;
    Ok(ca_cert)
}
  • 先生成 2048 位 RSA 私钥。
  • 再生成 CA 证书,指定用途为签名证书(KeyCertSign)、吊销列表(CrlSign),并标记为 CA。

快速生成一对 CA 证书与密钥:

pub fn create_ca_cert_and_key() -> Result<(Certificate, KeyPair)> {
    let private_key = gen_ca_private_key()?;
    let ca_cert = gen_ca_cert(&private_key)?;
    Ok((ca_cert, private_key))
}

4. 使用 CA 为域名/IP 签发服务端证书

核心函数如下:

pub fn gen_cert_by_ca(
    ca_cert: &Certificate,
    ca_key: &KeyPair,
    host: HostType,
) -> Result<Certificate> {
    let mut params = CertificateParams::default();
    params.serial_number = Some(thread_rng().gen::<u64>().into());
    let not_before = OffsetDateTime::now_utc() - Duration::seconds(NOT_BEFORE_OFFSET);
    params.not_before = not_before;
    params.not_after = not_before + Duration::seconds(TTL_SECS);
    params.distinguished_name.push(DnType::CommonName, &host);

    let subject_alt_name = match host {
        HostType::DnsName(host) => SanType::DnsName(Ia5String::try_from(host)?),
        HostType::IpAddress(ip) => SanType::IpAddress(ip),
    };
    params.subject_alt_names.push(subject_alt_name);

    let cert = params.signed_by(ca_key, ca_cert, ca_key)?;
    Ok(cert)
}
  • 支持 DNS 名称和 IP 地址(通过 HostType 枚举区分)。
  • 自动设置有效期、序列号、SAN。
  • 用 CA 密钥对该证书进行签名,得到服务端可用的证书。

5. PEM 文件的读写(证书落盘与加载)

证书和密钥通常需落盘保存或加载:

  • 写入文件
pub fn write_cert_to_file(ca_cert: Certificate, path: PathBuf) -> Result<()> {
    fs::write(path, ca_cert.pem())?;
    Ok(())
}
pub fn write_key_to_file(key: KeyPair, path: PathBuf) -> Result<()> {
    fs::write(path, key.serialize_pem())?;
    Ok(())
}
  • 读取文件
pub fn read_cert_key_by_file(path: &PathBuf) -> Result<KeyPair> {
    let pem_key = fs::read_to_string(path)?;
    let key = KeyPair::from_pem(&pem_key)?;
    Ok(key)
}
pub fn read_cert_by_file(key: &KeyPair, path: &PathBuf) -> Result<Certificate> {
    let ca_data = fs::read_to_string(path)?;
    let ca_params = CertificateParams::from_ca_cert_pem(&ca_data)?;
    let ca_cert = ca_params.self_signed(key)?;
    Ok(ca_cert)
}

6. 典型使用流程

  1. 生成 CA 证书和密钥
    let (ca_cert, ca_key) = create_ca_cert_and_key()?;
  2. 为指定域名/IP 签发服务证书
    let cert = gen_cert_by_ca(&ca_cert, &ca_key, HostType::DnsName("example.com".to_string()))?;
  3. 保存证书和密钥到 PEM 文件
    write_cert_to_file(cert, PathBuf::from("server.crt"))?;
  4. 在 TLS 服务端加载证书和密钥
    结合 rustls、tokio-rustls 的 ServerConfig 配置即可。

7. 总结

lynx-cert 模块为 Rust 项目提供了简洁、易扩展的证书签发能力,无论是自签名、CA 还是为多域名/IP 动态签发,都能快速实现。适合代理、HTTPS 中间人、服务网关等安全场景。


推荐项目

本文基于 lynx-server 开源项目。lynx-server 是一个用 Rust 编写的现代化高性能代理服务器,支持多协议代理、智能证书签发与管理。如果你对网络安全、Rust 网络开发感兴趣,欢迎 Star、试用和贡献代码!