21个版本 (稳定版)
5.0.0-rc.2 | 2024年6月1日 |
---|---|
5.0.0-alpha.3 | 2023年12月15日 |
4.0.0 | 2023年11月26日 |
3.0.0 | 2023年11月25日 |
1.5.0 | 2023年3月30日 |
#357 in 密码学
每月42次下载
170KB
3.5K SLoC
另一个证书管理引擎
YACME是ACME协议的实现。
功能
YACME支持自定义证书、CA和ACME服务器。它支持HTTP-01和DNS-01授权挑战。目前不支持TLS-ALPN-01挑战,但未来可能会支持。
YACME也不支持证书吊销或账户证书更新。
YACME使用jaws提供JWT支持,因此支持该crate支持的所有签名算法。
入门指南
使用高级服务接口,您可以连接到letsencrypt(或者实际上是ACME提供商)并颁发证书
(有关此示例的更多详细信息,请参阅letsencrypt-pebble.rs
,下面的代码不旨在编译)
//! Run a certificate issue process via the pebble local ACME server
//!
//! *Prerequisite*: Start the pebble server via docker-compose. It is defined in the
//! pebble/ directory, or available at https://github.com/letsencrypt/pebble/
//!
//! This example handles the challenge using pebble's challenge server. In the real world,
//! you would have to implement this yourself.
use std::sync::Arc;
use pkcs8::DecodePrivateKey;
use signature::rand_core::OsRng;
use yacme::schema::authorizations::AuthorizationStatus;
use yacme::schema::challenges::{ChallengeKind, Http01Challenge};
const PRIVATE_KEY_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/reference-keys/ec-p255.pem");
const PEBBLE_ROOT_CA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/pebble/pebble.minica.pem");
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
tracing::debug!("Loading root certificate from {PEBBLE_ROOT_CA}");
let cert = reqwest::Certificate::from_pem(&std::fs::read(PEBBLE_ROOT_CA)?)?;
let provider = yacme::service::Provider::build()
.directory_url(yacme::service::provider::PEBBLE.parse().unwrap())
.add_root_certificate(cert)
.timeout(std::time::Duration::from_secs(30))
.build()
.await?;
tracing::info!("Loading private key from {PRIVATE_KEY_PATH:?}");
let key = Arc::new(ecdsa::SigningKey::from_pkcs8_pem(
&std::fs::read_to_string(PRIVATE_KEY_PATH)?,
)?);
// Step 1: Get an account
tracing::info!("Requesting account");
let account = provider
.account(key)
.add_contact_email("[email protected]")?
.agree_to_terms_of_service()
.create()
.await?;
tracing::trace!("Account: \n{account:#?}");
tracing::info!("Requesting order");
let mut order = account
.order()
.dns("www.example.test")
.dns("internal.example.test")
.create()
.await?;
tracing::trace!("Order: \n{order:#?}");
tracing::info!("Completing Authorizations");
for auth in order.authorizations().await?.iter_mut() {
tracing::info!("Authorizing {:?}", auth.identifier());
tracing::trace!("Authorization: \n{auth:#?}");
if !matches!(auth.data().status, AuthorizationStatus::Pending) {
continue;
}
let mut chall = auth
.challenge(&ChallengeKind::Http01)
.ok_or("No http01 challenge provided")?;
let inner = chall.http01().unwrap();
http01_challenge_response(&inner, &account.key()).await?;
chall.ready().await?;
auth.finalize().await?;
tracing::info!("Authorization finalized");
}
tracing::info!("Finalizing order");
tracing::debug!("Generating random certificate key");
let certificate_key = Arc::new(ecdsa::SigningKey::<p256::NistP256>::random(&mut OsRng));
let cert = order
.finalize_and_download::<ecdsa::SigningKey::<p256::NistP256>, ecdsa::der::Signature<p256::NistP256>>(&certificate_key)
.await?;
println!("{}", cert.to_pem_documents()?.join(""));
Ok(())
}
// This method is specific to pebble - you would set up your challenge respons in an appropriate fashion
async fn http01_challenge_response(
challenge: &Http01Challenge,
key: &ecdsa::SigningKey<p256::NistP256>,
) -> Result<(), reqwest::Error> {
#[derive(Debug, serde::Serialize)]
struct Http01ChallengeSetup {
token: String,
content: String,
}
let chall_setup = Http01ChallengeSetup {
token: challenge.token().into(),
content: (*challenge.authorization(key)).to_owned(),
};
tracing::trace!(
"Challenge Setup:\n{}",
serde_json::to_string(&chall_setup).unwrap()
);
let resp = reqwest::Client::new()
.post("https://127.0.0.1:8055/add-http01")
.json(&chall_setup)
.send()
.await?;
match resp.error_for_status_ref() {
Ok(_) => {}
Err(_) => {
eprintln!("Request:");
eprintln!("{}", serde_json::to_string(&chall_setup).unwrap());
eprintln!("ERROR:");
eprintln!("Status: {:?}", resp.status().canonical_reason());
eprintln!("{}", resp.text().await?);
panic!("Failed to update challenge server");
}
}
Ok(())
}
导航指南
YACME分为几个API级别
service
是高级API,提供了一种简单的接口来颁发证书。schema
提供了实现单个ACME端点的所有数据结构。protocol
提供了ACME服务器使用的JWT协议。cert
提供了对X.509证书的支持。
目标
这是一个为了获得一个我喜欢的Rust ACME客户端,并更多地了解ACME和互联网协议密码学而进行的yak-shave项目。
本项目的目标设计是
- 无OpenSSL依赖。这里的密码学应该是纯Rust。
- 模块化和可重用。这不是一个有偏见的命令行工具来帮助您入门。相反,这个库希望能够轻松集成到现有项目中,如基于hyper的项目。
- 易于扩展:添加新的签名算法、挑战类型和其他扩展(假设它们由纯Rust库支持)应该相对简单。
- 运行时灵活。可以更换签名算法,而无需更改调用ACME服务的代码中的类型。
这可能不适合生产使用,但它基于RustCrypto的工作,他们制作了很好的东西。不要责怪他们,责怪我吧!
依赖关系
约14-27MB
约443K SLoC