async_acme/
rustls_helper.rs

1/*! utilities to help with rustls.
2
3```
4use async_acme::{
5    acme::LETS_ENCRYPT_STAGING_DIRECTORY,
6    rustls_helper::order,
7};
8async fn get_new_cert(){
9    let cache = "./cachedir/".to_string();
10    let new_cert = order(
11        |_sni, _cert| Ok(()),
12        LETS_ENCRYPT_STAGING_DIRECTORY,
13        &vec!["example.com".to_string()],
14        Some(&cache),
15        &vec!["mailto:admin@example.com".to_string()],
16    )
17    .await
18    .unwrap();
19}
20```
21
22*/
23
24use futures_util::future::try_join_all;
25use rustls::sign::CertifiedKey;
26use std::time::Duration;
27use thiserror::Error;
28
29use crate::{
30    acme::{Account, AcmeError, Auth, Directory, Identifier, Order},
31    cache::AcmeCache,
32    crypto::{gen_acme_cert, get_cert_duration_left, CertBuilder},
33};
34
35#[cfg(feature = "use_async_std")]
36use async_std::task::sleep;
37#[cfg(feature = "use_tokio")]
38use tokio::time::sleep;
39
40/// Obtain a signed certificate from the ACME provider at `directory_url` for the DNS `domains`.
41///
42/// The secret for the challenge is passed as a ready to use certificate to `set_auth_key(domain, certificate)?`.
43/// This certificate has to be presented upon a TLS request with ACME ALPN and SNI for that domain.
44///
45/// Provide your email in `contact` in the form *mailto:admin@example.com* to receive warnings regarding your certificate.
46/// Set a `cache` to remember your account.
47pub async fn order<C, F>(
48    set_auth_key: F,
49    directory_url: &str,
50    domains: &[String],
51    cache: Option<&C>,
52    contact: &[String],
53) -> Result<CertifiedKey, OrderError>
54where
55    C: AcmeCache,
56    F: Fn(String, CertifiedKey) -> Result<(), AcmeError>,
57{
58    let directory = Directory::discover(directory_url).await?;
59    let account = Account::load_or_create(directory, cache, contact).await?;
60
61    let (c, key_pem, cert_pem) = drive_order(set_auth_key, domains.to_vec(), account).await?;
62
63    if let Some(dir) = cache {
64        dir.write_certificate(domains, directory_url, &key_pem, &cert_pem)
65            .await
66            .map_err(AcmeError::cache)?;
67    };
68    Ok(c)
69}
70
71/// Obtain a signed certificate for the DNS `domains` using `account`.
72///
73/// The secret for the challenge is passed as a ready to use certificate to `set_auth_key(domain, certificate)?`.
74/// This certificate has to be presented upon a TLS request with ACME ALPN and SNI for that domain.
75///
76/// Returns the signed Certificate, its private key as pem, and the certificate as pem again
77pub async fn drive_order<F>(
78    set_auth_key: F,
79    domains: Vec<String>,
80    account: Account,
81) -> Result<(CertifiedKey, String, String), OrderError>
82where
83    F: Fn(String, CertifiedKey) -> Result<(), AcmeError>,
84{
85    let cert = CertBuilder::gen_new(domains.clone())?;
86    let mut order = account.new_order(domains).await?;
87    loop {
88        order = match order {
89            Order::Pending {
90                authorizations,
91                finalize,
92            } => {
93                let auth_futures = authorizations
94                    .iter()
95                    .map(|url| authorize(&set_auth_key, &account, url));
96                try_join_all(auth_futures).await?;
97                log::info!("completed all authorizations");
98                Order::Ready { finalize }
99            }
100            Order::Ready { finalize } => {
101                log::info!("sending csr");
102                let csr = cert.get_csr()?;
103                account.send_csr(finalize, csr).await?
104            }
105            Order::Valid { certificate } => {
106                log::info!("download certificate");
107                let acme_cert_pem = account.obtain_certificate(certificate).await?;
108                let rd = acme_cert_pem.as_bytes();
109                let pkey_pem = cert.private_key_as_pem_pkcs8();
110                let cert_key = cert.sign(rd).map_err(|_| {
111                    AcmeError::Io(std::io::Error::new(
112                        std::io::ErrorKind::InvalidData,
113                        "could not parse certificate",
114                    ))
115                })?;
116                return Ok((cert_key, pkey_pem, acme_cert_pem));
117            }
118            Order::Invalid => return Err(OrderError::BadOrder(order)),
119        }
120    }
121}
122async fn authorize<F>(set_auth_key: &F, account: &Account, url: &str) -> Result<(), OrderError>
123where
124    F: Fn(String, CertifiedKey) -> Result<(), AcmeError>,
125{
126    let (domain, challenge_url) = match account.check_auth(url).await? {
127        Auth::Pending {
128            identifier,
129            challenges,
130        } => {
131            let Identifier::Dns(domain) = identifier;
132            log::info!("trigger challenge for {}", &domain);
133            let (challenge, key_auth) = account.tls_alpn_01(&challenges)?;
134            let auth_key = gen_acme_cert(vec![domain.clone()], key_auth.as_ref())?;
135            set_auth_key(domain.clone(), auth_key)?;
136            account.trigger_challenge(&challenge.url).await?;
137            (domain, challenge.url.clone())
138        }
139        Auth::Valid => return Ok(()),
140        auth => return Err(OrderError::BadAuth(auth)),
141    };
142    for i in 0u8..5 {
143        sleep(Duration::from_secs(1u64 << i)).await;
144        match account.check_auth(url).await? {
145            Auth::Pending { .. } => {
146                log::info!("authorization for {} still pending", &domain);
147                account.trigger_challenge(&challenge_url).await?
148            }
149            Auth::Valid => return Ok(()),
150            auth => return Err(OrderError::BadAuth(auth)),
151        }
152    }
153    Err(OrderError::TooManyAttemptsAuth(domain))
154}
155
156/// get the duration until the next ACME refresh should be done
157pub fn duration_until_renewal_attempt(cert_key: Option<&CertifiedKey>, err_cnt: usize) -> Duration {
158    let valid_until = cert_key
159        .and_then(|cert_key| cert_key.cert.first())
160        .and_then(|cert| get_cert_duration_left(cert).ok())
161        .unwrap_or_default();
162
163    let wait_secs = valid_until / 2;
164    match err_cnt {
165        0 => wait_secs,
166        err_cnt => wait_secs.max(Duration::from_secs(1 << err_cnt)),
167    }
168}
169
170#[derive(Error, Debug)]
171pub enum OrderError {
172    #[error("acme error: {0}")]
173    Acme(#[from] AcmeError),
174    #[error("certificate generation error: {0}")]
175    Rcgen(#[from] rcgen::Error),
176    #[error("bad order object: {0:?}")]
177    BadOrder(Order),
178    #[error("bad auth object: {0:?}")]
179    BadAuth(Auth),
180    #[error("authorization for {0} failed too many times")]
181    TooManyAttemptsAuth(String),
182}