From b488f2ac8abfd978786e1e5084777f93808dca96 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Thu, 17 Feb 2022 05:07:03 -0800 Subject: [PATCH] Add some documentation Signed-off-by: Erik Hollensbe --- src/acme/ca.rs | 21 +++++++++++++++++++++ src/acme/challenge.rs | 17 +++++++++++++++-- src/acme/dns.rs | 3 +++ src/acme/mod.rs | 9 +++++++++ src/lib.rs | 21 ++++++++++++++++++++- 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/acme/ca.rs b/src/acme/ca.rs index 955cae9..7c5b5b2 100644 --- a/src/acme/ca.rs +++ b/src/acme/ca.rs @@ -26,6 +26,9 @@ pub(crate) fn st_to_asn1(time: SystemTime) -> Result { ) } +/// CA defines a certificate authority in the standard sense of the word; it is used to sign +/// certificate signing requests and return them as fully functional certificates. To create one, +/// use the ::new constructor. #[derive(Clone, Debug)] pub struct CA { certificate: X509, @@ -33,6 +36,7 @@ pub struct CA { } impl CA { + /// new constructs a new certificate authority with a X.509 certificate and private key. pub fn new(certificate: X509, private_key: PKey) -> Self { Self { certificate, @@ -40,14 +44,18 @@ impl CA { } } + /// returns the certificate pub fn certificate(self) -> X509 { self.certificate } + /// returns the private key pub fn private_key(self) -> PKey { self.private_key } + /// signs a CSR with the CA's private key. The not_before and not_after parameters can be used + /// to control its lifetime. pub fn generate_and_sign_cert( &self, req: X509Req, @@ -115,6 +123,8 @@ impl CA { Ok(builder.build()) } + /// new_test_ca is a convenience function for creating a quick and dirty CA for use in tests + /// and demo applications (such as the examples). pub fn new_test_ca() -> Result { let mut builder = X509::builder()?; @@ -186,15 +196,21 @@ impl CA { } } +/// CACollector is an async observer which waits for a CA to arrive, and fosters the creation of +/// signed CSRs as certificates. This allows for the rotation of CA certificates, or delayed +/// loading, without loss of functionality due to race conditions. Please see the `acmed` example for usage. #[derive(Clone, Debug)] pub struct CACollector { poll_interval: Duration, ca: SharedCA, } +/// SharedCA is a simple type for managing the locking around a CA. type SharedCA = Arc>>; impl CACollector { + /// new is a constructor; the duration provided determines how often the loop will awake and + /// process a CA injection. pub fn new(poll_interval: Duration) -> Self { Self { poll_interval, @@ -202,10 +218,13 @@ impl CACollector { } } + /// returns the CA as a SharedCA. pub fn ca(self) -> SharedCA { self.ca.clone() } + /// majority of callers will use this function to collect the CA. It takes a closure which + /// accepts a CA and returns it to this function so that it can overwrite the previous CA. pub async fn spawn_collector(&mut self, f: F) where F: Fn() -> Result, @@ -221,6 +240,8 @@ impl CACollector { } } + /// similar to CA::generate_and_sign_cert, this signs the CSR through the SharedCA provided by + /// the collector. pub async fn sign( self, req: X509Req, diff --git a/src/acme/challenge.rs b/src/acme/challenge.rs index 3df5882..561b387 100644 --- a/src/acme/challenge.rs +++ b/src/acme/challenge.rs @@ -14,9 +14,13 @@ use super::handlers::order::OrderStatus; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(into = "String")] +/// ChallengeType is an enum describing the challenge types coyote supports. Currently tls-alpn is +/// unsupported. pub enum ChallengeType { - DNS01, // dns-01 challenge type - HTTP01, // http-01 challenge type + /// dns-01 challenge type + DNS01, + /// http-01 challenge type + HTTP01, } impl TryFrom<&str> for ChallengeType { @@ -47,12 +51,16 @@ impl ChallengeType { } #[derive(Clone)] +/// Challenger is an async supervisor used to perform challenges on demand. This is a simple +/// monitored queue with expiration applied at every loop iteration. pub struct Challenger { list: Arc>>, expiration: Option, } impl Challenger { + /// Construct a new challenger; challenges will last as long as `expiriation` is set to, or + /// forever if Option::None. pub fn new(expiration: Option) -> Self { Self { list: Arc::new(Mutex::new(HashMap::new())), @@ -64,6 +72,9 @@ impl Challenger { self.list.lock().await.insert(c.reference.clone(), c); } + /// tick should be called in a loop in its own async routine with an interval between + /// iterations. This performs each challenge in the queue and invalidates any expired + /// challenges. To commit to storage, call reconcile. pub async fn tick(&self, ticker: T) where T: Fn(Challenge) -> Option<()>, @@ -118,6 +129,8 @@ impl Challenger { } } + /// reconcile should be called after tick. This actually commits the challenge results to the + /// backing storage. pub async fn reconcile(&self, db: Postgres) -> Result<(), SaveError> { let mut lock = self.list.lock().await; let mut db_lock = db.client().await?; diff --git a/src/acme/dns.rs b/src/acme/dns.rs index 71e56b3..ae8624b 100644 --- a/src/acme/dns.rs +++ b/src/acme/dns.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use trust_dns_client::rr::Name; #[derive(Debug, Clone, PartialEq)] +/// DNSName is used to provide a serde interface to DNS names. It is not frequently consumed by +/// external consumers. pub struct DNSName(pub(crate) Name); impl DNSName { @@ -15,6 +17,7 @@ impl DNSName { } } +/// serde codec implementation pub struct DNSNameVisitor; impl<'de> Visitor<'de> for DNSNameVisitor { diff --git a/src/acme/mod.rs b/src/acme/mod.rs index 45102b5..1a43547 100644 --- a/src/acme/mod.rs +++ b/src/acme/mod.rs @@ -1,7 +1,12 @@ +/// Certificate Authority functionality pub mod ca; +/// Challenge management, including supervisory handlers. pub mod challenge; +/// Types for managing DNS records pub mod dns; +/// ACME HTTP handlers pub mod handlers; +/// ACME JOSE implementation pub mod jose; use std::{collections::HashSet, convert::TryFrom, sync::Arc}; @@ -90,6 +95,9 @@ impl ACMEIdentifier { } #[async_trait] +/// NonceValidator is a storage trait that controls the generation and validation of nonces, used +/// heavily in ACME and especially in the `Replay-Nonce` HTTP header present in all calls, and the +/// `nonce` field in ACME protected headers. pub trait NonceValidator { /// This function must mutate the underlying storage to prune the nonce it's validating after a /// successful fetch. One may use ACMEValidationError::NonceFetchError to specify errors with @@ -134,6 +142,7 @@ impl NonceValidator for SetValidator { } #[derive(Clone)] +/// Defines a PostgreSQL-backed nonce validator pub struct PostgresNonceValidator(crate::models::Postgres); impl PostgresNonceValidator { diff --git a/src/lib.rs b/src/lib.rs index 7884a54..1b849fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,25 @@ #![allow(dead_code)] +//! Coyote lets you make ACME servers, which are not guaranteed to not explode in +//! your face. You have to code that out yourself. +//! +//! coyote aims to solve a few problems (not all of these are solved yet; see "Task List" below): +//! +//! - Provide ACME with backing storage you prefer to use, by way of Rust's traits for storage implementation. +//! - Provide ACME in non-conforming scenarios (e.g., behind corporate firewalls) +//! - Provide ACME services with hooks into the validation system, so you can implement validations however you feel like. +//! - It's a library; make it as big or as small as you like. No need for multiple implementations. +//! - A FOSS alternative to the letsencrypt canonical implementation that is _also_ tested against LE's test suite. +//! +//! `acmed` comes as an example with coyote; it is a complete canonical implementation against PostgreSQL for backing storage. It (deliberately) allows all challenges through and is not meant for production usage. +//! +//! `coyote` is intended to let you build an ACME service without using `acmed` itself, leveraging the traits and tools available in this library for scaffolding. For example, work to implement a Redis based nonce validation system would just be a trait implementation, even though it is not available in this library. +//! + +/// Core ACME implementation, including HTTP handlers, JOSE implementation and plenty of crypto pub mod acme; +/// Errors and conversions between different Error types pub mod errors; +/// Database types and traits pub mod models; -pub mod test; +pub(crate) mod test; pub(crate) mod util;