acme: switch from reqwest to hyper
This commit is contained in:
parent
3e64360c59
commit
40a561ffb5
|
@ -6,7 +6,8 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.13.0"
|
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"] }
|
ring = { version = "0.16.20", features = ["std"] }
|
||||||
serde = { version = "1.0.104", features = ["derive"] }
|
serde = { version = "1.0.104", features = ["derive"] }
|
||||||
serde_json = "1.0.78"
|
serde_json = "1.0.78"
|
||||||
|
|
68
src/lib.rs
68
src/lib.rs
|
@ -3,9 +3,9 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use base64::URL_SAFE_NO_PAD;
|
use base64::URL_SAFE_NO_PAD;
|
||||||
use reqwest::header::{CONTENT_TYPE, LOCATION};
|
use hyper::client::HttpConnector;
|
||||||
use reqwest::redirect::Policy;
|
use hyper::header::{CONTENT_TYPE, LOCATION};
|
||||||
use reqwest::{Body, Response};
|
use hyper::{Body, Method, Request, Response};
|
||||||
use ring::rand::SystemRandom;
|
use ring::rand::SystemRandom;
|
||||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
|
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
|
||||||
use serde::de::{DeserializeOwned, Error as _, Unexpected};
|
use serde::de::{DeserializeOwned, Error as _, Unexpected};
|
||||||
|
@ -67,11 +67,11 @@ impl Order {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.nonce = nonce_from_response(&rsp);
|
self.nonce = nonce_from_response(&rsp);
|
||||||
let status = rsp.status();
|
let body = hyper::body::to_bytes(Problem::from_response(rsp).await?).await?;
|
||||||
match status.is_client_error() || status.is_server_error() {
|
Ok(
|
||||||
false => Ok(rsp.text().await?),
|
String::from_utf8(body.to_vec())
|
||||||
true => Err(rsp.json::<Problem>().await?.into()),
|
.map_err(|_| "unable to decode certificate as UTF-8")?,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> {
|
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())
|
.and_then(|hv| hv.to_str().ok())
|
||||||
.map(|s| s.to_owned());
|
.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 {
|
Ok(Self {
|
||||||
inner: Arc::new(AccountInner {
|
inner: Arc::new(AccountInner {
|
||||||
client,
|
client,
|
||||||
|
@ -173,7 +174,7 @@ impl<'de> Deserialize<'de> for Account {
|
||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
client: Client {
|
client: Client {
|
||||||
client: client().map_err(D::Error::custom)?,
|
client: client(),
|
||||||
urls: creds.urls.clone(),
|
urls: creds.urls.clone(),
|
||||||
},
|
},
|
||||||
id: creds.id.clone(),
|
id: creds.id.clone(),
|
||||||
|
@ -218,7 +219,7 @@ impl AccountInner {
|
||||||
payload: Option<&impl Serialize>,
|
payload: Option<&impl Serialize>,
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
url: &str,
|
url: &str,
|
||||||
) -> Result<Response, Error> {
|
) -> Result<Response<Body>, Error> {
|
||||||
self.client.post(payload, nonce, self, url).await
|
self.client.post(payload, nonce, self, url).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,17 +241,18 @@ impl Signer for AccountInner {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Client {
|
struct Client {
|
||||||
client: reqwest::Client,
|
client: hyper::Client<hyper_rustls::HttpsConnector<HttpConnector>>,
|
||||||
urls: DirectoryUrls,
|
urls: DirectoryUrls,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
async fn new(server_url: &str) -> Result<Self, Error> {
|
async fn new(server_url: &str) -> Result<Self, Error> {
|
||||||
let client = client()?;
|
let client = client();
|
||||||
let urls = client.get(server_url).send().await?;
|
let rsp = client.get(server_url.parse()?).await?;
|
||||||
|
let body = hyper::body::to_bytes(rsp.into_body()).await?;
|
||||||
Ok(Client {
|
Ok(Client {
|
||||||
client,
|
client,
|
||||||
urls: urls.json().await?,
|
urls: serde_json::from_slice(&body)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,24 +262,31 @@ impl Client {
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
signer: &impl Signer,
|
signer: &impl Signer,
|
||||||
url: &str,
|
url: &str,
|
||||||
) -> Result<Response, Error> {
|
) -> Result<Response<Body>, Error> {
|
||||||
let nonce = match nonce {
|
let nonce = match nonce {
|
||||||
Some(nonce) => nonce,
|
Some(nonce) => nonce,
|
||||||
None => self.nonce().await?,
|
None => self.nonce().await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(self
|
let request = Request::builder()
|
||||||
.client
|
.method(Method::POST)
|
||||||
.post(url)
|
.uri(url)
|
||||||
.header(CONTENT_TYPE, JOSE_JSON)
|
.header(CONTENT_TYPE, JOSE_JSON)
|
||||||
.body(signer.signed_json(payload, &nonce, url)?)
|
.body(signer.signed_json(payload, &nonce, url)?)
|
||||||
.send()
|
.unwrap();
|
||||||
.await?)
|
|
||||||
|
Ok(self.client.request(request).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn nonce(&self) -> Result<String, Error> {
|
async fn nonce(&self) -> Result<String, Error> {
|
||||||
let future = self.client.head(&self.urls.new_nonce).send();
|
let request = Request::builder()
|
||||||
match nonce_from_response(&future.await?) {
|
.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),
|
Some(nonce) => Ok(nonce),
|
||||||
None => Err("no nonce found".into()),
|
None => Err("no nonce found".into()),
|
||||||
}
|
}
|
||||||
|
@ -372,7 +381,7 @@ trait Signer {
|
||||||
fn key(&self) -> &Key;
|
fn key(&self) -> &Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nonce_from_response(rsp: &Response) -> Option<String> {
|
fn nonce_from_response(rsp: &Response<Body>) -> Option<String> {
|
||||||
rsp.headers()
|
rsp.headers()
|
||||||
.get(REPLAY_NONCE)
|
.get(REPLAY_NONCE)
|
||||||
.and_then(|hv| String::from_utf8(hv.as_ref().to_vec()).ok())
|
.and_then(|hv| String::from_utf8(hv.as_ref().to_vec()).ok())
|
||||||
|
@ -385,8 +394,15 @@ fn base64(data: &impl Serialize) -> Result<String, serde_json::Error> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client() -> Result<reqwest::Client, reqwest::Error> {
|
fn client() -> hyper::Client<hyper_rustls::HttpsConnector<HttpConnector>> {
|
||||||
reqwest::Client::builder().redirect(Policy::none()).build()
|
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";
|
const JOSE_JSON: &str = "application/jose+json";
|
||||||
|
|
23
src/types.rs
23
src/types.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use base64::URL_SAFE_NO_PAD;
|
use base64::URL_SAFE_NO_PAD;
|
||||||
use reqwest::Response;
|
use hyper::{Body, Response};
|
||||||
use ring::digest::{digest, Digest, SHA256};
|
use ring::digest::{digest, Digest, SHA256};
|
||||||
use ring::signature::{EcdsaKeyPair, KeyPair};
|
use ring::signature::{EcdsaKeyPair, KeyPair};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
@ -19,7 +19,9 @@ pub enum Error {
|
||||||
#[error("invalid key bytes: {0}")]
|
#[error("invalid key bytes: {0}")]
|
||||||
CryptoKey(#[from] ring::error::KeyRejected),
|
CryptoKey(#[from] ring::error::KeyRejected),
|
||||||
#[error("HTTP request failure: {0}")]
|
#[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}")]
|
#[error("failed to (de)serialize JSON: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error("missing data: {0}")]
|
#[error("missing data: {0}")]
|
||||||
|
@ -48,16 +50,21 @@ pub struct Problem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Problem {
|
impl Problem {
|
||||||
pub(crate) async fn check<T: DeserializeOwned>(rsp: Response) -> Result<T, Error> {
|
pub(crate) async fn check<T: DeserializeOwned>(rsp: Response<Body>) -> Result<T, Error> {
|
||||||
Ok(Self::from_response(rsp).await?.json().await?)
|
Ok(serde_json::from_slice(
|
||||||
|
&hyper::body::to_bytes(Self::from_response(rsp).await?).await?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn from_response(rsp: Response) -> Result<Response, Error> {
|
pub(crate) async fn from_response(rsp: Response<Body>) -> Result<Body, Error> {
|
||||||
let status = rsp.status();
|
let status = rsp.status();
|
||||||
match status.is_client_error() || status.is_server_error() {
|
let body = rsp.into_body();
|
||||||
false => Ok(rsp),
|
if status.is_informational() || status.is_success() || status.is_redirection() {
|
||||||
true => Err(rsp.json::<Problem>().await?.into()),
|
return Ok(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body = hyper::body::to_bytes(body).await?;
|
||||||
|
Err(serde_json::from_slice::<Problem>(&body)?.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue