From 40a561ffb516ebe0fad2aa7c951b276fad5e981f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 10 May 2022 14:56:42 +0200 Subject: [PATCH] acme: switch from reqwest to hyper --- Cargo.toml | 3 ++- src/lib.rs | 68 ++++++++++++++++++++++++++++++++-------------------- src/types.rs | 23 +++++++++++------- 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1595204..dac0b34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ publish = false [dependencies] base64 = "0.13.0" -reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls-native-roots"] } +hyper = { version = "0.14.18", features = ["client", "http1", "http2"] } +hyper-rustls = { version = "0.23.0", default-features = false, features = ["http1", "http2", "native-tokio", "tls12"] } ring = { version = "0.16.20", features = ["std"] } serde = { version = "1.0.104", features = ["derive"] } serde_json = "1.0.78" diff --git a/src/lib.rs b/src/lib.rs index 25126a1..368ddba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use base64::URL_SAFE_NO_PAD; -use reqwest::header::{CONTENT_TYPE, LOCATION}; -use reqwest::redirect::Policy; -use reqwest::{Body, Response}; +use hyper::client::HttpConnector; +use hyper::header::{CONTENT_TYPE, LOCATION}; +use hyper::{Body, Method, Request, Response}; use ring::rand::SystemRandom; use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; use serde::de::{DeserializeOwned, Error as _, Unexpected}; @@ -67,11 +67,11 @@ impl Order { .await?; self.nonce = nonce_from_response(&rsp); - let status = rsp.status(); - match status.is_client_error() || status.is_server_error() { - false => Ok(rsp.text().await?), - true => Err(rsp.json::().await?.into()), - } + let body = hyper::body::to_bytes(Problem::from_response(rsp).await?).await?; + Ok( + String::from_utf8(body.to_vec()) + .map_err(|_| "unable to decode certificate as UTF-8")?, + ) } pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> { @@ -113,7 +113,8 @@ impl Account { .and_then(|hv| hv.to_str().ok()) .map(|s| s.to_owned()); - Problem::from_response(rsp).await?; + // The response redirects, we don't need the body + let _ = Problem::from_response(rsp).await?; Ok(Self { inner: Arc::new(AccountInner { client, @@ -173,7 +174,7 @@ impl<'de> Deserialize<'de> for Account { ) })?, client: Client { - client: client().map_err(D::Error::custom)?, + client: client(), urls: creds.urls.clone(), }, id: creds.id.clone(), @@ -218,7 +219,7 @@ impl AccountInner { payload: Option<&impl Serialize>, nonce: Option, url: &str, - ) -> Result { + ) -> Result, Error> { self.client.post(payload, nonce, self, url).await } } @@ -240,17 +241,18 @@ impl Signer for AccountInner { #[derive(Debug)] struct Client { - client: reqwest::Client, + client: hyper::Client>, urls: DirectoryUrls, } impl Client { async fn new(server_url: &str) -> Result { - let client = client()?; - let urls = client.get(server_url).send().await?; + let client = client(); + let rsp = client.get(server_url.parse()?).await?; + let body = hyper::body::to_bytes(rsp.into_body()).await?; Ok(Client { client, - urls: urls.json().await?, + urls: serde_json::from_slice(&body)?, }) } @@ -260,24 +262,31 @@ impl Client { nonce: Option, signer: &impl Signer, url: &str, - ) -> Result { + ) -> Result, Error> { let nonce = match nonce { Some(nonce) => nonce, None => self.nonce().await?, }; - Ok(self - .client - .post(url) + let request = Request::builder() + .method(Method::POST) + .uri(url) .header(CONTENT_TYPE, JOSE_JSON) .body(signer.signed_json(payload, &nonce, url)?) - .send() - .await?) + .unwrap(); + + Ok(self.client.request(request).await?) } async fn nonce(&self) -> Result { - let future = self.client.head(&self.urls.new_nonce).send(); - match nonce_from_response(&future.await?) { + let request = Request::builder() + .method(Method::HEAD) + .uri(&self.urls.new_nonce) + .body(Body::empty()) + .unwrap(); + + let rsp = self.client.request(request).await?; + match nonce_from_response(&rsp) { Some(nonce) => Ok(nonce), None => Err("no nonce found".into()), } @@ -372,7 +381,7 @@ trait Signer { fn key(&self) -> &Key; } -fn nonce_from_response(rsp: &Response) -> Option { +fn nonce_from_response(rsp: &Response) -> Option { rsp.headers() .get(REPLAY_NONCE) .and_then(|hv| String::from_utf8(hv.as_ref().to_vec()).ok()) @@ -385,8 +394,15 @@ fn base64(data: &impl Serialize) -> Result { )) } -fn client() -> Result { - reqwest::Client::builder().redirect(Policy::none()).build() +fn client() -> hyper::Client> { + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .https_only() + .enable_http1() + .enable_http2() + .build(); + + hyper::Client::builder().build(https) } const JOSE_JSON: &str = "application/jose+json"; diff --git a/src/types.rs b/src/types.rs index 68e5a8e..f098122 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,7 +1,7 @@ use std::fmt; use base64::URL_SAFE_NO_PAD; -use reqwest::Response; +use hyper::{Body, Response}; use ring::digest::{digest, Digest, SHA256}; use ring::signature::{EcdsaKeyPair, KeyPair}; use serde::de::DeserializeOwned; @@ -19,7 +19,9 @@ pub enum Error { #[error("invalid key bytes: {0}")] CryptoKey(#[from] ring::error::KeyRejected), #[error("HTTP request failure: {0}")] - Http(#[from] reqwest::Error), + Http(#[from] hyper::Error), + #[error("invalid URI: {0}")] + InvalidUri(#[from] hyper::http::uri::InvalidUri), #[error("failed to (de)serialize JSON: {0}")] Json(#[from] serde_json::Error), #[error("missing data: {0}")] @@ -48,16 +50,21 @@ pub struct Problem { } impl Problem { - pub(crate) async fn check(rsp: Response) -> Result { - Ok(Self::from_response(rsp).await?.json().await?) + pub(crate) async fn check(rsp: Response) -> Result { + Ok(serde_json::from_slice( + &hyper::body::to_bytes(Self::from_response(rsp).await?).await?, + )?) } - pub(crate) async fn from_response(rsp: Response) -> Result { + pub(crate) async fn from_response(rsp: Response) -> Result { let status = rsp.status(); - match status.is_client_error() || status.is_server_error() { - false => Ok(rsp), - true => Err(rsp.json::().await?.into()), + let body = rsp.into_body(); + if status.is_informational() || status.is_success() || status.is_redirection() { + return Ok(body); } + + let body = hyper::body::to_bytes(body).await?; + Err(serde_json::from_slice::(&body)?.into()) } }