Skip to content

Latest commit

 

History

History
560 lines (421 loc) · 19.2 KB

File metadata and controls

560 lines (421 loc) · 19.2 KB

Certon

Rust 自动 HTTPS/TLS 证书管理库,基于 ACME 协议。

Crates.io Documentation License

English | 简体中文

Certon 为 Rust 程序提供生产级的自动证书管理:从任何 ACME 兼容的证书颁发机构(CA)自动获取、续期和服务 TLS 证书,只需几行代码。

use certon::Config;

#[tokio::main]
async fn main() -> certon::Result<()> {
    let domains = vec!["example.com".into()];
    let tls_config = certon::manage(&domains).await?;
    // 将 tls_config 用于 tokio-rustls、hyper、axum、salvo 等框架
    Ok(())
}

目录


功能特性

  • 全自动证书管理 -- 无需人工干预即可获取、续期和缓存 TLS 证书
  • 支持全部三种 ACME 验证方式 -- HTTP-01、TLS-ALPN-01 和 DNS-01
  • 多 CA 支持 -- Let's Encrypt(生产和测试环境)、ZeroSSL、Google Trust Services,或任何 ACME 兼容的 CA
  • OCSP 装订 -- 自动获取并装订 OCSP 响应,提升隐私和性能;装订结果持久化到存储中,重启后仍然可用
  • 通配符证书 -- 通过 DNS-01 验证配合可插拔的 DnsProvider trait 实现
  • 按需 TLS -- 在 TLS 握手时为未预配置的域名获取证书,支持白名单、决策函数和速率限制
  • 证书缓存 -- 内存中的 CertCache,基于域名索引和通配符匹配,实现快速 TLS 握手查找
  • 可配置密钥类型 -- ECDSA P-256(默认)、ECDSA P-384、RSA 2048、RSA 4096 和 Ed25519
  • 后台维护 -- 自动续期检查(每 10 分钟)和 OCSP 装订刷新(每 1 小时)
  • 内置速率限制 -- 防止向 CA 发送过多请求
  • 指数退避重试 -- 失败的证书操作会以递增的延迟进行重试(最长 30 天)
  • 分布式验证求解 -- DistributedSolver 通过共享 Storage 在多实例间协调验证,支持负载均衡器后的集群部署
  • 文件系统存储与原子写入 -- 默认的 FileStorage 使用先写临时文件再重命名的方式确保崩溃安全;分布式锁文件通过后台保活任务实现集群协调
  • 自定义存储后端 -- 实现 Storage trait 即可使用数据库、KV 存储或任何其他持久化层
  • 事件回调 -- 监听证书生命周期事件(cert_obtainingcert_obtainedcert_renewedcert_failedcert_revoked 等)
  • Builder 模式 -- 人性化的 Config::builder()AcmeIssuer::builder()ZeroSslIssuer::builder() 简化配置
  • 外部账户绑定(EAB) -- 一等支持需要 EAB 的 CA(如 ZeroSSL)
  • 证书链偏好 -- 按根证书/颁发者 Common Name 或链大小选择首选证书链
  • 证书吊销 -- 通过 ACME 协议吊销受损的证书
  • 原生 rustls 集成 -- CertResolver 实现了 rustls::server::ResolvesServerCert,可直接接入任何基于 rustls 的服务器
  • 可选加密后端 -- 默认使用 aws-lc-rs(性能优秀,支持 FIPS),也可切换为 ring(纯 Rust 编译,体积更小)

环境要求

  1. Rust 2024 edition(Rust 1.89+)和 Tokio 异步运行时
  2. 您控制的公共 DNS 域名,A/AAAA 记录指向您的服务器
  3. 80 端口可从公网访问(用于 HTTP-01 验证),和/或 443 端口(用于 TLS-ALPN-01 验证)
    • 可以通过端口转发实现
    • 或使用 DNS-01 验证完全免除端口要求
    • 这是 ACME 协议的要求,并非本库的限制
  4. 持久化存储用于证书、密钥和元数据
    • 默认:本地文件系统(Linux: ~/.local/share/certon,macOS: ~/Library/Application Support/certon,Windows: %APPDATA%/certon
    • 可通过 Storage trait 使用自定义后端

使用本库之前,您的域名必须将 A/AAAA 记录指向您的服务器(除非使用 DNS-01 验证)。

安装

Cargo.toml 中添加 certon

[dependencies]
certon = "0.1"
tokio = { version = "1", features = ["full"] }

加密后端

Certon 支持两种加密后端,通过 feature flag 选择:

Feature 说明 默认
aws-lc-rs AWS Libcrypto (aws-lc-rs),性能优秀,支持 FIPS 默认启用
ring ring 加密库,纯 Rust 编译,体积更小 可选

默认使用 aws-lc-rs,无需额外配置。如需使用 ring

[dependencies]
certon = { version = "0.1", default-features = false, features = ["ring"] }

快速开始

最简单的使用方式 -- 一个函数调用管理一切:

use certon::Config;

#[tokio::main]
async fn main() -> certon::Result<()> {
    let domains = vec!["example.com".into()];

    // 获取(或从存储加载)证书,返回可直接使用的
    // rustls::ServerConfig,用于任何 TLS 服务器。
    let tls_config = certon::manage(&domains).await?;

    // 将 tls_config 用于 tokio-rustls、hyper、axum、salvo 等框架
    Ok(())
}

执行以上代码将会:

  1. 在操作系统默认目录创建 FileStorage
  2. 从 Let's Encrypt(生产环境)为指定域名获取证书。
  3. 返回配置了 CertResolverrustls::ServerConfig,可服务托管的证书。

使用示例

基础用法 -- 使用默认配置管理证书

use std::sync::Arc;
use certon::{Config, FileStorage, Storage};

#[tokio::main]
async fn main() -> certon::Result<()> {
    let storage: Arc<dyn Storage> = Arc::new(FileStorage::default());
    let config = Config::builder()
        .storage(storage)
        .build();

    let domains = vec!["example.com".into(), "www.example.com".into()];
    config.manage_sync(&domains).await?;

    // 启动后台维护(续期 + OCSP 刷新)
    let _handle = certon::start_maintenance(&config);

    Ok(())
}

自定义 CA 和邮箱

use std::sync::Arc;
use certon::{
    AcmeIssuer, Config, FileStorage, Storage,
    LETS_ENCRYPT_STAGING,
};

let storage: Arc<dyn Storage> = Arc::new(FileStorage::default());

let issuer = AcmeIssuer::builder()
    .ca(LETS_ENCRYPT_STAGING) // 开发阶段使用测试环境!
    .email("admin@example.com")
    .agreed(true)
    .storage(storage.clone())
    .build();

let config = Config::builder()
    .storage(storage)
    .issuers(vec![Arc::new(issuer)])
    .build();

DNS-01 验证(通配符证书)

DNS-01 验证是获取通配符证书的唯一方式,即使服务器不可公网访问也能正常工作。

use std::sync::Arc;
use certon::{AcmeIssuer, Dns01Solver, DnsProvider};

// 为您的 DNS 服务(Cloudflare、Route53 等)实现 DnsProvider
let dns_solver = Arc::new(Dns01Solver::new(
    Box::new(my_dns_provider),
));

let issuer = AcmeIssuer::builder()
    .dns01_solver(dns_solver)
    .email("admin@example.com")
    .agreed(true)
    .storage(storage.clone())
    .build();

// 现在可以获取通配符证书:
let domains = vec!["*.example.com".into()];

实现 DNS 提供者需要实现 DnsProvider trait:

use async_trait::async_trait;
use certon::{DnsProvider, Result};

struct MyDnsProvider { /* ... */ }

#[async_trait]
impl DnsProvider for MyDnsProvider {
    async fn set_record(
        &self, zone: &str, name: &str, value: &str, ttl: u32,
    ) -> Result<()> {
        // 通过 DNS 提供者的 API 创建 TXT 记录
        Ok(())
    }

    async fn delete_record(
        &self, zone: &str, name: &str, value: &str,
    ) -> Result<()> {
        // 删除 TXT 记录
        Ok(())
    }
}

ZeroSSL

ZeroSSL 通过 ACME 协议提供免费证书,需要外部账户绑定(EAB)。Certon 使用您的 ZeroSSL API 密钥自动处理 EAB 配置。

use std::sync::Arc;
use certon::{Config, FileStorage, Storage, ZeroSslIssuer};

let storage: Arc<dyn Storage> = Arc::new(FileStorage::default());

let issuer = ZeroSslIssuer::builder()
    .api_key("your-zerossl-api-key")
    .email("admin@example.com")
    .storage(storage.clone())
    .build()
    .await?;

let config = Config::builder()
    .storage(storage)
    .issuers(vec![Arc::new(issuer)])
    .build();

自定义存储后端

实现 Storage trait 即可使用数据库、Redis、S3 或任何其他持久化层。所有共享同一存储的实例被视为同一集群的成员。

use async_trait::async_trait;
use certon::storage::{Storage, KeyInfo};
use certon::Result;

struct MyDatabaseStorage { /* ... */ }

#[async_trait]
impl Storage for MyDatabaseStorage {
    async fn store(&self, key: &str, value: &[u8]) -> Result<()> {
        // 写入数据库
        Ok(())
    }

    async fn load(&self, key: &str) -> Result<Vec<u8>> {
        // 从数据库读取
        todo!()
    }

    async fn delete(&self, key: &str) -> Result<()> {
        // 从数据库删除
        Ok(())
    }

    async fn exists(&self, key: &str) -> Result<bool> {
        // 检查键是否存在
        todo!()
    }

    async fn list(&self, path: &str, recursive: bool) -> Result<Vec<String>> {
        // 列出指定前缀下的键
        todo!()
    }

    async fn stat(&self, key: &str) -> Result<KeyInfo> {
        // 返回键的元数据
        todo!()
    }

    async fn lock(&self, name: &str) -> Result<()> {
        // 获取分布式锁
        Ok(())
    }

    async fn unlock(&self, name: &str) -> Result<()> {
        // 释放分布式锁
        Ok(())
    }
}

按需 TLS

按需 TLS 在 TLS 握手时为未预配置的域名获取证书。务必通过白名单或决策函数进行限制,以防止滥用。

use std::collections::HashSet;
use std::sync::Arc;
use certon::OnDemandConfig;

let on_demand = Arc::new(OnDemandConfig {
    host_allowlist: Some(HashSet::from([
        "a.example.com".into(),
        "b.example.com".into(),
    ])),
    decision_func: None,
    rate_limit: None,
    obtain_func: None, // 由 Config 内部自动连接
});

let config = Config::builder()
    .storage(storage)
    .on_demand(on_demand)
    .build();

事件回调

订阅证书生命周期事件,用于日志记录、监控或告警:

use std::sync::Arc;

let config = Config::builder()
    .storage(storage)
    .on_event(Arc::new(|event: &str, data: &serde_json::Value| {
        println!("证书事件: {} {:?}", event, data);
    }))
    .build();

支持的事件包括:

  • cert_obtaining -- 正在开始获取证书
  • cert_obtained -- 证书获取成功
  • cert_renewed -- 证书续期成功
  • cert_failed -- 证书获取或续期失败
  • cert_revoked -- 证书已被吊销
  • cached_managed_cert -- 托管证书已从存储加载到缓存

架构概览

                    +-----------+
                    |  Config   |  中心协调器
                    +-----+-----+
                          |
          +---------------+---------------+
          |               |               |
    +-----v-----+   +----v----+   +------v------+
    |  Issuer   |   |  Cache  |   |   Storage   |
    +-----------+   +---------+   +-------------+
          |               |               |
    +-----v-----+   +----v--------+  +---v-----------+
    | AcmeIssuer|   | CertResolver|  | FileStorage   |
    | ZeroSSL   |   | (rustls)    |  | (或自定义)     |
    +-----------+   +-------------+  +---------------+
          |
    +-----v-------+
    |  AcmeClient  |----> ACME CA(Let's Encrypt、ZeroSSL 等)
    +--------------+

    +------------------+
    | start_maintenance| ---> 续期循环(每 10 分钟)
    |                  | ---> OCSP 刷新循环(每 1 小时)
    +------------------+

核心组件:

组件 职责
Config 中心入口;协调获取、续期、吊销和缓存操作
AcmeIssuer / ZeroSslIssuer 实现 Issuer trait;驱动 ACME 协议流程
AcmeClient 底层 ACME HTTP 客户端(目录、nonce、JWS 签名、订单管理)
CertCache 内存证书存储,按域名索引(支持通配符匹配)
CertResolver 实现 rustls::server::ResolvesServerCert;在 TLS 握手时解析证书
Storage / FileStorage 持久化键值存储,支持分布式锁
start_maintenance 后台 tokio 任务,用于自动续期和 OCSP 刷新

ACME 验证方式

ACME 协议通过验证(challenge)来验证域名所有权。Certon 支持全部三种标准验证类型。

HTTP-01 验证

HTTP-01 验证通过在 http://<domain>/.well-known/acme-challenge/<token>80 端口)提供特定令牌来证明域名控制权。

Certon 的 Http01Solver 启动一个轻量 HTTP 服务器,自动提供验证响应。服务器在验证开始时启动,完成后停止。

use certon::Http01Solver;

let solver = Http01Solver::new(80); // 或 Http01Solver::default()

要求: 80 端口必须可从公网访问(直接或通过端口转发)。

TLS-ALPN-01 验证

TLS-ALPN-01 验证通过在 443 端口的 TLS 握手中展示包含特殊 acmeIdentifier 扩展的自签名证书来证明域名控制权,通过 acme-tls/1 ALPN 协议协商。

Certon 的 TlsAlpn01Solver 生成临时验证证书并在临时 TLS 监听器上提供服务。

use certon::TlsAlpn01Solver;

let solver = TlsAlpn01Solver::new(443); // 或 TlsAlpn01Solver::default()

要求: 443 端口必须可从公网访问。这通常是最方便的验证类型,因为它使用与生产 TLS 服务器相同的端口。

DNS-01 验证

DNS-01 验证通过在 _acme-challenge.<domain> 创建特定的 TXT 记录来证明域名控制权。这是唯一支持通配符证书的验证类型,且不要求服务器可从公网访问。

Certon 的 Dns01Solver 接受一个 DnsProvider 实现,通过 DNS 提供者的 API 创建和删除 TXT 记录。它会自动等待 DNS 传播完成后再通知 CA。

use certon::Dns01Solver;

let solver = Dns01Solver::new(Box::new(my_cloudflare_provider));
// 使用自定义传播设置:
let solver = Dns01Solver::with_timeouts(
    Box::new(my_provider),
    std::time::Duration::from_secs(180),  // 传播超时
    std::time::Duration::from_secs(5),    // 检查间隔
);

要求: 提供 API 的 DNS 提供者,以及 DnsProvider trait 的实现。

存储

Certon 需要持久化存储来保存证书、私钥、元数据、OCSP 装订和锁文件。存储通过 Storage trait 抽象,可以轻松切换后端。

默认:FileStorage

内置的 FileStorage 将所有数据存储在本地文件系统上,具有以下特性:

  • 原子写入 -- 数据先写入临时文件,再原子重命名到目标位置,防止部分读取
  • 分布式锁 -- 锁文件包含 JSON 时间戳,由后台保活任务每 5 秒刷新;超过 10 秒的过期锁会被自动清除
  • 平台感知路径 -- 默认使用 ~/.local/share/certon(Linux)、~/Library/Application Support/certon(macOS)或 %APPDATA%/certon(Windows)

集群: 共享同一存储后端的实例被视为同一集群。对于 FileStorage,挂载共享网络文件夹即可。对于自定义后端,确保所有实例指向同一数据库/服务。

存储目录结构:

<root>/
  certificates/<issuer>/<domain>/
    <domain>.crt    -- PEM 证书链
    <domain>.key    -- PEM 私钥
    <domain>.json   -- 元数据(SANs、颁发者信息)
  ocsp/
    <domain>-<hash> -- 缓存的 OCSP 响应
  acme/<issuer>/
    users/<email>/  -- ACME 账户数据
  locks/
    <name>.lock     -- 分布式锁文件

证书维护

Certon 通过 certon::start_maintenance() 运行后台维护,它会生成一个 tokio 任务执行两个定期循环:

  1. 续期循环(默认每 10 分钟) -- 遍历缓存中所有托管证书,续期进入续期窗口的证书(默认在证书有效期剩余不足 1/3 时触发续期)

  2. OCSP 刷新循环(默认每 1 小时) -- 为所有缓存的证书获取最新的 OCSP 响应并持久化到存储

两个循环都遵循 CertCache::stop() 信号以实现优雅关闭。

let config = Config::builder().storage(storage).build();

// 启动后台维护
let handle = certon::start_maintenance(&config);

// ... 稍后优雅停止:
// config.cache.stop().await;
// handle.await;

按需 TLS(详细说明)

按需 TLS 在 TLS 握手时为未预配置的域名获取证书。当收到包含未知 SNI 值的 ClientHello 时,CertResolver 可以触发后台证书获取,使同一域名的后续握手成功。

这个功能强大但必须谨慎限制以防止滥用:

限制方式 说明
host_allowlist 允许的主机名 HashSet<String>(不区分大小写)
decision_func 动态允许/拒绝逻辑的闭包 Fn(&str) -> bool
rate_limit 可选的 RateLimiter 限制颁发速率

如果 decision_funchost_allowlist 均未配置,按需颁发将被拒绝(默认安全),以防止无限制的证书请求。

由于 rustls::server::ResolvesServerCert::resolve 是同步方法,按需获取会在后台生成任务。当前握手会收到默认证书(或 None);同一域名的下次握手将在缓存中找到证书。

API 参考

完整的 API 文档可在 docs.rs 查看。

关键入口点:

开发和测试

Let's Encrypt 对其生产端点施加严格的速率限制。开发阶段请始终使用测试端点:

use certon::LETS_ENCRYPT_STAGING;

let issuer = AcmeIssuer::builder()
    .ca(LETS_ENCRYPT_STAGING)
    .email("dev@example.com")
    .agreed(true)
    .storage(storage.clone())
    .build();

测试证书不受公共信任,但速率限制宽松得多。

许可证

Certon 采用 Apache 许可证 2.0 许可。