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}