instant_acme/
order.rs

1use std::borrow::Cow;
2use std::ops::{ControlFlow, Deref};
3use std::sync::Arc;
4use std::time::{Duration, Instant, SystemTime};
5use std::{fmt, slice};
6
7use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
8#[cfg(feature = "rcgen")]
9use rcgen::{CertificateParams, DistinguishedName, KeyPair};
10use serde::Serialize;
11use tokio::time::sleep;
12
13use crate::account::AccountInner;
14use crate::types::{
15    Authorization, AuthorizationState, AuthorizationStatus, AuthorizedIdentifier, Challenge,
16    ChallengeType, DeviceAttestation, Empty, FinalizeRequest, OrderState, OrderStatus, Problem,
17};
18use crate::{ChallengeStatus, Error, Key, crypto, nonce_from_response, retry_after};
19
20/// An ACME order as described in RFC 8555 (section 7.1.3)
21///
22/// An order is created from an [`Account`][crate::Account] by calling
23/// [`Account::new_order()`][crate::Account::new_order()]. The `Order` type represents the stable
24/// identity of an order, while the [`Order::state()`] method gives you access to the current
25/// state of the order according to the server.
26///
27/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3>
28pub struct Order {
29    pub(crate) account: Arc<AccountInner>,
30    pub(crate) nonce: Option<String>,
31    pub(crate) retry_after: Option<SystemTime>,
32    pub(crate) url: String,
33    pub(crate) state: OrderState,
34}
35
36impl Order {
37    /// Retrieve the authorizations for this order
38    ///
39    /// An order contains one authorization to complete per identifier in the order.
40    /// After creating an order, you'll need to retrieve the authorizations so that
41    /// you can set up a challenge response for each authorization.
42    ///
43    /// This method will retrieve the authorizations attached to this order if they have not
44    /// been retrieved yet. If you have already consumed the stream generated by this method
45    /// before, processing it again will not involve any network activity.
46    pub fn authorizations(&mut self) -> Authorizations<'_> {
47        Authorizations {
48            inner: AuthStream {
49                iter: self.state.authorizations.iter_mut(),
50                nonce: &mut self.nonce,
51                account: &self.account,
52            },
53        }
54    }
55
56    /// Generate a Certificate Signing Request for the order's identifiers and request finalization
57    ///
58    /// Uses the rcgen crate to generate a Certificate Signing Request (CSR) converting the order's
59    /// identifiers to Subject Alternative Names, then calls [`Order::finalize_csr()`] with it.
60    /// Returns the generated private key, serialized as PEM.
61    ///
62    /// After this succeeds, call [`Order::certificate()`] to retrieve the certificate chain once
63    /// the order is in the appropriate state.
64    #[cfg(feature = "rcgen")]
65    pub async fn finalize(&mut self) -> Result<String, Error> {
66        let mut names = Vec::with_capacity(self.state.authorizations.len());
67        let mut identifiers = self.identifiers();
68        while let Some(result) = identifiers.next().await {
69            names.push(result?.to_string());
70        }
71
72        let mut params = CertificateParams::new(names).map_err(Error::from_rcgen)?;
73        params.distinguished_name = DistinguishedName::new();
74        let private_key = KeyPair::generate().map_err(Error::from_rcgen)?;
75        let csr = params
76            .serialize_request(&private_key)
77            .map_err(Error::from_rcgen)?;
78
79        self.finalize_csr(csr.der()).await?;
80        Ok(private_key.serialize_pem())
81    }
82
83    /// Request a certificate from the given Certificate Signing Request (CSR)
84    ///
85    /// `csr_der` contains the CSR representation serialized in DER encoding. If you don't need
86    /// custom certificate parameters, [`Order::finalize()`] can generate the CSR for you.
87    ///
88    /// After this succeeds, call [`Order::certificate()`] to retrieve the certificate chain once
89    /// the order is in the appropriate state.
90    pub async fn finalize_csr(&mut self, csr_der: &[u8]) -> Result<(), Error> {
91        let rsp = self
92            .account
93            .post(
94                Some(&FinalizeRequest::new(csr_der)),
95                self.nonce.take(),
96                &self.state.finalize,
97            )
98            .await?;
99
100        self.nonce = nonce_from_response(&rsp);
101        self.state = Problem::check::<OrderState>(rsp).await?;
102        Ok(())
103    }
104
105    /// Get the certificate for this order
106    ///
107    /// If the cached order state is in `ready` or `processing` state, this will poll the server
108    /// for the latest state. If the order is still in `processing` state after that, this will
109    /// return `Ok(None)`. If the order is in `valid` state, this will attempt to retrieve
110    /// the certificate from the server and return it as a `String`. If the order contains
111    /// an error or ends up in any state other than `valid` or `processing`, return an error.
112    pub async fn certificate(&mut self) -> Result<Option<String>, Error> {
113        if matches!(self.state.status, OrderStatus::Processing) {
114            let rsp = self
115                .account
116                .post(None::<&Empty>, self.nonce.take(), &self.url)
117                .await?;
118            self.nonce = nonce_from_response(&rsp);
119            self.state = Problem::check::<OrderState>(rsp).await?;
120        }
121
122        if let Some(error) = &self.state.error {
123            return Err(Error::Api(error.clone()));
124        } else if self.state.status == OrderStatus::Processing {
125            return Ok(None);
126        } else if self.state.status != OrderStatus::Valid {
127            return Err(Error::Str("invalid order state"));
128        }
129
130        let Some(cert_url) = &self.state.certificate else {
131            return Err(Error::Str("no certificate URL found"));
132        };
133
134        let rsp = self
135            .account
136            .post(None::<&Empty>, self.nonce.take(), cert_url)
137            .await?;
138
139        self.nonce = nonce_from_response(&rsp);
140        let body = Problem::from_response(rsp).await?;
141        Ok(Some(
142            String::from_utf8(body.to_vec())
143                .map_err(|_| "unable to decode certificate as UTF-8")?,
144        ))
145    }
146
147    /// Retrieve the identifiers for this order
148    ///
149    /// This method will retrieve the identifiers attached to the authorizations for this order
150    /// if they have not been retrieved yet. If you have already consumed the stream generated
151    /// by [`Order::authorizations()`], this will not involve any network activity.
152    pub fn identifiers(&mut self) -> Identifiers<'_> {
153        Identifiers {
154            inner: AuthStream {
155                iter: self.state.authorizations.iter_mut(),
156                nonce: &mut self.nonce,
157                account: &self.account,
158            },
159        }
160    }
161
162    /// Poll the order with the given [`RetryPolicy`]
163    ///
164    /// Yields the [`OrderStatus`] immediately if `Ready` or `Invalid`, or yields an
165    /// [`Error::Timeout`] if the [`RetryPolicy::timeout`] has been reached.
166    pub async fn poll_ready(&mut self, retries: &RetryPolicy) -> Result<OrderStatus, Error> {
167        let mut retrying = retries.state();
168        self.retry_after = None;
169        loop {
170            if let ControlFlow::Break(err) = retrying.wait(self.retry_after.take()).await {
171                return Err(err);
172            }
173
174            let state = self.refresh().await?;
175            if let Some(error) = &state.error {
176                return Err(Error::Api(error.clone()));
177            } else if let OrderStatus::Ready | OrderStatus::Invalid = state.status {
178                return Ok(state.status);
179            }
180        }
181    }
182
183    /// Poll the certificate with the given [`RetryPolicy`]
184    ///
185    /// Yields the PEM encoded certificate chain for this order if the order state becomes
186    /// `Valid`. The function keeps polling as long as the order state is `Processing`.
187    /// An error is returned immediately: if the order state is `Invalid`, if polling runs
188    /// into a timeout, or if the ACME CA suggest to retry at a later time.
189    pub async fn poll_certificate(&mut self, retries: &RetryPolicy) -> Result<String, Error> {
190        let mut retrying = retries.state();
191        self.retry_after = None;
192        loop {
193            if let ControlFlow::Break(err) = retrying.wait(self.retry_after.take()).await {
194                return Err(err);
195            }
196
197            let state = self.refresh().await?;
198            if let Some(error) = &state.error {
199                return Err(Error::Api(error.clone()));
200            } else if let OrderStatus::Valid | OrderStatus::Invalid = state.status {
201                return self
202                    .certificate()
203                    .await?
204                    .ok_or(Error::Str("no certificates received from ACME CA"));
205            }
206        }
207    }
208
209    /// Refresh the current state of the order
210    pub async fn refresh(&mut self) -> Result<&OrderState, Error> {
211        let rsp = self
212            .account
213            .post(None::<&Empty>, self.nonce.take(), &self.url)
214            .await?;
215
216        self.nonce = nonce_from_response(&rsp);
217        self.retry_after = retry_after(&rsp);
218        self.state = Problem::check::<OrderState>(rsp).await?;
219        Ok(&self.state)
220    }
221
222    /// Extract the URL and last known state from the `Order`
223    pub fn into_parts(self) -> (String, OrderState) {
224        (self.url, self.state)
225    }
226
227    /// Get the last known state of the order
228    ///
229    /// Call `refresh()` to get the latest state from the server.
230    pub fn state(&mut self) -> &OrderState {
231        &self.state
232    }
233
234    /// Get the URL of the order
235    pub fn url(&self) -> &str {
236        &self.url
237    }
238}
239
240/// An stream-like interface that yields an [`Order`]'s authorizations
241///
242/// Call [`next()`] to get the next authorization in the order. If the order state
243/// does not yet contain the state of the authorization, it will be fetched from the server.
244///
245/// [`next()`]: Authorizations::next()
246pub struct Authorizations<'a> {
247    inner: AuthStream<'a>,
248}
249
250impl Authorizations<'_> {
251    /// Yield the next [`AuthorizationHandle`], fetching its state if we don't have it yet
252    pub async fn next(&mut self) -> Option<Result<AuthorizationHandle<'_>, Error>> {
253        let (url, state) = match self.inner.next().await? {
254            Ok((url, state)) => (url, state),
255            Err(err) => return Some(Err(err)),
256        };
257
258        Some(Ok(AuthorizationHandle {
259            state,
260            url,
261            nonce: self.inner.nonce,
262            account: self.inner.account,
263        }))
264    }
265}
266
267/// An stream-like interface that yields an [`Order`]'s identifiers
268///
269/// Call [`next()`] to get the next authorization in the order. If the order state
270/// does not yet contain the state of the authorization, it will be fetched from the server.
271///
272/// [`next()`]: Identifiers::next()
273pub struct Identifiers<'a> {
274    inner: AuthStream<'a>,
275}
276
277impl<'a> Identifiers<'a> {
278    /// Yield the next [`Identifier`][crate::Identifier], fetching the authorization's state if
279    /// we don't have it yet
280    pub async fn next(&mut self) -> Option<Result<AuthorizedIdentifier<'a>, Error>> {
281        Some(match self.inner.next().await? {
282            Ok((_, state)) => Ok(state.identifier()),
283            Err(err) => Err(err),
284        })
285    }
286}
287
288struct AuthStream<'a> {
289    iter: slice::IterMut<'a, Authorization>,
290    nonce: &'a mut Option<String>,
291    account: &'a AccountInner,
292}
293
294impl<'a> AuthStream<'a> {
295    async fn next(&mut self) -> Option<Result<(&'a str, &'a mut AuthorizationState), Error>> {
296        let authz = self.iter.next()?;
297        if authz.state.is_none() {
298            match self.account.get(self.nonce, &authz.url).await {
299                Ok(state) => authz.state = Some(state),
300                Err(e) => return Some(Err(e)),
301            }
302        }
303
304        // The `unwrap()` here is safe: the code above will either set it to `Some` or yield
305        // an error to the caller if it was `None` upon entering this method. I attempted to
306        // use `Option::insert()` which did not pass the borrow checker for reasons that I
307        // think have to do with the let scope extension that got fixed for 2024 edition.
308        // For now, our MSRV does not allow the use of the new edition.
309        let state = authz.state.as_mut().unwrap();
310        Some(Ok((&authz.url, state)))
311    }
312}
313
314/// An ACME authorization as described in RFC 8555 (section 7.1.4)
315///
316/// Authorizations are retrieved from an associated [`Order`] by calling
317/// [`Order::authorizations()`]. This type dereferences to the underlying
318/// [`AuthorizationState`] for easy access to the authorization's state.
319///
320/// For each authorization, you'll need to:
321///
322/// * Select which [`ChallengeType`] you want to complete
323/// * Call [`AuthorizationHandle::challenge()`] to get a [`ChallengeHandle`]
324/// * Use the `ChallengeHandle` to complete the authorization's challenge
325///
326/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3>
327pub struct AuthorizationHandle<'a> {
328    state: &'a mut AuthorizationState,
329    url: &'a str,
330    nonce: &'a mut Option<String>,
331    account: &'a AccountInner,
332}
333
334impl<'a> AuthorizationHandle<'a> {
335    /// Refresh the current state of the authorization
336    pub async fn refresh(&mut self) -> Result<&AuthorizationState, Error> {
337        let rsp = self
338            .account
339            .post(None::<&Empty>, self.nonce.take(), self.url)
340            .await?;
341
342        *self.nonce = nonce_from_response(&rsp);
343        *self.state = Problem::check::<AuthorizationState>(rsp).await?;
344        Ok(self.state)
345    }
346
347    /// Deactivate a pending or valid authorization
348    ///
349    /// Returns the updated [`AuthorizationState`] if the deactivation was successful.
350    /// If the authorization was not pending or valid, an error is returned.
351    ///
352    /// Once deactivated the authorization and associated challenges can not be updated
353    /// further.
354    ///
355    /// This is useful when you want to cancel a pending authorization attempt you wish
356    /// to abandon, or if you wish to revoke valid authorization for an identifier to
357    /// force future uses of the identifier by the same ACME account to require
358    /// re-verification with fresh authorizations/challenges.
359    pub async fn deactivate(&mut self) -> Result<&AuthorizationState, Error> {
360        if !matches!(
361            self.state.status,
362            AuthorizationStatus::Pending | AuthorizationStatus::Valid
363        ) {
364            return Err(Error::Other("authorization not pending or valid".into()));
365        }
366
367        #[derive(Serialize)]
368        struct DeactivateRequest {
369            status: AuthorizationStatus,
370        }
371
372        let rsp = self
373            .account
374            .post(
375                Some(&DeactivateRequest {
376                    status: AuthorizationStatus::Deactivated,
377                }),
378                self.nonce.take(),
379                self.url,
380            )
381            .await?;
382
383        *self.nonce = nonce_from_response(&rsp);
384        *self.state = Problem::check::<AuthorizationState>(rsp).await?;
385        match self.state.status {
386            AuthorizationStatus::Deactivated => Ok(self.state),
387            _ => Err(Error::Other(
388                "authorization was not deactivated by ACME server".into(),
389            )),
390        }
391    }
392
393    /// Get a [`ChallengeHandle`] for the given `type`
394    ///
395    /// Yields an object to interact with the challenge for the given type, if available.
396    pub fn challenge(&'a mut self, r#type: ChallengeType) -> Option<ChallengeHandle<'a>> {
397        let challenge = self.state.challenges.iter().find(|c| c.r#type == r#type)?;
398        Some(ChallengeHandle {
399            identifier: self.state.identifier(),
400            challenge,
401            nonce: self.nonce,
402            account: self.account,
403        })
404    }
405
406    /// Get the URL of the authorization
407    pub fn url(&self) -> &str {
408        self.url
409    }
410}
411
412impl Deref for AuthorizationHandle<'_> {
413    type Target = AuthorizationState;
414
415    fn deref(&self) -> &Self::Target {
416        self.state
417    }
418}
419
420/// Wrapper type for interacting with a [`Challenge`]'s state
421///
422/// For each challenge, you'll need to:
423///
424/// * Obtain the [`ChallengeHandle::key_authorization()`] for the challenge response
425/// * Set up the challenge response in your infrastructure (details vary by challenge type)
426/// * Call [`ChallengeHandle::set_ready()`] for that challenge after setup is complete
427///
428/// After the challenges have been set to ready, call [`Order::poll_ready()`] to wait until the
429/// order is ready to be finalized (or to learn if it becomes invalid). Once it is ready, call
430/// [`Order::finalize()`] to get the certificate.
431///
432/// Dereferences to the underlying [`Challenge`] for easy access to the challenge's state.
433pub struct ChallengeHandle<'a> {
434    identifier: AuthorizedIdentifier<'a>,
435    challenge: &'a Challenge,
436    nonce: &'a mut Option<String>,
437    account: &'a AccountInner,
438}
439
440impl ChallengeHandle<'_> {
441    /// Notify the server that the given challenge is ready to be completed
442    pub async fn set_ready(&mut self) -> Result<(), Error> {
443        let rsp = self
444            .account
445            .post(Some(&Empty {}), self.nonce.take(), &self.challenge.url)
446            .await?;
447
448        *self.nonce = nonce_from_response(&rsp);
449        let response = Problem::check::<Challenge>(rsp).await?;
450        match response.error {
451            Some(details) => Err(Error::Api(details)),
452            None => Ok(()),
453        }
454    }
455
456    /// Notify the server that the challenge is ready by sending a device attestation
457    ///
458    /// This function is for the ACME challenge device-attest-01. It should not be used
459    /// with other challenge types.
460    /// See <https://datatracker.ietf.org/doc/draft-acme-device-attest/> for details.
461    ///
462    /// `payload` is the device attestation object as defined in link. Provide the attestation
463    /// object as a raw blob. Base64 encoding of the attestation object `payload.att_obj`
464    /// is done by this function.
465    ///
466    /// The function yields the challenge status from the ACME server that validated the
467    /// attestation challenge.
468    ///
469    /// Note: Device attestation support is experimental.
470    pub async fn send_device_attestation(
471        &mut self,
472        payload: &DeviceAttestation<'_>,
473    ) -> Result<ChallengeStatus, Error> {
474        if self.challenge.r#type != ChallengeType::DeviceAttest01 {
475            return Err(Error::Str("challenge type should be device-attest-01"));
476        }
477
478        #[derive(Serialize)]
479        #[serde(rename_all = "camelCase")]
480        struct DeviceAttestationBase64<'a> {
481            att_obj: Cow<'a, str>,
482        }
483
484        let payload = DeviceAttestationBase64 {
485            att_obj: Cow::Owned(BASE64_URL_SAFE_NO_PAD.encode(&payload.att_obj)),
486        };
487
488        let rsp = self
489            .account
490            .post(Some(&payload), self.nonce.take(), &self.challenge.url)
491            .await?;
492
493        *self.nonce = nonce_from_response(&rsp);
494        let response = Problem::check::<Challenge>(rsp).await?;
495        match response.error {
496            Some(details) => Err(Error::Api(details)),
497            None => Ok(response.status),
498        }
499    }
500
501    /// Create a [`KeyAuthorization`] for this challenge
502    ///
503    /// Combines a challenge's token with the thumbprint of the account's public key to compute
504    /// the challenge's `KeyAuthorization`. The `KeyAuthorization` must be used to provision the
505    /// expected challenge response based on the challenge type in use.
506    pub fn key_authorization(&self) -> KeyAuthorization {
507        KeyAuthorization::new(self.challenge, &self.account.key)
508    }
509
510    /// The identifier for this challenge's authorization
511    pub fn identifier(&self) -> &AuthorizedIdentifier<'_> {
512        &self.identifier
513    }
514}
515
516impl Deref for ChallengeHandle<'_> {
517    type Target = Challenge;
518
519    fn deref(&self) -> &Self::Target {
520        self.challenge
521    }
522}
523
524/// The response value to use for challenge responses
525///
526/// Refer to the methods below to see which encoding to use for your challenge type.
527///
528/// <https://datatracker.ietf.org/doc/html/rfc8555#section-8.1>
529pub struct KeyAuthorization(String);
530
531impl KeyAuthorization {
532    fn new(challenge: &Challenge, key: &Key) -> Self {
533        Self(format!("{}.{}", challenge.token, &key.thumb))
534    }
535
536    /// Get the key authorization value
537    ///
538    /// This can be used for HTTP-01 challenge responses.
539    pub fn as_str(&self) -> &str {
540        &self.0
541    }
542
543    /// Get the SHA-256 digest of the key authorization
544    ///
545    /// This can be used for TLS-ALPN-01 challenge responses.
546    ///
547    /// <https://datatracker.ietf.org/doc/html/rfc8737#section-3>
548    pub fn digest(&self) -> impl AsRef<[u8]> {
549        crypto::digest(&crypto::SHA256, self.0.as_bytes())
550    }
551
552    /// Get the base64-encoded SHA256 digest of the key authorization
553    ///
554    /// This can be used for DNS-01 challenge responses.
555    pub fn dns_value(&self) -> String {
556        BASE64_URL_SAFE_NO_PAD.encode(self.digest())
557    }
558}
559
560impl fmt::Debug for KeyAuthorization {
561    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562        f.debug_tuple("KeyAuthorization").finish()
563    }
564}
565
566/// A policy for retrying API requests
567///
568/// Refresh the order state repeatedly, waiting `delay` before the first attempt and increasing
569/// the delay by a factor of `backoff` after each attempt, until the `timeout` is reached.
570#[derive(Debug, Clone, Copy)]
571pub struct RetryPolicy {
572    delay: Duration,
573    backoff: f32,
574    timeout: Duration,
575}
576
577impl RetryPolicy {
578    /// A constructor for the default `RetryPolicy`
579    ///
580    /// Will retry for 5s with an initial delay of 250ms and a backoff factor of 2.0.
581    pub const fn new() -> Self {
582        Self {
583            delay: Duration::from_millis(250),
584            backoff: 2.0,
585            timeout: Duration::from_secs(30),
586        }
587    }
588
589    /// Set the initial delay
590    ///
591    /// This is the delay before the first retry attempt. The delay will be multiplied by the
592    /// backoff factor after each attempt.
593    pub const fn initial_delay(mut self, delay: Duration) -> Self {
594        self.delay = delay;
595        self
596    }
597
598    /// Set the backoff factor
599    ///
600    /// The delay will be multiplied by this factor after each retry attempt.
601    pub const fn backoff(mut self, backoff: f32) -> Self {
602        self.backoff = backoff;
603        self
604    }
605
606    /// Set the timeout for retries
607    ///
608    /// After this duration has passed, no more retries will be attempted.
609    pub const fn timeout(mut self, timeout: Duration) -> Self {
610        self.timeout = timeout;
611        self
612    }
613
614    fn state(&self) -> RetryState {
615        RetryState {
616            delay: self.delay,
617            backoff: self.backoff,
618            deadline: Instant::now() + self.timeout,
619        }
620    }
621}
622
623impl Default for RetryPolicy {
624    fn default() -> Self {
625        Self::new()
626    }
627}
628
629struct RetryState {
630    delay: Duration,
631    backoff: f32,
632    deadline: Instant,
633}
634
635impl RetryState {
636    async fn wait(&mut self, after: Option<SystemTime>) -> ControlFlow<Error, ()> {
637        if let Some(after) = after {
638            let now = SystemTime::now();
639            if let Ok(delay) = after.duration_since(now) {
640                let next = Instant::now() + delay;
641                if next > self.deadline {
642                    return ControlFlow::Break(Error::Timeout(Some(next)));
643                } else {
644                    sleep(delay).await;
645                    return ControlFlow::Continue(());
646                }
647            }
648        }
649
650        sleep(self.delay).await;
651        self.delay = self.delay.mul_f32(self.backoff);
652        match Instant::now() + self.delay > self.deadline {
653            true => ControlFlow::Break(Error::Timeout(None)),
654            false => ControlFlow::Continue(()),
655        }
656    }
657}