diff --git a/Cargo.toml b/Cargo.toml index 8c1d628..dc0ea02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.1.1" +version = "0.2.0" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -15,6 +15,7 @@ edition = "2018" members = [ "http-signature-normalization-actix", "http-signature-normalization-http", + "http-signature-normalization-reqwest", "http-signature-normalization-warp", ] diff --git a/http-signature-normalization-actix/Cargo.toml b/http-signature-normalization-actix/Cargo.toml index f01d66c..d130820 100644 --- a/http-signature-normalization-actix/Cargo.toml +++ b/http-signature-normalization-actix/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-actix" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.1.0" +version = "0.2.0" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -29,7 +29,7 @@ actix-web = "1.0" base64 = { version = "0.10", optional = true } failure = "0.1" futures = "0.1" -http-signature-normalization = { version = "0.1.0", path = ".." } +http-signature-normalization = { version = "0.2.0", path = ".." } sha2 = { version = "0.8", optional = true } sha3 = { version = "0.8", optional = true } diff --git a/http-signature-normalization-http/Cargo.toml b/http-signature-normalization-http/Cargo.toml index d568f42..c50857c 100644 --- a/http-signature-normalization-http/Cargo.toml +++ b/http-signature-normalization-http/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-http" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.2.0" +version = "0.3.0" authors = ["asonix "] license-file = "../LICENSE" readme = "../README.md" @@ -13,4 +13,4 @@ edition = "2018" [dependencies] http = "0.2" -http-signature-normalization = { version = "0.1.0", path = ".." } +http-signature-normalization = { version = "0.2.0", path = ".." } diff --git a/http-signature-normalization-reqwest/Cargo.toml b/http-signature-normalization-reqwest/Cargo.toml new file mode 100644 index 0000000..6723fd1 --- /dev/null +++ b/http-signature-normalization-reqwest/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "http-signature-normalization-reqwest" +version = "0.1.0" +authors = ["asonix "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4.10" +http = "0.2.0" +http-signature-normalization = { version = "0.2.0", path = ".." } +reqwest = "0.10.1" diff --git a/http-signature-normalization-reqwest/src/lib.rs b/http-signature-normalization-reqwest/src/lib.rs new file mode 100644 index 0000000..3a9bea7 --- /dev/null +++ b/http-signature-normalization-reqwest/src/lib.rs @@ -0,0 +1,88 @@ +use chrono::Duration; +use http_signature_normalization::create::Signed; +use reqwest::{ + header::{InvalidHeaderValue, ToStrError}, + Request, +}; +use std::fmt::Display; + +pub struct Config(http_signature_normalization::Config); + +pub trait Sign { + fn authorization_signature(self, config: &Config, key_id: K, f: F) -> Result + where + Self: Sized, + F: FnOnce(&str) -> Result, + E: From + From, + K: Display; + + fn signature(self, config: &Config, key_id: K, f: F) -> Result + where + Self: Sized, + F: FnOnce(&str) -> Result, + E: From + From, + K: Display; +} + +impl Config { + pub fn new(expires_after: Duration) -> Self { + Config(http_signature_normalization::Config { expires_after }) + } +} + +impl Sign for Request { + fn authorization_signature( + mut self, + config: &Config, + key_id: K, + f: F, + ) -> Result + where + F: FnOnce(&str) -> Result, + E: From + From, + K: Display, + { + let signed = prepare(&self, config, key_id, f)?; + + let auth_header = signed.authorization_header(); + self.headers_mut() + .insert("Authorization", auth_header.parse()?); + Ok(self) + } + + fn signature(mut self, config: &Config, key_id: K, f: F) -> Result + where + F: FnOnce(&str) -> Result, + E: From + From, + K: Display, + { + let signed = prepare(&self, config, key_id, f)?; + + let sig_header = signed.signature_header(); + self.headers_mut().insert("Signature", sig_header.parse()?); + Ok(self) + } +} + +fn prepare(req: &Request, config: &Config, key_id: K, f: F) -> Result +where + F: FnOnce(&str) -> Result, + E: From + From, + K: Display, +{ + let mut bt = std::collections::BTreeMap::new(); + for (k, v) in req.headers().iter() { + bt.insert(k.as_str().to_owned(), v.to_str()?.to_owned()); + } + let path_and_query = if let Some(query) = req.url().query() { + format!("{}?{}", req.url().path(), query) + } else { + req.url().path().to_string() + }; + let unsigned = config + .0 + .begin_sign(req.method().as_str(), &path_and_query, bt); + + let signed = unsigned.sign(key_id.to_string(), f)?; + Ok(signed) +} diff --git a/http-signature-normalization-warp/Cargo.toml b/http-signature-normalization-warp/Cargo.toml index 25aa401..c71762d 100644 --- a/http-signature-normalization-warp/Cargo.toml +++ b/http-signature-normalization-warp/Cargo.toml @@ -15,7 +15,7 @@ base64 = { version = "0.11.0", optional = true } bytes = "0.5.3" futures = "0.3.1" http = "0.2.0" -http-signature-normalization-http = { version = "0.2.0", path = "../http-signature-normalization-http" } +http-signature-normalization-http = { version = "0.3.0", path = "../http-signature-normalization-http" } serde = { version = "1.0.104", features = ["derive"], optional = true } serde_json = { version = "1.0.44", optional = true } serde_urlencoded = { version = "0.6.1", optional = true } diff --git a/http-signature-normalization-warp/src/digest/mod.rs b/http-signature-normalization-warp/src/digest/mod.rs index 155f4ab..c84615a 100644 --- a/http-signature-normalization-warp/src/digest/mod.rs +++ b/http-signature-normalization-warp/src/digest/mod.rs @@ -38,9 +38,8 @@ pub struct ParseBodyError; pub fn verify_bytes( verifier: impl DigestVerify + Clone + Send, ) -> impl Filter + Clone { - parse_digest_header() - .and(body::bytes()) - .and_then(move |parts: Vec, bytes: Bytes| { + parse_digest_header().and(body::bytes()).and_then( + move |parts: Vec, bytes: Bytes| { let mut verifier = verifier.clone(); async move { if verifier.verify(&parts, &bytes) { @@ -49,11 +48,12 @@ pub fn verify_bytes( Err(warp::reject::custom(VerifyError)) } } - }) + }, + ) } pub fn verify_json( - verifier: impl DigestVerify + Clone + Send + verifier: impl DigestVerify + Clone + Send, ) -> impl Filter + Clone where T: serde::de::DeserializeOwned, @@ -117,7 +117,9 @@ where V: DigestVerify, { fn verify(&mut self, parts: &[DigestPart], payload: &[u8]) -> bool { - self.0.verify(parts, payload) || self.1.verify(parts, payload) || self.2.verify(parts, payload) + self.0.verify(parts, payload) + || self.1.verify(parts, payload) + || self.2.verify(parts, payload) } } @@ -129,7 +131,10 @@ where W: DigestVerify, { fn verify(&mut self, parts: &[DigestPart], payload: &[u8]) -> bool { - self.0.verify(parts, payload) || self.1.verify(parts, payload) || self.2.verify(parts, payload) || self.3.verify(parts, payload) + self.0.verify(parts, payload) + || self.1.verify(parts, payload) + || self.2.verify(parts, payload) + || self.3.verify(parts, payload) } } diff --git a/src/create.rs b/src/create.rs index 0ab8edf..82f48d8 100644 --- a/src/create.rs +++ b/src/create.rs @@ -35,14 +35,14 @@ impl Signed { /// /// Done manually, it would look like `format!("Signature: {}", signed.signature_header())` pub fn signature_header(self) -> String { - format!("Signature {}", self.into_header()) + self.into_header() } /// Turn the Signed type into a String that can be used as the Authorization Header /// /// Done manually, it would look like `format!("Authorization: {}", signed.authorization_header())` pub fn authorization_header(self) -> String { - self.into_header() + format!("Signature {}", self.into_header()) } fn into_header(self) -> String {