Run signature creation in spawn_blocking

Also move -reqwest fully to async trait
This commit is contained in:
asonix 2023-07-06 12:11:27 -05:00
parent c7cb3ce602
commit ac25b1d951
3 changed files with 120 additions and 116 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-reqwest" name = "http-signature-normalization-reqwest"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.8.0" version = "0.9.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license = "AGPL-3.0" license = "AGPL-3.0"
readme = "README.md" readme = "README.md"
@ -22,6 +22,7 @@ name = "client"
required-features = ["sha-2"] required-features = ["sha-2"]
[dependencies] [dependencies]
async-trait = "0.1.71"
base64 = { version = "0.13", optional = true } base64 = { version = "0.13", optional = true }
http-signature-normalization = { version = "0.7.0", path = ".." } http-signature-normalization = { version = "0.7.0", path = ".." }
httpdate = "1.0.2" httpdate = "1.0.2"

View file

@ -1,6 +1,6 @@
use crate::{Config, Sign, SignError}; use crate::{Config, Sign, SignError};
use reqwest::{Body, Request, RequestBuilder}; use reqwest::{Body, Request, RequestBuilder};
use std::{fmt::Display, future::Future, pin::Pin}; use std::fmt::Display;
#[cfg(feature = "sha-2")] #[cfg(feature = "sha-2")]
mod sha2; mod sha2;
@ -21,107 +21,105 @@ pub trait DigestCreate {
/// It generates HTTP Signatures after the Digest header has been added, in order to have /// It generates HTTP Signatures after the Digest header has been added, in order to have
/// verification that the body has not been tampered with, or that the request can't be replayed by /// verification that the body has not been tampered with, or that the request can't be replayed by
/// a malicious entity /// a malicious entity
#[async_trait::async_trait]
pub trait SignExt: Sign { pub trait SignExt: Sign {
fn authorization_signature_with_digest<F, E, K, D, V>( async fn authorization_signature_with_digest<F, E, K, D, V>(
self, self,
config: Config, config: Config,
key_id: K, key_id: K,
digest: D, digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>> + Send>> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display + Send + 'static, K: Display + Send + 'static,
D: DigestCreate + Send + 'static, D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static, V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized; Self: Sized;
fn signature_with_digest<F, E, K, D, V>( async fn signature_with_digest<F, E, K, D, V>(
self, self,
config: Config, config: Config,
key_id: K, key_id: K,
digest: D, digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>> + Send>> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display + Send + 'static, K: Display + Send + 'static,
D: DigestCreate + Send + 'static, D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static, V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized; Self: Sized;
} }
#[async_trait::async_trait]
impl SignExt for RequestBuilder { impl SignExt for RequestBuilder {
fn authorization_signature_with_digest<F, E, K, D, V>( async fn authorization_signature_with_digest<F, E, K, D, V>(
self, self,
config: Config, config: Config,
key_id: K, key_id: K,
mut digest: D, mut digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>> + Send>> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display + Send + 'static, K: Display + Send + 'static,
D: DigestCreate + Send + 'static, D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static, V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized,
{ {
Box::pin(async move { let (v, digest) = tokio::task::spawn_blocking(move || {
let (v, digest) = tokio::task::spawn_blocking(move || { let digest = digest.compute(v.as_ref());
let digest = digest.compute(v.as_ref()); (v, digest)
(v, digest)
})
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.authorization_signature(&config, key_id, f)?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
}) })
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.authorization_signature(&config, key_id, f)
.await?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
} }
fn signature_with_digest<F, E, K, D, V>( async fn signature_with_digest<F, E, K, D, V>(
self, self,
config: Config, config: Config,
key_id: K, key_id: K,
mut digest: D, mut digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>> + Send>> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display + Send + 'static, K: Display + Send + 'static,
D: DigestCreate + Send + 'static, D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static, V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized,
{ {
Box::pin(async move { let (v, digest) = tokio::task::spawn_blocking(move || {
let (v, digest) = tokio::task::spawn_blocking(move || { let digest = digest.compute(v.as_ref());
let digest = digest.compute(v.as_ref()); (v, digest)
(v, digest)
})
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.signature(&config, key_id, f)?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
}) })
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.signature(&config, key_id, f)
.await?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
} }
} }
@ -130,75 +128,74 @@ mod middleware {
use super::{Config, DigestCreate, Sign, SignError, SignExt}; use super::{Config, DigestCreate, Sign, SignError, SignExt};
use reqwest::{Body, Request}; use reqwest::{Body, Request};
use reqwest_middleware::RequestBuilder; use reqwest_middleware::RequestBuilder;
use std::{fmt::Display, future::Future, pin::Pin}; use std::fmt::Display;
#[async_trait::async_trait]
impl SignExt for RequestBuilder { impl SignExt for RequestBuilder {
fn authorization_signature_with_digest<F, E, K, D, V>( async fn authorization_signature_with_digest<F, E, K, D, V>(
self, self,
config: Config, config: Config,
key_id: K, key_id: K,
mut digest: D, mut digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>> + Send>> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display + Send + 'static, K: Display + Send + 'static,
D: DigestCreate + Send + 'static, D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static, V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized, Self: Sized,
{ {
Box::pin(async move { let (v, digest) = tokio::task::spawn_blocking(move || {
let (v, digest) = tokio::task::spawn_blocking(move || { let digest = digest.compute(v.as_ref());
let digest = digest.compute(v.as_ref()); (v, digest)
(v, digest)
})
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.authorization_signature(&config, key_id, f)?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
}) })
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.authorization_signature(&config, key_id, f)
.await?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
} }
fn signature_with_digest<F, E, K, D, V>( async fn signature_with_digest<F, E, K, D, V>(
self, self,
config: Config, config: Config,
key_id: K, key_id: K,
mut digest: D, mut digest: D,
v: V, v: V,
f: F, f: F,
) -> Pin<Box<dyn Future<Output = Result<Request, E>> + Send>> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E> + Send + 'static, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display + Send + 'static, K: Display + Send + 'static,
D: DigestCreate + Send + 'static, D: DigestCreate + Send + 'static,
V: AsRef<[u8]> + Into<Body> + Send + 'static, V: AsRef<[u8]> + Into<Body> + Send + 'static,
Self: Sized, Self: Sized,
{ {
Box::pin(async move { let (v, digest) = tokio::task::spawn_blocking(move || {
let (v, digest) = tokio::task::spawn_blocking(move || { let digest = digest.compute(v.as_ref());
let digest = digest.compute(v.as_ref()); (v, digest)
(v, digest)
})
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.signature(&config, key_id, f)?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
}) })
.await
.map_err(|_| SignError::Canceled)?;
let mut req = self
.header("Digest", format!("{}={}", D::NAME, digest))
.signature(&config, key_id, f)
.await?;
*req.body_mut() = Some(Body::from(v.as_ref().to_vec()));
Ok(req)
} }
} }
} }

View file

@ -39,9 +39,10 @@ pub struct Config {
} }
/// A trait implemented by the reqwest RequestBuilder type to add an HTTP Signature to the request /// A trait implemented by the reqwest RequestBuilder type to add an HTTP Signature to the request
#[async_trait::async_trait]
pub trait Sign { pub trait Sign {
/// Add an Authorization Signature to the request /// Add an Authorization Signature to the request
fn authorization_signature<F, E, K>( async fn authorization_signature<F, E, K>(
self, self,
config: &Config, config: &Config,
key_id: K, key_id: K,
@ -49,17 +50,17 @@ pub trait Sign {
) -> Result<Request, E> ) -> Result<Request, E>
where where
Self: Sized, Self: Sized,
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display; K: Display + Send;
/// Add a Signature to the request /// Add a Signature to the request
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E> async fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E>
where where
Self: Sized, Self: Sized,
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display; K: Display + Send;
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -156,20 +157,21 @@ impl Config {
} }
} }
#[async_trait::async_trait]
impl Sign for RequestBuilder { impl Sign for RequestBuilder {
fn authorization_signature<F, E, K>( async fn authorization_signature<F, E, K>(
self, self,
config: &Config, config: &Config,
key_id: K, key_id: K,
f: F, f: F,
) -> Result<Request, E> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display, K: Display + Send,
{ {
let mut request = self.build()?; let mut request = self.build()?;
let signed = prepare(&mut request, config, key_id, f)?; let signed = prepare(&mut request, config, key_id, f).await?;
let auth_header = signed.authorization_header(); let auth_header = signed.authorization_header();
request.headers_mut().insert( request.headers_mut().insert(
@ -180,14 +182,14 @@ impl Sign for RequestBuilder {
Ok(request) Ok(request)
} }
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E> async fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display, K: Display + Send,
{ {
let mut request = self.build()?; let mut request = self.build()?;
let signed = prepare(&mut request, config, key_id, f)?; let signed = prepare(&mut request, config, key_id, f).await?;
let sig_header = signed.signature_header(); let sig_header = signed.signature_header();
@ -200,11 +202,11 @@ impl Sign for RequestBuilder {
} }
} }
fn prepare<F, E, K>(req: &mut Request, config: &Config, key_id: K, f: F) -> Result<Signed, E> async fn prepare<F, E, K>(req: &mut Request, config: &Config, key_id: K, f: F) -> Result<Signed, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError>, E: From<SignError> + Send + 'static,
K: Display, K: Display + Send,
{ {
if config.set_date && !req.headers().contains_key("date") { if config.set_date && !req.headers().contains_key("date") {
req.headers_mut().insert( req.headers_mut().insert(
@ -246,7 +248,10 @@ where
.begin_sign(req.method().as_str(), &path_and_query, bt) .begin_sign(req.method().as_str(), &path_and_query, bt)
.map_err(SignError::from)?; .map_err(SignError::from)?;
let signed = unsigned.sign(key_id.to_string(), f)?; let key_string = key_id.to_string();
let signed = tokio::task::spawn_blocking(move || unsigned.sign(key_string, f))
.await
.map_err(|_| SignError::Canceled)??;
Ok(signed) Ok(signed)
} }
@ -257,20 +262,21 @@ mod middleware {
use reqwest_middleware::RequestBuilder; use reqwest_middleware::RequestBuilder;
use std::fmt::Display; use std::fmt::Display;
#[async_trait::async_trait]
impl Sign for RequestBuilder { impl Sign for RequestBuilder {
fn authorization_signature<F, E, K>( async fn authorization_signature<F, E, K>(
self, self,
config: &Config, config: &Config,
key_id: K, key_id: K,
f: F, f: F,
) -> Result<Request, E> ) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display, K: Display + Send,
{ {
let mut request = self.build()?; let mut request = self.build()?;
let signed = prepare(&mut request, config, key_id, f)?; let signed = prepare(&mut request, config, key_id, f).await?;
let auth_header = signed.authorization_header(); let auth_header = signed.authorization_header();
request.headers_mut().insert( request.headers_mut().insert(
@ -281,14 +287,14 @@ mod middleware {
Ok(request) Ok(request)
} }
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E> async fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Request, E>
where where
F: FnOnce(&str) -> Result<String, E>, F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<SignError> + From<reqwest::Error>, E: From<SignError> + From<reqwest::Error> + Send + 'static,
K: Display, K: Display + Send,
{ {
let mut request = self.build()?; let mut request = self.build()?;
let signed = prepare(&mut request, config, key_id, f)?; let signed = prepare(&mut request, config, key_id, f).await?;
let sig_header = signed.signature_header(); let sig_header = signed.signature_header();