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