Fix header strings

This commit is contained in:
asonix 2020-01-17 18:35:30 -06:00
parent 1202b7ff43
commit 421eb3520e
8 changed files with 122 additions and 15 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization" name = "http-signature-normalization"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.1.1" version = "0.2.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -15,6 +15,7 @@ edition = "2018"
members = [ members = [
"http-signature-normalization-actix", "http-signature-normalization-actix",
"http-signature-normalization-http", "http-signature-normalization-http",
"http-signature-normalization-reqwest",
"http-signature-normalization-warp", "http-signature-normalization-warp",
] ]

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-actix" name = "http-signature-normalization-actix"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.1.0" version = "0.2.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -29,7 +29,7 @@ actix-web = "1.0"
base64 = { version = "0.10", optional = true } base64 = { version = "0.10", optional = true }
failure = "0.1" failure = "0.1"
futures = "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 } sha2 = { version = "0.8", optional = true }
sha3 = { version = "0.8", optional = true } sha3 = { version = "0.8", optional = true }

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-http" name = "http-signature-normalization-http"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.2.0" version = "0.3.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "../LICENSE" license-file = "../LICENSE"
readme = "../README.md" readme = "../README.md"
@ -13,4 +13,4 @@ edition = "2018"
[dependencies] [dependencies]
http = "0.2" http = "0.2"
http-signature-normalization = { version = "0.1.0", path = ".." } http-signature-normalization = { version = "0.2.0", path = ".." }

View file

@ -0,0 +1,13 @@
[package]
name = "http-signature-normalization-reqwest"
version = "0.1.0"
authors = ["asonix <asonix@asonix.dog>"]
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"

View file

@ -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<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E>
where
Self: Sized,
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
K: Display;
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E>
where
Self: Sized,
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
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<F, E, K>(
mut self,
config: &Config,
key_id: K,
f: F,
) -> Result<Self, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
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<F, E, K>(mut self, config: &Config, key_id: K, f: F) -> Result<Self, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
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<F, E, K>(req: &Request, config: &Config, key_id: K, f: F) -> Result<Signed, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
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)
}

View file

@ -15,7 +15,7 @@ base64 = { version = "0.11.0", optional = true }
bytes = "0.5.3" bytes = "0.5.3"
futures = "0.3.1" futures = "0.3.1"
http = "0.2.0" 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 = { version = "1.0.104", features = ["derive"], optional = true }
serde_json = { version = "1.0.44", optional = true } serde_json = { version = "1.0.44", optional = true }
serde_urlencoded = { version = "0.6.1", optional = true } serde_urlencoded = { version = "0.6.1", optional = true }

View file

@ -38,9 +38,8 @@ pub struct ParseBodyError;
pub fn verify_bytes( pub fn verify_bytes(
verifier: impl DigestVerify + Clone + Send, verifier: impl DigestVerify + Clone + Send,
) -> impl Filter<Extract = (Bytes,), Error = Rejection> + Clone { ) -> impl Filter<Extract = (Bytes,), Error = Rejection> + Clone {
parse_digest_header() parse_digest_header().and(body::bytes()).and_then(
.and(body::bytes()) move |parts: Vec<DigestPart>, bytes: Bytes| {
.and_then(move |parts: Vec<DigestPart>, bytes: Bytes| {
let mut verifier = verifier.clone(); let mut verifier = verifier.clone();
async move { async move {
if verifier.verify(&parts, &bytes) { if verifier.verify(&parts, &bytes) {
@ -49,11 +48,12 @@ pub fn verify_bytes(
Err(warp::reject::custom(VerifyError)) Err(warp::reject::custom(VerifyError))
} }
} }
}) },
)
} }
pub fn verify_json<T>( pub fn verify_json<T>(
verifier: impl DigestVerify + Clone + Send verifier: impl DigestVerify + Clone + Send,
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone ) -> impl Filter<Extract = (T,), Error = Rejection> + Clone
where where
T: serde::de::DeserializeOwned, T: serde::de::DeserializeOwned,
@ -117,7 +117,9 @@ where
V: DigestVerify, V: DigestVerify,
{ {
fn verify(&mut self, parts: &[DigestPart], payload: &[u8]) -> bool { 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, W: DigestVerify,
{ {
fn verify(&mut self, parts: &[DigestPart], payload: &[u8]) -> bool { 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)
} }
} }

View file

@ -35,14 +35,14 @@ impl Signed {
/// ///
/// Done manually, it would look like `format!("Signature: {}", signed.signature_header())` /// Done manually, it would look like `format!("Signature: {}", signed.signature_header())`
pub fn signature_header(self) -> String { 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 /// 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())` /// Done manually, it would look like `format!("Authorization: {}", signed.authorization_header())`
pub fn authorization_header(self) -> String { pub fn authorization_header(self) -> String {
self.into_header() format!("Signature {}", self.into_header())
} }
fn into_header(self) -> String { fn into_header(self) -> String {