1#[cfg(feature = "hyper-rustls")]
2use std::path::Path;
3use std::sync::Arc;
4#[cfg(feature = "time")]
5use std::time::{Duration, SystemTime};
6
7use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
8use http::header::LOCATION;
9#[cfg(feature = "time")]
10use http::header::USER_AGENT;
11#[cfg(feature = "time")]
12use http::{Method, Request};
13#[cfg(feature = "hyper-rustls")]
14use rustls::RootCertStore;
15#[cfg(feature = "hyper-rustls")]
16use rustls_pki_types::CertificateDer;
17#[cfg(feature = "hyper-rustls")]
18use rustls_pki_types::pem::PemObject;
19use rustls_pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer};
20use serde::de::DeserializeOwned;
21use serde::{Deserialize, Serialize};
22
23#[cfg(feature = "hyper-rustls")]
24use crate::DefaultClient;
25use crate::order::Order;
26use crate::types::{
27 AccountCredentials, AuthorizationStatus, Empty, Header, JoseJson, Jwk, KeyOrKeyId, NewAccount,
28 NewAccountPayload, NewOrder, OrderState, Problem, ProfileMeta, RevocationRequest, Signer,
29 SigningAlgorithm,
30};
31#[cfg(feature = "time")]
32use crate::types::{CertificateIdentifier, RenewalInfo};
33#[cfg(feature = "time")]
34use crate::{BodyWrapper, CRATE_USER_AGENT, retry_after};
35use crate::{BytesResponse, Client, Error, HttpClient, crypto, nonce_from_response};
36
37#[derive(Clone)]
48pub struct Account {
49 inner: Arc<AccountInner>,
50}
51
52impl Account {
53 #[cfg(feature = "hyper-rustls")]
55 pub fn builder() -> Result<AccountBuilder, Error> {
56 Ok(AccountBuilder {
57 http: Box::new(DefaultClient::try_new()?),
58 })
59 }
60
61 #[cfg(feature = "hyper-rustls")]
66 pub fn builder_with_root(pem_path: impl AsRef<Path>) -> Result<AccountBuilder, Error> {
67 let root_der = match CertificateDer::from_pem_file(pem_path) {
68 Ok(root_der) => root_der,
69 Err(err) => return Err(Error::Other(err.into())),
70 };
71
72 let mut roots = RootCertStore::empty();
73 match roots.add(root_der) {
74 Ok(()) => Ok(AccountBuilder {
75 http: Box::new(DefaultClient::with_roots(roots)?),
76 }),
77 Err(err) => Err(Error::Other(err.into())),
78 }
79 }
80
81 pub fn builder_with_http(http: Box<dyn HttpClient>) -> AccountBuilder {
83 AccountBuilder { http }
84 }
85
86 pub async fn new_order(&self, order: &NewOrder<'_>) -> Result<Order, Error> {
90 if order.replaces.is_some() && self.inner.client.directory.renewal_info.is_none() {
91 return Err(Error::Unsupported("ACME renewal information (ARI)"));
92 }
93
94 let rsp = self
95 .inner
96 .post(Some(order), None, &self.inner.client.directory.new_order)
97 .await?;
98
99 let nonce = nonce_from_response(&rsp);
100 let order_url = rsp
101 .parts
102 .headers
103 .get(LOCATION)
104 .and_then(|hv| hv.to_str().ok())
105 .map(|s| s.to_owned());
106
107 let state = Problem::check::<OrderState>(rsp).await?;
110
111 if order.replaces.is_some() && order.replaces != state.replaces {
116 return Err(Error::Other(
117 format!(
118 "replaces field mismatch: expected {expected:?}, found {found:?}",
119 expected = order.replaces,
120 found = state.replaces,
121 )
122 .into(),
123 ));
124 }
125
126 Ok(Order {
127 account: self.inner.clone(),
128 nonce,
129 retry_after: None,
130 state,
131 url: order_url.ok_or("no order URL found")?,
132 })
133 }
134
135 pub async fn order(&self, url: String) -> Result<Order, Error> {
141 let rsp = self.inner.post(None::<&Empty>, None, &url).await?;
142 Ok(Order {
143 account: self.inner.clone(),
144 nonce: nonce_from_response(&rsp),
145 retry_after: None,
146 state: Problem::check::<OrderState>(rsp).await?,
150 url,
151 })
152 }
153
154 pub async fn revoke<'a>(&'a self, payload: &RevocationRequest<'a>) -> Result<(), Error> {
156 let Some(revoke_url) = self.inner.client.directory.revoke_cert.as_deref() else {
157 return Err("no revokeCert URL found".into());
162 };
163
164 let rsp = self.inner.post(Some(payload), None, revoke_url).await?;
165 let _ = Problem::from_response(rsp).await?;
167 Ok(())
168 }
169
170 #[cfg(feature = "time")]
185 pub async fn renewal_info(
186 &self,
187 certificate_id: &CertificateIdentifier<'_>,
188 ) -> Result<(RenewalInfo, Duration), Error> {
189 let Some(renewal_info_url) = self.inner.client.directory.renewal_info.as_deref() else {
190 return Err(Error::Unsupported("ACME renewal information (ARI)"));
191 };
192
193 let request = Request::builder()
196 .method(Method::GET)
197 .uri(format!("{renewal_info_url}/{certificate_id}"))
198 .header(USER_AGENT, CRATE_USER_AGENT)
199 .body(BodyWrapper::default())?;
200
201 let rsp = self.inner.client.http.request(request).await?;
202 let Some(retry_after) = retry_after(&rsp) else {
203 return Err(Error::Str("missing Retry-After header"));
204 };
205
206 let delay = retry_after
207 .duration_since(SystemTime::now())
208 .unwrap_or(Duration::ZERO);
209
210 Ok((Problem::check::<RenewalInfo>(rsp).await?, delay))
211 }
212
213 pub async fn update_key(&mut self) -> Result<AccountCredentials, Error> {
222 let Some(new_key_url) = self.inner.client.directory.key_change.as_deref() else {
223 return Err("Account key rollover not supported by ACME CA".into());
224 };
225
226 #[derive(Debug, Serialize)]
227 struct NewKey<'a> {
228 account: &'a str,
229 #[serde(rename = "oldKey")]
230 old_key: Jwk,
231 }
232
233 let (new_key, new_key_pkcs8) = Key::generate_pkcs8()?;
234 let mut header = new_key.header(Some("nonce"), new_key_url);
235 header.nonce = None;
236 let payload = NewKey {
237 account: &self.inner.id,
238 old_key: Jwk::new(&self.inner.key.inner),
239 };
240
241 let body = JoseJson::new(Some(&payload), header, &new_key)?;
242 let rsp = self.inner.post(Some(&body), None, new_key_url).await?;
243 let _ = Problem::from_response(rsp).await?;
244
245 self.inner = Arc::new(AccountInner {
246 client: self.inner.client.clone(),
247 key: new_key,
248 id: self.inner.id.clone(),
249 });
250
251 let (directory, urls) = match &self.inner.client.directory_url {
252 Some(directory_url) => (Some(directory_url.clone()), None),
253 None => (None, Some(self.inner.client.directory.clone())),
254 };
255
256 Ok(AccountCredentials {
257 id: self.inner.id.clone(),
258 key_pkcs8: new_key_pkcs8,
259 directory,
260 urls,
261 })
262 }
263
264 pub async fn update_contacts<'a>(&self, contacts: &'a [&'a str]) -> Result<(), Error> {
272 #[derive(Debug, Serialize)]
273 struct Contacts<'a> {
274 contact: &'a [&'a str],
275 }
276
277 let payload = Contacts { contact: contacts };
278 let rsp = self
279 .inner
280 .post(Some(&payload), None, &self.inner.id)
281 .await?;
282
283 #[derive(Debug, Deserialize)]
284 struct Account {
285 status: AuthorizationStatus,
286 }
287
288 let response = Problem::check::<Account>(rsp).await?;
289 match response.status {
290 AuthorizationStatus::Valid => Ok(()),
291 _ => Err("Unexpected account status after updating contact information".into()),
292 }
293 }
294
295 pub async fn deactivate(self) -> Result<(), Error> {
305 #[derive(Serialize)]
306 struct DeactivateRequest<'a> {
307 status: &'a str,
308 }
309
310 let _ = self
311 .inner
312 .post(
313 Some(&DeactivateRequest {
314 status: "deactivated",
315 }),
316 None,
317 self.id(),
318 )
319 .await?;
320
321 Ok(())
322 }
323
324 pub fn profiles(&self) -> impl Iterator<Item = ProfileMeta<'_>> {
326 self.inner
327 .client
328 .directory
329 .meta
330 .profiles
331 .iter()
332 .map(|(name, description)| ProfileMeta { name, description })
333 }
334
335 pub fn id(&self) -> &str {
337 &self.inner.id
338 }
339
340 pub fn key_thumbprint(&self) -> &str {
342 &self.inner.key.thumb
343 }
344}
345
346pub(crate) struct AccountInner {
347 client: Arc<Client>,
348 pub(crate) key: Key,
349 id: String,
350}
351
352impl AccountInner {
353 async fn from_credentials(
354 credentials: AccountCredentials,
355 http: Box<dyn HttpClient>,
356 ) -> Result<Self, Error> {
357 Ok(Self {
358 id: credentials.id,
359 key: Key::from_pkcs8_der(credentials.key_pkcs8)?,
360 client: Arc::new(match (credentials.directory, credentials.urls) {
361 (Some(directory_url), _) => Client::new(directory_url, http).await?,
362 (None, Some(directory)) => Client {
363 http,
364 directory,
365 directory_url: None,
366 },
367 (None, None) => return Err("no server URLs found".into()),
368 }),
369 })
370 }
371
372 pub(crate) async fn get<T: DeserializeOwned>(
373 &self,
374 nonce: &mut Option<String>,
375 url: &str,
376 ) -> Result<T, Error> {
377 let rsp = self.post(None::<&Empty>, nonce.take(), url).await?;
378 *nonce = nonce_from_response(&rsp);
379 Problem::check(rsp).await
380 }
381
382 pub(crate) async fn post(
383 &self,
384 payload: Option<&impl Serialize>,
385 nonce: Option<String>,
386 url: &str,
387 ) -> Result<BytesResponse, Error> {
388 self.client.post(payload, nonce, self, url).await
389 }
390}
391
392impl Signer for AccountInner {
393 type Signature = <Key as Signer>::Signature;
394
395 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
396 debug_assert!(nonce.is_some());
397 Header {
398 alg: self.key.signing_algorithm,
399 key: KeyOrKeyId::KeyId(&self.id),
400 nonce,
401 url,
402 }
403 }
404
405 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
406 self.key.sign(payload)
407 }
408}
409
410pub struct AccountBuilder {
414 http: Box<dyn HttpClient>,
415}
416
417impl AccountBuilder {
418 #[allow(clippy::wrong_self_convention)]
422 pub async fn from_credentials(self, credentials: AccountCredentials) -> Result<Account, Error> {
423 Ok(Account {
424 inner: Arc::new(AccountInner::from_credentials(credentials, self.http).await?),
425 })
426 }
427
428 pub async fn create(
433 self,
434 account: &NewAccount<'_>,
435 directory_url: String,
436 external_account: Option<&ExternalAccountKey>,
437 ) -> Result<(Account, AccountCredentials), Error> {
438 let (key, key_pkcs8) = Key::generate_pkcs8()?;
439 Self::create_inner(
440 account,
441 (key, key_pkcs8),
442 external_account,
443 Client::new(directory_url, self.http).await?,
444 )
445 .await
446 }
447
448 pub async fn from_key(
455 self,
456 key: (Key, PrivateKeyDer<'static>),
457 directory_url: String,
458 ) -> Result<(Account, AccountCredentials), Error> {
459 self.from_key_inner(key, directory_url, true).await
460 }
461
462 pub async fn create_from_key(
470 self,
471 key: (Key, PrivateKeyDer<'static>),
472 directory_url: String,
473 ) -> Result<(Account, AccountCredentials), Error> {
474 self.from_key_inner(key, directory_url, false).await
475 }
476
477 #[allow(clippy::wrong_self_convention)]
479 async fn from_key_inner(
480 self,
481 key: (Key, PrivateKeyDer<'static>),
482 directory_url: String,
483 only_return_existing: bool,
484 ) -> Result<(Account, AccountCredentials), Error> {
485 Self::create_inner(
486 &NewAccount {
487 contact: &[],
488 terms_of_service_agreed: true,
489 only_return_existing,
490 },
491 match key {
492 (key, PrivateKeyDer::Pkcs8(pkcs8)) => (key, pkcs8),
493 _ => return Err("unsupported key format, expected PKCS#8".into()),
494 },
495 None,
496 Client::new(directory_url, self.http).await?,
497 )
498 .await
499 }
500
501 #[allow(clippy::wrong_self_convention)]
506 pub async fn from_parts(
507 self,
508 id: String,
509 key_pkcs8_der: PrivatePkcs8KeyDer<'_>,
510 directory_url: String,
511 ) -> Result<Account, Error> {
512 Ok(Account {
513 inner: Arc::new(AccountInner {
514 id,
515 key: Key::from_pkcs8_der(key_pkcs8_der)?,
516 client: Arc::new(Client::new(directory_url, self.http).await?),
517 }),
518 })
519 }
520
521 async fn create_inner(
522 account: &NewAccount<'_>,
523 (key, key_pkcs8): (Key, PrivatePkcs8KeyDer<'static>),
524 external_account: Option<&ExternalAccountKey>,
525 client: Client,
526 ) -> Result<(Account, AccountCredentials), Error> {
527 let payload = NewAccountPayload {
528 new_account: account,
529 external_account_binding: external_account
530 .map(|eak| {
531 JoseJson::new(
532 Some(&Jwk::new(&key.inner)),
533 eak.header(None, &client.directory.new_account),
534 eak,
535 )
536 })
537 .transpose()?,
538 };
539
540 let rsp = client
541 .post(Some(&payload), None, &key, &client.directory.new_account)
542 .await?;
543
544 let account_url = rsp
545 .parts
546 .headers
547 .get(LOCATION)
548 .and_then(|hv| hv.to_str().ok())
549 .map(|s| s.to_owned());
550
551 let _ = Problem::from_response(rsp).await?;
553 let id = account_url.ok_or("failed to get account URL")?;
554 let credentials = AccountCredentials {
555 id: id.clone(),
556 key_pkcs8,
557 directory: Some(client.directory_url.clone().unwrap()), urls: None,
561 };
562
563 let account = AccountInner {
564 client: Arc::new(client),
565 key,
566 id: id.clone(),
567 };
568
569 Ok((
570 Account {
571 inner: Arc::new(account),
572 },
573 credentials,
574 ))
575 }
576}
577
578pub struct Key {
580 rng: crypto::SystemRandom,
581 signing_algorithm: SigningAlgorithm,
582 inner: crypto::EcdsaKeyPair,
583 pub(crate) thumb: String,
584}
585
586impl Key {
587 #[deprecated(since = "0.8.3", note = "use `generate_pkcs8()` instead")]
589 pub fn generate() -> Result<(Self, PrivateKeyDer<'static>), Error> {
590 let (key, pkcs8) = Self::generate_pkcs8()?;
591 Ok((key, PrivateKeyDer::Pkcs8(pkcs8)))
592 }
593
594 pub fn generate_pkcs8() -> Result<(Self, PrivatePkcs8KeyDer<'static>), Error> {
596 let rng = crypto::SystemRandom::new();
597 let pkcs8 =
598 crypto::EcdsaKeyPair::generate_pkcs8(&crypto::ECDSA_P256_SHA256_FIXED_SIGNING, &rng)
599 .map_err(|_| Error::Crypto)?;
600 Ok((
601 Self::new(pkcs8.as_ref(), rng)?,
602 PrivatePkcs8KeyDer::from(pkcs8.as_ref().to_vec()),
603 ))
604 }
605
606 pub fn from_pkcs8_der(pkcs8_der: PrivatePkcs8KeyDer<'_>) -> Result<Self, Error> {
610 Self::new(pkcs8_der.secret_pkcs8_der(), crypto::SystemRandom::new())
611 }
612
613 fn new(pkcs8_der: &[u8], rng: crypto::SystemRandom) -> Result<Self, Error> {
614 let inner = crypto::p256_key_pair_from_pkcs8(pkcs8_der, &rng)?;
615 let thumb = BASE64_URL_SAFE_NO_PAD.encode(Jwk::thumb_sha256(&inner)?);
616 Ok(Self {
617 rng,
618 signing_algorithm: SigningAlgorithm::Es256,
619 inner,
620 thumb,
621 })
622 }
623}
624
625impl Signer for Key {
626 type Signature = crypto::Signature;
627
628 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
629 debug_assert!(nonce.is_some());
630 Header {
631 alg: self.signing_algorithm,
632 key: KeyOrKeyId::from_key(&self.inner),
633 nonce,
634 url,
635 }
636 }
637
638 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
639 self.inner
640 .sign(&self.rng, payload)
641 .map_err(|_| Error::Crypto)
642 }
643}
644
645pub struct ExternalAccountKey {
649 id: String,
650 key: crypto::hmac::Key,
651}
652
653impl ExternalAccountKey {
654 pub fn new(id: String, key_value: &[u8]) -> Self {
659 Self {
660 id,
661 key: crypto::hmac::Key::new(crypto::hmac::HMAC_SHA256, key_value),
662 }
663 }
664}
665
666impl Signer for ExternalAccountKey {
667 type Signature = crypto::hmac::Tag;
668
669 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
670 debug_assert_eq!(nonce, None);
671 Header {
672 alg: SigningAlgorithm::Hs256,
673 key: KeyOrKeyId::KeyId(&self.id),
674 nonce,
675 url,
676 }
677 }
678
679 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
680 Ok(crypto::hmac::sign(&self.key, payload))
681 }
682}