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 revoke_url = match self.inner.client.directory.revoke_cert.as_deref() {
157 Some(url) => url,
158 None => return Err("no revokeCert URL found".into()),
163 };
164
165 let rsp = self.inner.post(Some(payload), None, revoke_url).await?;
166 let _ = Problem::from_response(rsp).await?;
168 Ok(())
169 }
170
171 #[cfg(feature = "time")]
186 pub async fn renewal_info(
187 &self,
188 certificate_id: &CertificateIdentifier<'_>,
189 ) -> Result<(RenewalInfo, Duration), Error> {
190 let renewal_info_url = match self.inner.client.directory.renewal_info.as_deref() {
191 Some(url) => url,
192 None => return Err(Error::Unsupported("ACME renewal information (ARI)")),
193 };
194
195 let request = Request::builder()
198 .method(Method::GET)
199 .uri(format!("{renewal_info_url}/{certificate_id}"))
200 .header(USER_AGENT, CRATE_USER_AGENT)
201 .body(BodyWrapper::default())?;
202
203 let rsp = self.inner.client.http.request(request).await?;
204 let Some(retry_after) = retry_after(&rsp) else {
205 return Err(Error::Str("missing Retry-After header"));
206 };
207
208 let delay = retry_after
209 .duration_since(SystemTime::now())
210 .unwrap_or(Duration::ZERO);
211
212 Ok((Problem::check::<RenewalInfo>(rsp).await?, delay))
213 }
214
215 pub async fn update_key(&mut self) -> Result<AccountCredentials, Error> {
224 let new_key_url = match self.inner.client.directory.key_change.as_deref() {
225 Some(url) => url,
226 None => return Err("Account key rollover not supported by ACME CA".into()),
227 };
228
229 #[derive(Debug, Serialize)]
230 struct NewKey<'a> {
231 account: &'a str,
232 #[serde(rename = "oldKey")]
233 old_key: Jwk,
234 }
235
236 let (new_key, new_key_pkcs8) = Key::generate_pkcs8()?;
237 let mut header = new_key.header(Some("nonce"), new_key_url);
238 header.nonce = None;
239 let payload = NewKey {
240 account: &self.inner.id,
241 old_key: Jwk::new(&self.inner.key.inner),
242 };
243
244 let body = JoseJson::new(Some(&payload), header, &new_key)?;
245 let rsp = self.inner.post(Some(&body), None, new_key_url).await?;
246 let _ = Problem::from_response(rsp).await?;
247
248 self.inner = Arc::new(AccountInner {
249 client: self.inner.client.clone(),
250 key: new_key,
251 id: self.inner.id.clone(),
252 });
253
254 let (directory, urls) = match &self.inner.client.directory_url {
255 Some(directory_url) => (Some(directory_url.clone()), None),
256 None => (None, Some(self.inner.client.directory.clone())),
257 };
258
259 Ok(AccountCredentials {
260 id: self.inner.id.clone(),
261 key_pkcs8: new_key_pkcs8,
262 directory,
263 urls,
264 })
265 }
266
267 pub async fn update_contacts<'a>(&self, contacts: &'a [&'a str]) -> Result<(), Error> {
275 #[derive(Debug, Serialize)]
276 struct Contacts<'a> {
277 contact: &'a [&'a str],
278 }
279
280 let payload = Contacts { contact: contacts };
281 let rsp = self
282 .inner
283 .post(Some(&payload), None, &self.inner.id)
284 .await?;
285
286 #[derive(Debug, Deserialize)]
287 struct Account {
288 status: AuthorizationStatus,
289 }
290
291 let response = Problem::check::<Account>(rsp).await?;
292 match response.status {
293 AuthorizationStatus::Valid => Ok(()),
294 _ => Err("Unexpected account status after updating contact information".into()),
295 }
296 }
297
298 pub async fn deactivate(self) -> Result<(), Error> {
308 #[derive(Serialize)]
309 struct DeactivateRequest<'a> {
310 status: &'a str,
311 }
312
313 let _ = self
314 .inner
315 .post(
316 Some(&DeactivateRequest {
317 status: "deactivated",
318 }),
319 None,
320 self.id(),
321 )
322 .await?;
323
324 Ok(())
325 }
326
327 pub fn profiles(&self) -> impl Iterator<Item = ProfileMeta<'_>> {
329 self.inner
330 .client
331 .directory
332 .meta
333 .profiles
334 .iter()
335 .map(|(name, description)| ProfileMeta { name, description })
336 }
337
338 pub fn id(&self) -> &str {
340 &self.inner.id
341 }
342
343 pub fn key_thumbprint(&self) -> &str {
345 &self.inner.key.thumb
346 }
347}
348
349pub(crate) struct AccountInner {
350 client: Arc<Client>,
351 pub(crate) key: Key,
352 id: String,
353}
354
355impl AccountInner {
356 async fn from_credentials(
357 credentials: AccountCredentials,
358 http: Box<dyn HttpClient>,
359 ) -> Result<Self, Error> {
360 Ok(Self {
361 id: credentials.id,
362 key: Key::from_pkcs8_der(credentials.key_pkcs8)?,
363 client: Arc::new(match (credentials.directory, credentials.urls) {
364 (Some(directory_url), _) => Client::new(directory_url, http).await?,
365 (None, Some(directory)) => Client {
366 http,
367 directory,
368 directory_url: None,
369 },
370 (None, None) => return Err("no server URLs found".into()),
371 }),
372 })
373 }
374
375 pub(crate) async fn get<T: DeserializeOwned>(
376 &self,
377 nonce: &mut Option<String>,
378 url: &str,
379 ) -> Result<T, Error> {
380 let rsp = self.post(None::<&Empty>, nonce.take(), url).await?;
381 *nonce = nonce_from_response(&rsp);
382 Problem::check(rsp).await
383 }
384
385 pub(crate) async fn post(
386 &self,
387 payload: Option<&impl Serialize>,
388 nonce: Option<String>,
389 url: &str,
390 ) -> Result<BytesResponse, Error> {
391 self.client.post(payload, nonce, self, url).await
392 }
393}
394
395impl Signer for AccountInner {
396 type Signature = <Key as Signer>::Signature;
397
398 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
399 debug_assert!(nonce.is_some());
400 Header {
401 alg: self.key.signing_algorithm,
402 key: KeyOrKeyId::KeyId(&self.id),
403 nonce,
404 url,
405 }
406 }
407
408 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
409 self.key.sign(payload)
410 }
411}
412
413pub struct AccountBuilder {
417 http: Box<dyn HttpClient>,
418}
419
420impl AccountBuilder {
421 #[allow(clippy::wrong_self_convention)]
425 pub async fn from_credentials(self, credentials: AccountCredentials) -> Result<Account, Error> {
426 Ok(Account {
427 inner: Arc::new(AccountInner::from_credentials(credentials, self.http).await?),
428 })
429 }
430
431 pub async fn create(
436 self,
437 account: &NewAccount<'_>,
438 directory_url: String,
439 external_account: Option<&ExternalAccountKey>,
440 ) -> Result<(Account, AccountCredentials), Error> {
441 let (key, key_pkcs8) = Key::generate_pkcs8()?;
442 Self::create_inner(
443 account,
444 (key, key_pkcs8),
445 external_account,
446 Client::new(directory_url, self.http).await?,
447 )
448 .await
449 }
450
451 pub async fn from_key(
458 self,
459 key: (Key, PrivateKeyDer<'static>),
460 directory_url: String,
461 ) -> Result<(Account, AccountCredentials), Error> {
462 Self::create_inner(
463 &NewAccount {
464 contact: &[],
465 terms_of_service_agreed: true,
466 only_return_existing: true,
467 },
468 match key {
469 (key, PrivateKeyDer::Pkcs8(pkcs8)) => (key, pkcs8),
470 _ => return Err("unsupported key format, expected PKCS#8".into()),
471 },
472 None,
473 Client::new(directory_url, self.http).await?,
474 )
475 .await
476 }
477
478 #[allow(clippy::wrong_self_convention)]
483 pub async fn from_parts(
484 self,
485 id: String,
486 key_pkcs8_der: PrivatePkcs8KeyDer<'_>,
487 directory_url: String,
488 ) -> Result<Account, Error> {
489 Ok(Account {
490 inner: Arc::new(AccountInner {
491 id,
492 key: Key::from_pkcs8_der(key_pkcs8_der)?,
493 client: Arc::new(Client::new(directory_url, self.http).await?),
494 }),
495 })
496 }
497
498 async fn create_inner(
499 account: &NewAccount<'_>,
500 (key, key_pkcs8): (Key, PrivatePkcs8KeyDer<'static>),
501 external_account: Option<&ExternalAccountKey>,
502 client: Client,
503 ) -> Result<(Account, AccountCredentials), Error> {
504 let payload = NewAccountPayload {
505 new_account: account,
506 external_account_binding: external_account
507 .map(|eak| {
508 JoseJson::new(
509 Some(&Jwk::new(&key.inner)),
510 eak.header(None, &client.directory.new_account),
511 eak,
512 )
513 })
514 .transpose()?,
515 };
516
517 let rsp = client
518 .post(Some(&payload), None, &key, &client.directory.new_account)
519 .await?;
520
521 let account_url = rsp
522 .parts
523 .headers
524 .get(LOCATION)
525 .and_then(|hv| hv.to_str().ok())
526 .map(|s| s.to_owned());
527
528 let _ = Problem::from_response(rsp).await?;
530 let id = account_url.ok_or("failed to get account URL")?;
531 let credentials = AccountCredentials {
532 id: id.clone(),
533 key_pkcs8,
534 directory: Some(client.directory_url.clone().unwrap()), urls: None,
538 };
539
540 let account = AccountInner {
541 client: Arc::new(client),
542 key,
543 id: id.clone(),
544 };
545
546 Ok((
547 Account {
548 inner: Arc::new(account),
549 },
550 credentials,
551 ))
552 }
553}
554
555pub struct Key {
557 rng: crypto::SystemRandom,
558 signing_algorithm: SigningAlgorithm,
559 inner: crypto::EcdsaKeyPair,
560 pub(crate) thumb: String,
561}
562
563impl Key {
564 #[deprecated(since = "0.8.3", note = "use `generate_pkcs8()` instead")]
566 pub fn generate() -> Result<(Self, PrivateKeyDer<'static>), Error> {
567 let (key, pkcs8) = Self::generate_pkcs8()?;
568 Ok((key, PrivateKeyDer::Pkcs8(pkcs8)))
569 }
570
571 pub fn generate_pkcs8() -> Result<(Self, PrivatePkcs8KeyDer<'static>), Error> {
573 let rng = crypto::SystemRandom::new();
574 let pkcs8 =
575 crypto::EcdsaKeyPair::generate_pkcs8(&crypto::ECDSA_P256_SHA256_FIXED_SIGNING, &rng)
576 .map_err(|_| Error::Crypto)?;
577 Ok((
578 Self::new(pkcs8.as_ref(), rng)?,
579 PrivatePkcs8KeyDer::from(pkcs8.as_ref().to_vec()),
580 ))
581 }
582
583 pub fn from_pkcs8_der(pkcs8_der: PrivatePkcs8KeyDer<'_>) -> Result<Self, Error> {
587 Self::new(pkcs8_der.secret_pkcs8_der(), crypto::SystemRandom::new())
588 }
589
590 fn new(pkcs8_der: &[u8], rng: crypto::SystemRandom) -> Result<Self, Error> {
591 let inner = crypto::p256_key_pair_from_pkcs8(pkcs8_der, &rng)?;
592 let thumb = BASE64_URL_SAFE_NO_PAD.encode(Jwk::thumb_sha256(&inner)?);
593 Ok(Self {
594 rng,
595 signing_algorithm: SigningAlgorithm::Es256,
596 inner,
597 thumb,
598 })
599 }
600}
601
602impl Signer for Key {
603 type Signature = crypto::Signature;
604
605 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
606 debug_assert!(nonce.is_some());
607 Header {
608 alg: self.signing_algorithm,
609 key: KeyOrKeyId::from_key(&self.inner),
610 nonce,
611 url,
612 }
613 }
614
615 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
616 self.inner
617 .sign(&self.rng, payload)
618 .map_err(|_| Error::Crypto)
619 }
620}
621
622pub struct ExternalAccountKey {
626 id: String,
627 key: crypto::hmac::Key,
628}
629
630impl ExternalAccountKey {
631 pub fn new(id: String, key_value: &[u8]) -> Self {
636 Self {
637 id,
638 key: crypto::hmac::Key::new(crypto::hmac::HMAC_SHA256, key_value),
639 }
640 }
641}
642
643impl Signer for ExternalAccountKey {
644 type Signature = crypto::hmac::Tag;
645
646 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
647 debug_assert_eq!(nonce, None);
648 Header {
649 alg: SigningAlgorithm::Hs256,
650 key: KeyOrKeyId::KeyId(&self.id),
651 nonce,
652 url,
653 }
654 }
655
656 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
657 Ok(crypto::hmac::sign(&self.key, payload))
658 }
659}