1#![warn(unreachable_pub)]
4#![warn(missing_docs)]
5
6use std::{fmt, sync::Arc};
7
8use crypto::{Hasher, encoding::pkcs8, hmac::Hmac, p256::SecretKey, sha2::Sha256};
9use reqwest::{
10 Method, Response, StatusCode,
11 header::{CONTENT_TYPE, LOCATION},
12};
13use serde::{Serialize, de::DeserializeOwned};
14
15mod types;
16pub use types::{
17 AccountCredentials,
18 Authorization,
19 AuthorizationStatus,
20 Challenge,
21 ChallengeType,
22 Error,
23 Identifier,
24 LetsEncrypt,
25 NewAccount,
26 NewOrder,
27 OrderState,
28 OrderStatus,
29 Problem,
30 RevocationReason,
31 ZeroSsl, };
33use types::{
34 DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload, Signer,
35 SigningAlgorithm,
36};
37
38pub struct Order {
46 account: Arc<AccountInner>,
47 nonce: Option<String>,
48 url: String,
49 state: OrderState,
50}
51
52impl Order {
53 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 pub fn key_authorization(&self, challenge: &Challenge) -> KeyAuthorization {
81 KeyAuthorization::new(challenge, &self.account.key)
82 }
83
84 pub async fn finalize(&mut self, csr_der: &[u8]) -> Result<(), Error> {
90 let rsp = self
91 .account
92 .post(Some(&FinalizeRequest::new(csr_der)), self.nonce.take(), &self.state.finalize)
93 .await?;
94
95 self.nonce = nonce_from_response(&rsp);
96 self.state = Problem::check::<OrderState>(rsp).await?;
97 Ok(())
98 }
99
100 pub async fn certificate(&mut self) -> Result<Option<String>, Error> {
108 if matches!(self.state.status, OrderStatus::Processing) {
109 let rsp = self.account.post(None::<&Empty>, self.nonce.take(), &self.url).await?;
110 self.nonce = nonce_from_response(&rsp);
111 self.state = Problem::check::<OrderState>(rsp).await?;
112 }
113
114 if let Some(error) = &self.state.error {
115 return Err(Error::Api(error.clone()));
116 } else if self.state.status == OrderStatus::Processing {
117 return Ok(None);
118 } else if self.state.status != OrderStatus::Valid {
119 return Err(Error::Str("invalid order state"));
120 }
121
122 let cert_url = match &self.state.certificate {
123 Some(cert_url) => cert_url,
124 None => return Err(Error::Str("no certificate URL found")),
125 };
126
127 let rsp = self.account.post(None::<&Empty>, self.nonce.take(), cert_url).await?;
128
129 let body = Problem::from_response(rsp).await?;
131 Ok(Some(
132 String::from_utf8(body.to_vec()).map_err(|_| "unable to decode certificate as UTF-8")?,
133 ))
134 }
135
136 pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> {
140 let rsp = self
141 .account
142 .post(Some(&Empty {}), self.nonce.take(), challenge_url)
143 .await?;
144
145 self.nonce = nonce_from_response(&rsp);
146 let _ = Problem::check::<Challenge>(rsp).await?;
147 Ok(())
148 }
149
150 pub async fn challenge(&mut self, challenge_url: &str) -> Result<Challenge, Error> {
152 self.account.get(&mut self.nonce, challenge_url).await
153 }
154
155 pub async fn refresh(&mut self) -> Result<&OrderState, Error> {
157 let rsp = self.account.post(None::<&Empty>, self.nonce.take(), &self.url).await?;
158
159 self.nonce = nonce_from_response(&rsp);
160 self.state = Problem::check::<OrderState>(rsp).await?;
161 Ok(&self.state)
162 }
163
164 pub fn state(&mut self) -> &OrderState {
168 &self.state
169 }
170
171 pub fn url(&self) -> &str {
173 &self.url
174 }
175}
176
177#[derive(Clone)]
186pub struct Account {
187 inner: Arc<AccountInner>,
188}
189
190impl Account {
191 pub async fn from_credentials(credentials: AccountCredentials) -> Result<Self, Error> {
196 crypto_rustls::install_default_rustls_provider();
197 Ok(Self {
198 inner: Arc::new(AccountInner::from_credentials(credentials, reqwest::Client::new()).await?),
199 })
200 }
201
202 pub async fn from_credentials_and_http(
206 credentials: AccountCredentials,
207 http: reqwest::Client,
208 ) -> Result<Self, Error> {
209 Ok(Self {
210 inner: Arc::new(AccountInner::from_credentials(credentials, http).await?),
211 })
212 }
213
214 pub async fn from_parts(
219 id: String,
220 key_pkcs8_der: &[u8],
221 directory_url: &str,
222 http: reqwest::Client,
223 ) -> Result<Self, Error> {
224 Ok(Self {
225 inner: Arc::new(AccountInner {
226 id,
227 key: Key::from_pkcs8_der(key_pkcs8_der)?,
228 client: Client::new(directory_url, http).await?,
229 }),
230 })
231 }
232
233 pub async fn create(
257 account: &NewAccount<'_>,
258 server_url: &str,
259 external_account: Option<&ExternalAccountKey>,
260 http: reqwest::Client,
261 ) -> Result<(Account, AccountCredentials), Error> {
262 Self::create_inner(account, external_account, Client::new(server_url, http).await?, server_url).await
263 }
264
265 async fn create_inner(
266 account: &NewAccount<'_>,
267 external_account: Option<&ExternalAccountKey>,
268 client: Client,
269 server_url: &str,
270 ) -> Result<(Account, AccountCredentials), Error> {
271 let (key, key_pkcs8) = Key::generate()?;
272 let payload = NewAccountPayload {
273 new_account: account,
274 external_account_binding: external_account
275 .map(|eak| {
276 JoseJson::new(
277 Some(&Jwk::new(&key.inner.public_key())),
278 eak.header(None, &client.urls.new_account),
279 eak,
280 )
281 })
282 .transpose()?,
283 };
284
285 let rsp = client
286 .post(Some(&payload), None, &key, &client.urls.new_account)
287 .await?;
288
289 let account_url = rsp
290 .headers()
291 .get(LOCATION)
292 .and_then(|hv| hv.to_str().ok())
293 .map(|s| s.to_owned());
294
295 let _ = Problem::from_response(rsp).await?;
297 let id = account_url.ok_or("failed to get account URL")?;
298 let credentials = AccountCredentials {
299 id: id.clone(),
300 key_pkcs8: key_pkcs8.to_vec(),
301 directory: Some(server_url.to_owned()),
302 urls: None,
305 };
306
307 let account = AccountInner {
308 client,
309 key,
310 id: id.clone(),
311 };
312
313 Ok((
314 Self {
315 inner: Arc::new(account),
316 },
317 credentials,
318 ))
319 }
320
321 pub async fn new_order<'a>(&'a self, order: &NewOrder<'_>) -> Result<Order, Error> {
325 let rsp = self
326 .inner
327 .post(Some(order), None, &self.inner.client.urls.new_order)
328 .await?;
329
330 let nonce = nonce_from_response(&rsp);
331 let order_url = rsp
332 .headers()
333 .get(LOCATION)
334 .and_then(|hv| hv.to_str().ok())
335 .map(|s| s.to_owned());
336
337 Ok(Order {
338 account: self.inner.clone(),
339 nonce,
340 state: Problem::check::<OrderState>(rsp).await?,
344 url: order_url.ok_or("no order URL found")?,
345 })
346 }
347
348 }
365
366struct AccountInner {
367 client: Client,
368 key: Key,
369 id: String,
370}
371
372impl AccountInner {
373 async fn from_credentials(credentials: AccountCredentials, http: reqwest::Client) -> Result<Self, Error> {
374 Ok(Self {
375 id: credentials.id,
376 key: Key::from_pkcs8_der(credentials.key_pkcs8.as_ref())?,
377 client: match (credentials.directory, credentials.urls) {
378 (Some(server_url), _) => Client::new(&server_url, http).await?,
379 (None, Some(urls)) => Client {
380 http,
381 urls,
382 },
383 (None, None) => return Err("no server URLs found".into()),
384 },
385 })
386 }
387
388 async fn get<T: DeserializeOwned>(&self, nonce: &mut Option<String>, url: &str) -> Result<T, Error> {
389 let rsp = self.post(None::<&Empty>, nonce.take(), url).await?;
390 *nonce = nonce_from_response(&rsp);
391 Problem::check(rsp).await
392 }
393
394 async fn post(
395 &self,
396 payload: Option<&impl Serialize>,
397 nonce: Option<String>,
398 url: &str,
399 ) -> Result<Response, Error> {
400 self.client.post(payload, nonce, self, url).await
401 }
402}
403
404impl Signer for AccountInner {
405 type Signature = <Key as Signer>::Signature;
406
407 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
408 debug_assert!(nonce.is_some());
409 Header {
410 alg: self.key.signing_algorithm,
411 key: KeyOrKeyId::KeyId(&self.id),
412 nonce,
413 url,
414 }
415 }
416
417 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
418 self.key.sign(payload)
419 }
420}
421
422struct Client {
423 http: reqwest::Client,
424 urls: DirectoryUrls,
425}
426
427impl Client {
428 async fn new(server_url: &str, http: reqwest::Client) -> Result<Self, Error> {
429 let res = http.get(server_url).send().await?;
433
434 let urls = res.json().await?;
436 Ok(Client {
437 http,
438 urls,
439 })
440 }
441
442 async fn post(
443 &self,
444 payload: Option<&impl Serialize>,
445 nonce: Option<String>,
446 signer: &impl Signer,
447 url: &str,
448 ) -> Result<Response, Error> {
449 let nonce = self.nonce(nonce).await?;
450 let body = JoseJson::new(payload, signer.header(Some(&nonce), url), signer)?;
451
452 let request = self
453 .http
454 .request(Method::POST, url)
455 .header(CONTENT_TYPE, JOSE_JSON)
456 .json(&body)
457 .build()?;
458 Ok(self.http.execute(request).await?)
466 }
467
468 async fn nonce(&self, nonce: Option<String>) -> Result<String, Error> {
469 if let Some(nonce) = nonce {
470 return Ok(nonce);
471 }
472
473 let rsp = self.http.head(&self.urls.new_nonce).send().await?;
474
475 if rsp.status() != StatusCode::OK {
479 return Err("error response from newNonce resource".into());
480 }
481
482 match nonce_from_response(&rsp) {
483 Some(nonce) => Ok(nonce),
484 None => Err("no nonce found in newNonce response".into()),
485 }
486 }
487}
488
489impl fmt::Debug for Client {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 f.debug_struct("Client")
492 .field("client", &"..")
493 .field("urls", &self.urls)
494 .finish()
495 }
496}
497
498struct Key {
499 signing_algorithm: SigningAlgorithm,
500 inner: SecretKey,
501 thumb: String,
502}
503
504impl Key {
505 fn generate() -> Result<(Self, [u8; 138]), Error> {
506 let inner = SecretKey::generate().map_err(|_| Error::Crypto)?;
507 let pkcs8_der = pkcs8::encode_p256_pkcs8_der(&inner).map_err(|_| Error::Crypto)?;
508 let thumb = base64::encode(Jwk::thumb_sha256(&inner.public_key())?, base64::Alphabet::UrlNoPadding);
509
510 Ok((
511 Self {
512 signing_algorithm: SigningAlgorithm::Es256,
513 inner,
514 thumb,
515 },
516 pkcs8_der,
517 ))
518 }
519
520 fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self, Error> {
521 let inner = pkcs8::decode_p256_pkcs8_der(pkcs8_der).map_err(|_| Error::CryptoKey)?;
522 let thumb = base64::encode(Jwk::thumb_sha256(&inner.public_key())?, base64::Alphabet::UrlNoPadding);
523
524 Ok(Self {
525 signing_algorithm: SigningAlgorithm::Es256,
526 inner,
527 thumb,
528 })
529 }
530}
531
532impl Signer for Key {
533 type Signature = [u8; 64];
534
535 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
536 debug_assert!(nonce.is_some());
537 Header {
538 alg: self.signing_algorithm,
539 key: KeyOrKeyId::from_key(&self.inner.public_key()),
540 nonce,
541 url,
542 }
543 }
544
545 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
546 self.inner.sign(payload).map_err(|_| Error::Crypto)
547 }
548}
549
550pub struct KeyAuthorization(String);
556
557impl KeyAuthorization {
558 fn new(challenge: &Challenge, key: &Key) -> Self {
559 Self(format!("{}.{}", challenge.token, &key.thumb))
560 }
561
562 pub fn as_str(&self) -> &str {
566 &self.0
567 }
568
569 pub fn digest(&self) -> impl AsRef<[u8]> {
575 let hash = Sha256::hash(self.0.as_bytes());
576 let mut out = [0u8; 32];
577 out.copy_from_slice(&hash.as_ref()[..32]);
578 out
579 }
580
581 pub fn dns_value(&self) -> String {
585 base64::encode(self.digest().as_ref(), base64::Alphabet::UrlNoPadding)
586 }
587}
588
589impl fmt::Debug for KeyAuthorization {
590 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591 f.debug_tuple("KeyAuthorization").finish()
592 }
593}
594
595pub struct ExternalAccountKey {
599 id: String,
600 key_value: Vec<u8>,
601}
602
603impl ExternalAccountKey {
604 pub fn new(id: String, key_value: &[u8]) -> Self {
606 Self {
607 id,
608 key_value: key_value.to_vec(),
609 }
610 }
611}
612
613impl Signer for ExternalAccountKey {
614 type Signature = [u8; 32];
615
616 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
617 debug_assert_eq!(nonce, None);
618 Header {
619 alg: SigningAlgorithm::Hs256,
620 key: KeyOrKeyId::KeyId(&self.id),
621 nonce,
622 url,
623 }
624 }
625
626 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
627 let h = Hmac::<Sha256>::mac(&self.key_value, payload);
628 let mut out = [0u8; 32];
629 out.copy_from_slice(&h.as_ref()[..32]);
630 Ok(out)
631 }
632}
633
634fn nonce_from_response(res: &Response) -> Option<String> {
641 res.headers()
642 .get(REPLAY_NONCE)
643 .map(|header| header.to_str().unwrap_or_default().to_string())
644}
645
646const JOSE_JSON: &str = "application/jose+json";
694const REPLAY_NONCE: &str = "Replay-Nonce";
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699
700 #[tokio::test]
701 async fn deserialize_old_credentials() -> Result<(), Error> {
702 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"}}"#;
703 Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
704 Ok(())
705 }
706
707 #[tokio::test]
708 async fn deserialize_new_credentials() -> Result<(), Error> {
709 const CREDENTIALS: &str = r#"{"id":"id","key_pkcs8":"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJVWC_QzOTCS5vtsJp2IG-UDc8cdDfeoKtxSZxaznM-mhRANCAAQenCPoGgPFTdPJ7VLLKt56RxPlYT1wNXnHc54PEyBg3LxKaH0-sJkX0mL8LyPEdsfL_Oz4TxHkWLJGrXVtNhfH","directory":"https://acme-staging-v02.api.letsencrypt.org/directory"}"#;
710 Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
711 Ok(())
712 }
713}