1#![warn(unreachable_pub)]
4#![warn(missing_docs)]
5
6use std::{fmt, sync::Arc};
7
8use crypto::{Hasher, encoding::pkcs8, hmac::Hmac, p256::PrivateKey, 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 Ok(Self {
197 inner: Arc::new(AccountInner::from_credentials(credentials, reqwest::Client::new()).await?),
198 })
199 }
200
201 pub async fn from_credentials_and_http(
205 credentials: AccountCredentials,
206 http: reqwest::Client,
207 ) -> Result<Self, Error> {
208 Ok(Self {
209 inner: Arc::new(AccountInner::from_credentials(credentials, http).await?),
210 })
211 }
212
213 pub async fn from_parts(
218 id: String,
219 key_pkcs8_der: &[u8],
220 directory_url: &str,
221 http: reqwest::Client,
222 ) -> Result<Self, Error> {
223 Ok(Self {
224 inner: Arc::new(AccountInner {
225 id,
226 key: Key::from_pkcs8_der(key_pkcs8_der)?,
227 client: Client::new(directory_url, http).await?,
228 }),
229 })
230 }
231
232 #[cfg(feature = "hyper-rustls")]
237 pub async fn create(
238 account: &NewAccount<'_>,
239 server_url: &str,
240 external_account: Option<&ExternalAccountKey>,
241 ) -> Result<(Account, AccountCredentials), Error> {
242 Self::create_inner(
243 account,
244 external_account,
245 Client::new(server_url, Box::<DefaultClient>::default()).await?,
246 server_url,
247 )
248 .await
249 }
250
251 pub async fn create_with_http(
256 account: &NewAccount<'_>,
257 server_url: &str,
258 external_account: Option<&ExternalAccountKey>,
259 http: reqwest::Client,
260 ) -> Result<(Account, AccountCredentials), Error> {
261 Self::create_inner(account, external_account, Client::new(server_url, http).await?, server_url).await
262 }
263
264 async fn create_inner(
265 account: &NewAccount<'_>,
266 external_account: Option<&ExternalAccountKey>,
267 client: Client,
268 server_url: &str,
269 ) -> Result<(Account, AccountCredentials), Error> {
270 let (key, key_pkcs8) = Key::generate()?;
271 let payload = NewAccountPayload {
272 new_account: account,
273 external_account_binding: external_account
274 .map(|eak| {
275 JoseJson::new(
276 Some(&Jwk::new(&key.inner.public_key())),
277 eak.header(None, &client.urls.new_account),
278 eak,
279 )
280 })
281 .transpose()?,
282 };
283
284 let rsp = client
285 .post(Some(&payload), None, &key, &client.urls.new_account)
286 .await?;
287
288 let account_url = rsp
289 .headers()
290 .get(LOCATION)
291 .and_then(|hv| hv.to_str().ok())
292 .map(|s| s.to_owned());
293
294 let _ = Problem::from_response(rsp).await?;
296 let id = account_url.ok_or("failed to get account URL")?;
297 let credentials = AccountCredentials {
298 id: id.clone(),
299 key_pkcs8: key_pkcs8.to_vec(),
300 directory: Some(server_url.to_owned()),
301 urls: None,
304 };
305
306 let account = AccountInner {
307 client,
308 key,
309 id: id.clone(),
310 };
311
312 Ok((
313 Self {
314 inner: Arc::new(account),
315 },
316 credentials,
317 ))
318 }
319
320 pub async fn new_order<'a>(&'a self, order: &NewOrder<'_>) -> Result<Order, Error> {
324 let rsp = self
325 .inner
326 .post(Some(order), None, &self.inner.client.urls.new_order)
327 .await?;
328
329 let nonce = nonce_from_response(&rsp);
330 let order_url = rsp
331 .headers()
332 .get(LOCATION)
333 .and_then(|hv| hv.to_str().ok())
334 .map(|s| s.to_owned());
335
336 Ok(Order {
337 account: self.inner.clone(),
338 nonce,
339 state: Problem::check::<OrderState>(rsp).await?,
343 url: order_url.ok_or("no order URL found")?,
344 })
345 }
346
347 }
364
365struct AccountInner {
366 client: Client,
367 key: Key,
368 id: String,
369}
370
371impl AccountInner {
372 async fn from_credentials(credentials: AccountCredentials, http: reqwest::Client) -> Result<Self, Error> {
373 Ok(Self {
374 id: credentials.id,
375 key: Key::from_pkcs8_der(credentials.key_pkcs8.as_ref())?,
376 client: match (credentials.directory, credentials.urls) {
377 (Some(server_url), _) => Client::new(&server_url, http).await?,
378 (None, Some(urls)) => Client {
379 http,
380 urls,
381 },
382 (None, None) => return Err("no server URLs found".into()),
383 },
384 })
385 }
386
387 async fn get<T: DeserializeOwned>(&self, nonce: &mut Option<String>, url: &str) -> Result<T, Error> {
388 let rsp = self.post(None::<&Empty>, nonce.take(), url).await?;
389 *nonce = nonce_from_response(&rsp);
390 Problem::check(rsp).await
391 }
392
393 async fn post(
394 &self,
395 payload: Option<&impl Serialize>,
396 nonce: Option<String>,
397 url: &str,
398 ) -> Result<Response, Error> {
399 self.client.post(payload, nonce, self, url).await
400 }
401}
402
403impl Signer for AccountInner {
404 type Signature = <Key as Signer>::Signature;
405
406 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
407 debug_assert!(nonce.is_some());
408 Header {
409 alg: self.key.signing_algorithm,
410 key: KeyOrKeyId::KeyId(&self.id),
411 nonce,
412 url,
413 }
414 }
415
416 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
417 self.key.sign(payload)
418 }
419}
420
421struct Client {
422 http: reqwest::Client,
423 urls: DirectoryUrls,
424}
425
426impl Client {
427 async fn new(server_url: &str, http: reqwest::Client) -> Result<Self, Error> {
428 let res = http.get(server_url).send().await?;
432
433 let urls = res.json().await?;
435 Ok(Client {
436 http,
437 urls,
438 })
439 }
440
441 async fn post(
442 &self,
443 payload: Option<&impl Serialize>,
444 nonce: Option<String>,
445 signer: &impl Signer,
446 url: &str,
447 ) -> Result<Response, Error> {
448 let nonce = self.nonce(nonce).await?;
449 let body = JoseJson::new(payload, signer.header(Some(&nonce), url), signer)?;
450
451 let request = self
452 .http
453 .request(Method::POST, url)
454 .header(CONTENT_TYPE, JOSE_JSON)
455 .json(&body)
456 .build()?;
457 Ok(self.http.execute(request).await?)
465 }
466
467 async fn nonce(&self, nonce: Option<String>) -> Result<String, Error> {
468 if let Some(nonce) = nonce {
469 return Ok(nonce);
470 }
471
472 let rsp = self.http.head(&self.urls.new_nonce).send().await?;
473
474 if rsp.status() != StatusCode::OK {
478 return Err("error response from newNonce resource".into());
479 }
480
481 match nonce_from_response(&rsp) {
482 Some(nonce) => Ok(nonce),
483 None => Err("no nonce found in newNonce response".into()),
484 }
485 }
486}
487
488impl fmt::Debug for Client {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 f.debug_struct("Client")
491 .field("client", &"..")
492 .field("urls", &self.urls)
493 .finish()
494 }
495}
496
497struct Key {
498 signing_algorithm: SigningAlgorithm,
499 inner: PrivateKey,
500 thumb: String,
501}
502
503impl Key {
504 fn generate() -> Result<(Self, [u8; 138]), Error> {
505 let inner = PrivateKey::generate().map_err(|_| Error::Crypto)?;
506 let pkcs8_der = pkcs8::encode_p256_pkcs8_der(&inner).map_err(|_| Error::Crypto)?;
507 let thumb = base64::encode(&Jwk::thumb_sha256(&inner.public_key())?, base64::Alphabet::UrlNoPadding);
508
509 Ok((
510 Self {
511 signing_algorithm: SigningAlgorithm::Es256,
512 inner,
513 thumb,
514 },
515 pkcs8_der,
516 ))
517 }
518
519 fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self, Error> {
520 let inner = pkcs8::decode_p256_pkcs8_der(pkcs8_der).map_err(|_| Error::CryptoKey)?;
521 let thumb = base64::encode(&Jwk::thumb_sha256(&inner.public_key())?, base64::Alphabet::UrlNoPadding);
522
523 Ok(Self {
524 signing_algorithm: SigningAlgorithm::Es256,
525 inner,
526 thumb,
527 })
528 }
529}
530
531impl Signer for Key {
532 type Signature = [u8; 64];
533
534 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
535 debug_assert!(nonce.is_some());
536 Header {
537 alg: self.signing_algorithm,
538 key: KeyOrKeyId::from_key(&self.inner.public_key()),
539 nonce,
540 url,
541 }
542 }
543
544 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
545 self.inner.sign(payload).map_err(|_| Error::Crypto)
546 }
547}
548
549pub struct KeyAuthorization(String);
555
556impl KeyAuthorization {
557 fn new(challenge: &Challenge, key: &Key) -> Self {
558 Self(format!("{}.{}", challenge.token, &key.thumb))
559 }
560
561 pub fn as_str(&self) -> &str {
565 &self.0
566 }
567
568 pub fn digest(&self) -> impl AsRef<[u8]> {
574 let hash = Sha256::hash(self.0.as_bytes());
575 let mut out = [0u8; 32];
576 out.copy_from_slice(&hash.as_ref()[..32]);
577 out
578 }
579
580 pub fn dns_value(&self) -> String {
584 base64::encode(self.digest().as_ref(), base64::Alphabet::UrlNoPadding)
585 }
586}
587
588impl fmt::Debug for KeyAuthorization {
589 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590 f.debug_tuple("KeyAuthorization").finish()
591 }
592}
593
594pub struct ExternalAccountKey {
598 id: String,
599 key_value: Vec<u8>,
600}
601
602impl ExternalAccountKey {
603 pub fn new(id: String, key_value: &[u8]) -> Self {
605 Self {
606 id,
607 key_value: key_value.to_vec(),
608 }
609 }
610}
611
612impl Signer for ExternalAccountKey {
613 type Signature = [u8; 32];
614
615 fn header<'n, 'u: 'n, 's: 'u>(&'s self, nonce: Option<&'n str>, url: &'u str) -> Header<'n> {
616 debug_assert_eq!(nonce, None);
617 Header {
618 alg: SigningAlgorithm::Hs256,
619 key: KeyOrKeyId::KeyId(&self.id),
620 nonce,
621 url,
622 }
623 }
624
625 fn sign(&self, payload: &[u8]) -> Result<Self::Signature, Error> {
626 let h = Hmac::<Sha256>::mac(&self.key_value, payload);
627 let mut out = [0u8; 32];
628 out.copy_from_slice(&h.as_ref()[..32]);
629 Ok(out)
630 }
631}
632
633fn nonce_from_response(res: &Response) -> Option<String> {
640 res.headers()
641 .get(REPLAY_NONCE)
642 .map(|header| header.to_str().unwrap_or_default().to_string())
643}
644
645#[cfg(feature = "hyper-rustls")]
646struct DefaultClient(hyper::Client<hyper_rustls::HttpsConnector<HttpConnector>>);
647
648#[cfg(feature = "hyper-rustls")]
649impl HttpClient for DefaultClient {
650 fn request(&self, req: Request<Body>) -> Pin<Box<dyn Future<Output = hyper::Result<Response<Body>>> + Send>> {
651 Box::pin(self.0.request(req))
652 }
653}
654
655#[cfg(feature = "hyper-rustls")]
656impl Default for DefaultClient {
657 fn default() -> Self {
658 Self(
659 hyper::Client::builder().build(
660 hyper_rustls::HttpsConnectorBuilder::new()
661 .with_native_roots()
662 .https_only()
663 .enable_http1()
664 .enable_http2()
665 .build(),
666 ),
667 )
668 }
669}
670
671const JOSE_JSON: &str = "application/jose+json";
693const REPLAY_NONCE: &str = "Replay-Nonce";
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[tokio::test]
700 async fn deserialize_old_credentials() -> Result<(), Error> {
701 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"}}"#;
702 Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
703 Ok(())
704 }
705
706 #[tokio::test]
707 async fn deserialize_new_credentials() -> Result<(), Error> {
708 const CREDENTIALS: &str = r#"{"id":"id","key_pkcs8":"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJVWC_QzOTCS5vtsJp2IG-UDc8cdDfeoKtxSZxaznM-mhRANCAAQenCPoGgPFTdPJ7VLLKt56RxPlYT1wNXnHc54PEyBg3LxKaH0-sJkX0mL8LyPEdsfL_Oz4TxHkWLJGrXVtNhfH","directory":"https://acme-staging-v02.api.letsencrypt.org/directory"}"#;
709 Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
710 Ok(())
711 }
712}