instant_acme/
lib.rs

1//! Async pure-Rust ACME (RFC 8555) client.
2
3#![warn(unreachable_pub)]
4#![warn(missing_docs)]
5
6use std::error::Error as StdError;
7use std::fmt;
8use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11
12use async_trait::async_trait;
13use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
14use bytes::Bytes;
15use http::header::{CONTENT_TYPE, LOCATION};
16use http::{Method, Request, Response, StatusCode};
17use http_body_util::{BodyExt, Full};
18#[cfg(feature = "hyper-rustls")]
19use hyper_util::client::legacy::connect::Connect;
20#[cfg(feature = "hyper-rustls")]
21use hyper_util::client::legacy::Client as HyperClient;
22#[cfg(feature = "hyper-rustls")]
23use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
24use serde::de::DeserializeOwned;
25use serde::Serialize;
26
27mod types;
28pub use types::{
29    AccountCredentials, Authorization, AuthorizationStatus, Challenge, ChallengeType, Error,
30    Identifier, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus, Problem,
31    RevocationReason, RevocationRequest, ZeroSsl,
32};
33use types::{
34    DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload,
35    Signer, SigningAlgorithm,
36};
37
38/// An ACME order as described in RFC 8555 (section 7.1.3)
39///
40/// An order is created from an [`Account`] by calling [`Account::new_order()`]. The `Order`
41/// type represents the stable identity of an order, while the [`Order::state()`] method
42/// gives you access to the current state of the order according to the server.
43///
44/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3>
45pub struct Order {
46    account: Arc<AccountInner>,
47    nonce: Option<String>,
48    url: String,
49    state: OrderState,
50}
51
52impl Order {
53    /// Retrieve the authorizations for this order
54    ///
55    /// An order will contain one authorization to complete per identifier in the order.
56    /// After creating an order, you'll need to retrieve the authorizations so that
57    /// you can set up a challenge response for each authorization.
58    ///
59    /// For each authorization, you'll need to:
60    ///
61    /// * Select which [`ChallengeType`] you want to complete
62    /// * Create a [`KeyAuthorization`] for that [`Challenge`]
63    /// * Call [`Order::set_challenge_ready()`] for that challenge
64    ///
65    /// After the challenges have been set up, check the [`Order::state()`] to see
66    /// if the order is ready to be finalized (or becomes invalid). Once it is
67    /// ready, call `Order::finalize()` to get the certificate.
68    pub async fn authorizations(&mut self) -> Result<Vec<Authorization>, Error> {
69        let mut authorizations = Vec::with_capacity(self.state.authorizations.len());
70        for url in &self.state.authorizations {
71            authorizations.push(self.account.get(&mut self.nonce, url).await?);
72        }
73        Ok(authorizations)
74    }
75
76    /// Create a [`KeyAuthorization`] for the given [`Challenge`]
77    ///
78    /// Signs the challenge's token with the account's private key and use the
79    /// value from [`KeyAuthorization::as_str()`] as the challenge response.
80    pub fn key_authorization(&self, challenge: &Challenge) -> KeyAuthorization {
81        KeyAuthorization::new(challenge, &self.account.key)
82    }
83
84    /// Request a certificate from the given Certificate Signing Request (CSR)
85    ///
86    /// Creating a CSR is outside of the scope of instant-acme. Make sure you pass in a
87    /// DER representation of the CSR in `csr_der`. Call `certificate()` to retrieve the
88    /// certificate chain once the order is in the appropriate state.
89    pub async fn finalize(&mut self, csr_der: &[u8]) -> Result<(), Error> {
90        let rsp = self
91            .account
92            .post(
93                Some(&FinalizeRequest::new(csr_der)),
94                self.nonce.take(),
95                &self.state.finalize,
96            )
97            .await?;
98
99        self.nonce = nonce_from_response(&rsp);
100        self.state = Problem::check::<OrderState>(rsp).await?;
101        Ok(())
102    }
103
104    /// Get the certificate for this order
105    ///
106    /// If the cached order state is in `ready` or `processing` state, this will poll the server
107    /// for the latest state. If the order is still in `processing` state after that, this will
108    /// return `Ok(None)`. If the order is in `valid` state, this will attempt to retrieve
109    /// the certificate from the server and return it as a `String`. If the order contains
110    /// an error or ends up in any state other than `valid` or `processing`, return an error.
111    pub async fn certificate(&mut self) -> Result<Option<String>, Error> {
112        if matches!(self.state.status, OrderStatus::Processing) {
113            let rsp = self
114                .account
115                .post(None::<&Empty>, self.nonce.take(), &self.url)
116                .await?;
117            self.nonce = nonce_from_response(&rsp);
118            self.state = Problem::check::<OrderState>(rsp).await?;
119        }
120
121        if let Some(error) = &self.state.error {
122            return Err(Error::Api(error.clone()));
123        } else if self.state.status == OrderStatus::Processing {
124            return Ok(None);
125        } else if self.state.status != OrderStatus::Valid {
126            return Err(Error::Str("invalid order state"));
127        }
128
129        let cert_url = match &self.state.certificate {
130            Some(cert_url) => cert_url,
131            None => 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    /// Notify the server that the given challenge is ready to be completed
148    ///
149    /// `challenge_url` should be the `Challenge::url` field.
150    pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> {
151        let rsp = self
152            .account
153            .post(Some(&Empty {}), self.nonce.take(), challenge_url)
154            .await?;
155
156        self.nonce = nonce_from_response(&rsp);
157        let _ = Problem::check::<Challenge>(rsp).await?;
158        Ok(())
159    }
160
161    /// Get the current state of the given challenge
162    pub async fn challenge(&mut self, challenge_url: &str) -> Result<Challenge, Error> {
163        self.account.get(&mut self.nonce, challenge_url).await
164    }
165
166    /// Refresh the current state of the order
167    pub async fn refresh(&mut self) -> Result<&OrderState, Error> {
168        let rsp = self
169            .account
170            .post(None::<&Empty>, self.nonce.take(), &self.url)
171            .await?;
172
173        self.nonce = nonce_from_response(&rsp);
174        self.state = Problem::check::<OrderState>(rsp).await?;
175        Ok(&self.state)
176    }
177
178    /// Extract the URL and last known state from the `Order`
179    pub fn into_parts(self) -> (String, OrderState) {
180        (self.url, self.state)
181    }
182
183    /// Get the last known state of the order
184    ///
185    /// Call `refresh()` to get the latest state from the server.
186    pub fn state(&mut self) -> &OrderState {
187        &self.state
188    }
189
190    /// Get the URL of the order
191    pub fn url(&self) -> &str {
192        &self.url
193    }
194}
195
196/// An ACME account as described in RFC 8555 (section 7.1.2)
197///
198/// Create an [`Account`] with [`Account::create()`] or restore it from serialized data
199/// by passing deserialized [`AccountCredentials`] to [`Account::from_credentials()`].
200///
201/// The [`Account`] type is cheap to clone.
202///
203/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.2>
204#[derive(Clone)]
205pub struct Account {
206    inner: Arc<AccountInner>,
207}
208
209impl Account {
210    /// Restore an existing account from the given credentials
211    ///
212    /// The [`AccountCredentials`] type is opaque, but supports deserialization.
213    #[cfg(feature = "hyper-rustls")]
214    pub async fn from_credentials(credentials: AccountCredentials) -> Result<Self, Error> {
215        Ok(Self {
216            inner: Arc::new(
217                AccountInner::from_credentials(credentials, Box::new(DefaultClient::try_new()?))
218                    .await?,
219            ),
220        })
221    }
222
223    /// Restore an existing account from the given credentials and HTTP client
224    ///
225    /// The [`AccountCredentials`] type is opaque, but supports deserialization.
226    pub async fn from_credentials_and_http(
227        credentials: AccountCredentials,
228        http: Box<dyn HttpClient>,
229    ) -> Result<Self, Error> {
230        Ok(Self {
231            inner: Arc::new(AccountInner::from_credentials(credentials, http).await?),
232        })
233    }
234
235    /// Restore an existing account from the given ID, private key, server URL and HTTP client
236    ///
237    /// The key must be provided in DER-encoded PKCS#8. This is usually how ECDSA keys are
238    /// encoded in PEM files. Use a crate like rustls-pemfile to decode from PEM to DER.
239    pub async fn from_parts(
240        id: String,
241        key_pkcs8_der: &[u8],
242        directory_url: &str,
243        http: Box<dyn HttpClient>,
244    ) -> Result<Self, Error> {
245        Ok(Self {
246            inner: Arc::new(AccountInner {
247                id,
248                key: Key::from_pkcs8_der(key_pkcs8_der)?,
249                client: Client::new(directory_url, http).await?,
250            }),
251        })
252    }
253
254    /// Create a new account on the `server_url` with the information in [`NewAccount`]
255    ///
256    /// The returned [`AccountCredentials`] can be serialized and stored for later use.
257    /// Use [`Account::from_credentials()`] to restore the account from the credentials.
258    #[cfg(feature = "hyper-rustls")]
259    pub async fn create(
260        account: &NewAccount<'_>,
261        server_url: &str,
262        external_account: Option<&ExternalAccountKey>,
263    ) -> Result<(Account, AccountCredentials), Error> {
264        Self::create_inner(
265            account,
266            external_account,
267            Client::new(server_url, Box::new(DefaultClient::try_new()?)).await?,
268            server_url,
269        )
270        .await
271    }
272
273    /// Create a new account with a custom HTTP client
274    ///
275    /// The returned [`AccountCredentials`] can be serialized and stored for later use.
276    /// Use [`Account::from_credentials()`] to restore the account from the credentials.
277    pub async fn create_with_http(
278        account: &NewAccount<'_>,
279        server_url: &str,
280        external_account: Option<&ExternalAccountKey>,
281        http: Box<dyn HttpClient>,
282    ) -> Result<(Account, AccountCredentials), Error> {
283        Self::create_inner(
284            account,
285            external_account,
286            Client::new(server_url, http).await?,
287            server_url,
288        )
289        .await
290    }
291
292    async fn create_inner(
293        account: &NewAccount<'_>,
294        external_account: Option<&ExternalAccountKey>,
295        client: Client,
296        server_url: &str,
297    ) -> Result<(Account, AccountCredentials), Error> {
298        let (key, key_pkcs8) = Key::generate()?;
299        let payload = NewAccountPayload {
300            new_account: account,
301            external_account_binding: external_account
302                .map(|eak| {
303                    JoseJson::new(
304                        Some(&Jwk::new(&key.inner)),
305                        eak.header(None, &client.urls.new_account),
306                        eak,
307                    )
308                })
309                .transpose()?,
310        };
311
312        let rsp = client
313            .post(Some(&payload), None, &key, &client.urls.new_account)
314            .await?;
315
316        let account_url = rsp
317            .parts
318            .headers
319            .get(LOCATION)
320            .and_then(|hv| hv.to_str().ok())
321            .map(|s| s.to_owned());
322
323        // The response redirects, we don't need the body
324        let _ = Problem::from_response(rsp).await?;
325        let id = account_url.ok_or("failed to get account URL")?;
326        let credentials = AccountCredentials {
327            id: id.clone(),
328            key_pkcs8: key_pkcs8.as_ref().to_vec(),
329            directory: Some(server_url.to_owned()),
330            // We support deserializing URLs for compatibility with versions pre 0.4,
331            // but we prefer to get fresh URLs from the `server_url` for newer credentials.
332            urls: None,
333        };
334
335        let account = AccountInner {
336            client,
337            key,
338            id: id.clone(),
339        };
340
341        Ok((
342            Self {
343                inner: Arc::new(account),
344            },
345            credentials,
346        ))
347    }
348
349    /// Create a new order based on the given [`NewOrder`]
350    ///
351    /// Returns an [`Order`] instance. Use the [`Order::state()`] method to inspect its state.
352    pub async fn new_order(&self, order: &NewOrder<'_>) -> Result<Order, Error> {
353        let rsp = self
354            .inner
355            .post(Some(order), None, &self.inner.client.urls.new_order)
356            .await?;
357
358        let nonce = nonce_from_response(&rsp);
359        let order_url = rsp
360            .parts
361            .headers
362            .get(LOCATION)
363            .and_then(|hv| hv.to_str().ok())
364            .map(|s| s.to_owned());
365
366        Ok(Order {
367            account: self.inner.clone(),
368            nonce,
369            // Order of fields matters! We return errors from Problem::check
370            // before emitting an error if there is no order url. Or the
371            // simple no url error hides the causing error in `Problem::check`.
372            state: Problem::check::<OrderState>(rsp).await?,
373            url: order_url.ok_or("no order URL found")?,
374        })
375    }
376
377    /// Fetch the order state for an existing order based on the given `url`
378    ///
379    /// This might fail if the given URL's order belongs to a different account.
380    ///
381    /// Returns an [`Order`] instance. Use the [`Order::state`] method to inspect its state.
382    pub async fn order(&self, url: String) -> Result<Order, Error> {
383        let rsp = self.inner.post(None::<&Empty>, None, &url).await?;
384        Ok(Order {
385            account: self.inner.clone(),
386            nonce: nonce_from_response(&rsp),
387            // Order of fields matters! We return errors from Problem::check
388            // before emitting an error if there is no order url. Or the
389            // simple no url error hides the causing error in `Problem::check`.
390            state: Problem::check::<OrderState>(rsp).await?,
391            url,
392        })
393    }
394
395    /// Revokes a previously issued certificate
396    pub async fn revoke<'a>(&'a self, payload: &RevocationRequest<'a>) -> Result<(), Error> {
397        let revoke_url = match self.inner.client.urls.revoke_cert.as_deref() {
398            Some(url) => url,
399            // This happens because the current account credentials were deserialized from an
400            // older version which only serialized a subset of the directory URLs. You should
401            // make sure the account credentials include a `directory` field containing a
402            // string with the server's directory URL.
403            None => return Err("no revokeCert URL found".into()),
404        };
405
406        let rsp = self.inner.post(Some(payload), None, revoke_url).await?;
407        // The body is empty if the request was successful
408        let _ = Problem::from_response(rsp).await?;
409        Ok(())
410    }
411
412    /// Get the account ID
413    pub fn id(&self) -> &str {
414        &self.inner.id
415    }
416}
417
418struct AccountInner {
419    client: Client,
420    key: Key,
421    id: String,
422}
423
424impl AccountInner {
425    async fn from_credentials(
426        credentials: AccountCredentials,
427        http: Box<dyn HttpClient>,
428    ) -> Result<Self, Error> {
429        Ok(Self {
430            id: credentials.id,
431            key: Key::from_pkcs8_der(credentials.key_pkcs8.as_ref())?,
432            client: match (credentials.directory, credentials.urls) {
433                (Some(server_url), _) => Client::new(&server_url, http).await?,
434                (None, Some(urls)) => Client { http, urls },
435                (None, None) => return Err("no server URLs found".into()),
436            },
437        })
438    }
439
440    async fn get<T: DeserializeOwned>(
441        &self,
442        nonce: &mut Option<String>,
443        url: &str,
444    ) -> Result<T, Error> {
445        let rsp = self.post(None::<&Empty>, nonce.take(), url).await?;
446        *nonce = nonce_from_response(&rsp);
447        Problem::check(rsp).await
448    }
449
450    async fn post(
451        &self,
452        payload: Option<&impl Serialize>,
453        nonce: Option<String>,
454        url: &str,
455    ) -> Result<BytesResponse, Error> {
456        self.client.post(payload, nonce, self, url).await
457    }
458}
459
460impl Signer for AccountInner {
461    type Signature = <Key as Signer>::Signature;
462
463    fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
464        debug_assert!(nonce.is_some());
465        Header {
466            alg: self.key.signing_algorithm,
467            key: KeyOrKeyId::KeyId(&self.id),
468            nonce,
469            url,
470        }
471    }
472
473    fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
474        self.key.sign(payload)
475    }
476}
477
478struct Client {
479    http: Box<dyn HttpClient>,
480    urls: DirectoryUrls,
481}
482
483impl Client {
484    async fn new(server_url: &str, http: Box<dyn HttpClient>) -> Result<Self, Error> {
485        let req = Request::builder()
486            .uri(server_url)
487            .body(Full::default())
488            .expect("infallible error should not occur");
489        let rsp = http.request(req).await?;
490        let body = rsp.body().await.map_err(Error::Other)?;
491        Ok(Client {
492            http,
493            urls: serde_json::from_slice(&body)?,
494        })
495    }
496
497    async fn post(
498        &self,
499        payload: Option<&impl Serialize>,
500        mut nonce: Option<String>,
501        signer: &impl Signer,
502        url: &str,
503    ) -> Result<BytesResponse, Error> {
504        let mut retries = 3;
505        loop {
506            let mut response = self
507                .post_attempt(payload, nonce.clone(), signer, url)
508                .await?;
509            if response.parts.status != StatusCode::BAD_REQUEST {
510                return Ok(response);
511            }
512            let body = response.body.into_bytes().await.map_err(Error::Other)?;
513            let problem = serde_json::from_slice::<Problem>(&body)?;
514            if let Some("urn:ietf:params:acme:error:badNonce") = problem.r#type.as_deref() {
515                retries -= 1;
516                if retries != 0 {
517                    // Retrieve the new nonce. If it isn't there (it
518                    // should be, the spec requires it) then we will
519                    // manually refresh a new one in `post_attempt`
520                    // due to `nonce` being `None` but getting it from
521                    // the response saves us making that request.
522                    nonce = nonce_from_response(&response);
523                    continue;
524                }
525            }
526
527            return Ok(BytesResponse {
528                parts: response.parts,
529                body: Box::new(body),
530            });
531        }
532    }
533
534    async fn post_attempt(
535        &self,
536        payload: Option<&impl Serialize>,
537        nonce: Option<String>,
538        signer: &impl Signer,
539        url: &str,
540    ) -> Result<BytesResponse, Error> {
541        let nonce = self.nonce(nonce).await?;
542        let body = JoseJson::new(payload, signer.header(Some(&nonce), url), signer)?;
543        let request = Request::builder()
544            .method(Method::POST)
545            .uri(url)
546            .header(CONTENT_TYPE, JOSE_JSON)
547            .body(Full::from(serde_json::to_vec(&body)?))?;
548
549        self.http.request(request).await
550    }
551
552    async fn nonce(&self, nonce: Option<String>) -> Result<String, Error> {
553        if let Some(nonce) = nonce {
554            return Ok(nonce);
555        }
556
557        let request = Request::builder()
558            .method(Method::HEAD)
559            .uri(&self.urls.new_nonce)
560            .body(Full::default())
561            .expect("infallible error should not occur");
562
563        let rsp = self.http.request(request).await?;
564        // https://datatracker.ietf.org/doc/html/rfc8555#section-7.2
565        // "The server's response MUST include a Replay-Nonce header field containing a fresh
566        // nonce and SHOULD have status code 200 (OK)."
567        if rsp.parts.status != StatusCode::OK {
568            return Err("error response from newNonce resource".into());
569        }
570
571        match nonce_from_response(&rsp) {
572            Some(nonce) => Ok(nonce),
573            None => Err("no nonce found in newNonce response".into()),
574        }
575    }
576}
577
578impl fmt::Debug for Client {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        f.debug_struct("Client")
581            .field("client", &"..")
582            .field("urls", &self.urls)
583            .finish()
584    }
585}
586
587struct Key {
588    rng: crypto::SystemRandom,
589    signing_algorithm: SigningAlgorithm,
590    inner: crypto::EcdsaKeyPair,
591    thumb: String,
592}
593
594impl Key {
595    fn generate() -> Result<(Self, crypto::pkcs8::Document), Error> {
596        let rng = crypto::SystemRandom::new();
597        let pkcs8 =
598            crypto::EcdsaKeyPair::generate_pkcs8(&crypto::ECDSA_P256_SHA256_FIXED_SIGNING, &rng)?;
599        Self::new(pkcs8.as_ref(), rng).map(|key| (key, pkcs8))
600    }
601
602    fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self, Error> {
603        Self::new(pkcs8_der, crypto::SystemRandom::new())
604    }
605
606    fn new(pkcs8_der: &[u8], rng: crypto::SystemRandom) -> Result<Self, Error> {
607        let inner = crypto::p256_key_pair_from_pkcs8(pkcs8_der, &rng)?;
608        let thumb = BASE64_URL_SAFE_NO_PAD.encode(Jwk::thumb_sha256(&inner)?);
609        Ok(Self {
610            rng,
611            signing_algorithm: SigningAlgorithm::Es256,
612            inner,
613            thumb,
614        })
615    }
616}
617
618impl Signer for Key {
619    type Signature = crypto::Signature;
620
621    fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
622        debug_assert!(nonce.is_some());
623        Header {
624            alg: self.signing_algorithm,
625            key: KeyOrKeyId::from_key(&self.inner),
626            nonce,
627            url,
628        }
629    }
630
631    fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
632        Ok(self.inner.sign(&self.rng, payload)?)
633    }
634}
635
636/// The response value to use for challenge responses
637///
638/// Refer to the methods below to see which encoding to use for your challenge type.
639///
640/// <https://datatracker.ietf.org/doc/html/rfc8555#section-8.1>
641pub struct KeyAuthorization(String);
642
643impl KeyAuthorization {
644    fn new(challenge: &Challenge, key: &Key) -> Self {
645        Self(format!("{}.{}", challenge.token, &key.thumb))
646    }
647
648    /// Get the key authorization value
649    ///
650    /// This can be used for HTTP-01 challenge responses.
651    pub fn as_str(&self) -> &str {
652        &self.0
653    }
654
655    /// Get the SHA-256 digest of the key authorization
656    ///
657    /// This can be used for TLS-ALPN-01 challenge responses.
658    ///
659    /// <https://datatracker.ietf.org/doc/html/rfc8737#section-3>
660    pub fn digest(&self) -> impl AsRef<[u8]> {
661        crypto::digest(&crypto::SHA256, self.0.as_bytes())
662    }
663
664    /// Get the base64-encoded SHA256 digest of the key authorization
665    ///
666    /// This can be used for DNS-01 challenge responses.
667    pub fn dns_value(&self) -> String {
668        BASE64_URL_SAFE_NO_PAD.encode(self.digest())
669    }
670}
671
672impl fmt::Debug for KeyAuthorization {
673    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674        f.debug_tuple("KeyAuthorization").finish()
675    }
676}
677
678/// A HMAC key used to link account creation requests to an external account
679///
680/// See RFC 8555 section 7.3.4 for more information.
681pub struct ExternalAccountKey {
682    id: String,
683    key: crypto::hmac::Key,
684}
685
686impl ExternalAccountKey {
687    /// Create a new external account key
688    pub fn new(id: String, key_value: &[u8]) -> Self {
689        Self {
690            id,
691            key: crypto::hmac::Key::new(crypto::hmac::HMAC_SHA256, key_value),
692        }
693    }
694}
695
696impl Signer for ExternalAccountKey {
697    type Signature = crypto::hmac::Tag;
698
699    fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
700        debug_assert_eq!(nonce, None);
701        Header {
702            alg: SigningAlgorithm::Hs256,
703            key: KeyOrKeyId::KeyId(&self.id),
704            nonce,
705            url,
706        }
707    }
708
709    fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
710        Ok(crypto::hmac::sign(&self.key, payload))
711    }
712}
713
714fn nonce_from_response(rsp: &BytesResponse) -> Option<String> {
715    rsp.parts
716        .headers
717        .get(REPLAY_NONCE)
718        .and_then(|hv| String::from_utf8(hv.as_ref().to_vec()).ok())
719}
720
721#[cfg(feature = "hyper-rustls")]
722struct DefaultClient(HyperClient<hyper_rustls::HttpsConnector<HttpConnector>, Full<Bytes>>);
723
724#[cfg(feature = "hyper-rustls")]
725impl DefaultClient {
726    fn try_new() -> Result<Self, Error> {
727        Ok(Self(
728            HyperClient::builder(TokioExecutor::new()).build(
729                hyper_rustls::HttpsConnectorBuilder::new()
730                    .with_native_roots()
731                    .map_err(|e| Error::Other(Box::new(e)))?
732                    .https_only()
733                    .enable_http1()
734                    .enable_http2()
735                    .build(),
736            ),
737        ))
738    }
739}
740
741#[cfg(feature = "hyper-rustls")]
742impl HttpClient for DefaultClient {
743    fn request(
744        &self,
745        req: Request<Full<Bytes>>,
746    ) -> Pin<Box<dyn Future<Output = Result<BytesResponse, Error>> + Send>> {
747        let fut = self.0.request(req);
748        Box::pin(async move {
749            match fut.await {
750                Ok(rsp) => Ok(BytesResponse::from(rsp)),
751                Err(e) => Err(e.into()),
752            }
753        })
754    }
755}
756
757/// A HTTP client abstraction
758pub trait HttpClient: Send + Sync + 'static {
759    /// Send the given request and return the response
760    fn request(
761        &self,
762        req: Request<Full<Bytes>>,
763    ) -> Pin<Box<dyn Future<Output = Result<BytesResponse, Error>> + Send>>;
764}
765
766#[cfg(feature = "hyper-rustls")]
767impl<C: Connect + Clone + Send + Sync + 'static> HttpClient for HyperClient<C, Full<Bytes>> {
768    fn request(
769        &self,
770        req: Request<Full<Bytes>>,
771    ) -> Pin<Box<dyn Future<Output = Result<BytesResponse, Error>> + Send>> {
772        let fut = self.request(req);
773        Box::pin(async move {
774            match fut.await {
775                Ok(rsp) => Ok(BytesResponse::from(rsp)),
776                Err(e) => Err(e.into()),
777            }
778        })
779    }
780}
781
782/// Response with object safe body type
783pub struct BytesResponse {
784    /// Response status and header
785    pub parts: http::response::Parts,
786    /// Response body
787    pub body: Box<dyn BytesBody>,
788}
789
790impl BytesResponse {
791    pub(crate) async fn body(mut self) -> Result<Bytes, Box<dyn StdError + Send + Sync + 'static>> {
792        self.body.into_bytes().await
793    }
794}
795
796impl<B> From<Response<B>> for BytesResponse
797where
798    B: http_body::Body + Send + Unpin + 'static,
799    B::Data: Send,
800    B::Error: Into<Box<dyn StdError + Send + Sync + 'static>>,
801{
802    fn from(rsp: Response<B>) -> Self {
803        let (parts, body) = rsp.into_parts();
804        Self {
805            parts,
806            body: Box::new(BodyWrapper { inner: Some(body) }),
807        }
808    }
809}
810
811struct BodyWrapper<B> {
812    inner: Option<B>,
813}
814
815#[async_trait]
816impl<B> BytesBody for BodyWrapper<B>
817where
818    B: http_body::Body + Send + Unpin + 'static,
819    B::Data: Send,
820    B::Error: Into<Box<dyn StdError + Send + Sync + 'static>>,
821{
822    async fn into_bytes(&mut self) -> Result<Bytes, Box<dyn StdError + Send + Sync + 'static>> {
823        let Some(body) = self.inner.take() else {
824            return Ok(Bytes::new());
825        };
826
827        match body.collect().await {
828            Ok(body) => Ok(body.to_bytes()),
829            Err(e) => Err(e.into()),
830        }
831    }
832}
833
834#[async_trait]
835impl BytesBody for Bytes {
836    async fn into_bytes(&mut self) -> Result<Bytes, Box<dyn StdError + Send + Sync + 'static>> {
837        Ok(self.to_owned())
838    }
839}
840
841/// Object safe body trait
842#[async_trait]
843pub trait BytesBody: Send {
844    /// Convert the body into [`Bytes`]
845    ///
846    /// This consumes the body. The behavior for calling this method multiple times is undefined.
847    #[allow(clippy::wrong_self_convention)] // async_trait doesn't support taking `self`
848    async fn into_bytes(&mut self) -> Result<Bytes, Box<dyn StdError + Send + Sync + 'static>>;
849}
850
851mod crypto {
852    #[cfg(all(feature = "aws-lc-rs", any(feature = "fips", not(feature = "ring"))))]
853    pub(crate) use aws_lc_rs as ring_like;
854    #[cfg(all(feature = "ring", not(feature = "fips")))]
855    pub(crate) use ring as ring_like;
856
857    pub(crate) use ring_like::digest::{digest, Digest, SHA256};
858    pub(crate) use ring_like::error::{KeyRejected, Unspecified};
859    pub(crate) use ring_like::rand::SystemRandom;
860    pub(crate) use ring_like::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
861    pub(crate) use ring_like::signature::{KeyPair, Signature};
862    pub(crate) use ring_like::{hmac, pkcs8};
863
864    #[cfg(all(feature = "aws-lc-rs", any(feature = "fips", not(feature = "ring"))))]
865    pub(crate) fn p256_key_pair_from_pkcs8(
866        pkcs8: &[u8],
867        _: &SystemRandom,
868    ) -> Result<EcdsaKeyPair, KeyRejected> {
869        EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8)
870    }
871
872    #[cfg(all(feature = "ring", not(feature = "fips")))]
873    pub(crate) fn p256_key_pair_from_pkcs8(
874        pkcs8: &[u8],
875        rng: &SystemRandom,
876    ) -> Result<EcdsaKeyPair, KeyRejected> {
877        EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8, rng)
878    }
879}
880
881const JOSE_JSON: &str = "application/jose+json";
882const REPLAY_NONCE: &str = "Replay-Nonce";
883
884#[cfg(test)]
885mod tests {
886    use super::*;
887
888    #[tokio::test]
889    async fn deserialize_old_credentials() -> Result<(), Error> {
890        const CREDENTIALS: &str = r#"{"id":"id","key_pkcs8":"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJVWC_QzOTCS5vtsJp2IG-UDc8cdDfeoKtxSZxaznM-mhRANCAAQenCPoGgPFTdPJ7VLLKt56RxPlYT1wNXnHc54PEyBg3LxKaH0-sJkX0mL8LyPEdsfL_Oz4TxHkWLJGrXVtNhfH","urls":{"newNonce":"new-nonce","newAccount":"new-acct","newOrder":"new-order", "revokeCert": "revoke-cert"}}"#;
891        Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
892        Ok(())
893    }
894
895    #[tokio::test]
896    async fn deserialize_new_credentials() -> Result<(), Error> {
897        const CREDENTIALS: &str = r#"{"id":"id","key_pkcs8":"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJVWC_QzOTCS5vtsJp2IG-UDc8cdDfeoKtxSZxaznM-mhRANCAAQenCPoGgPFTdPJ7VLLKt56RxPlYT1wNXnHc54PEyBg3LxKaH0-sJkX0mL8LyPEdsfL_Oz4TxHkWLJGrXVtNhfH","directory":"https://acme-staging-v02.api.letsencrypt.org/directory"}"#;
898        Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
899        Ok(())
900    }
901}