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}