diff --git a/.gitignore b/.gitignore index 6936990..3833e07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +**/target/ /target **/*.rs.bk Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 309ceea..c2d388e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = [ + "http-signature-normalization-http", + "http-signature-normalization-actix", +] + [dependencies] base64 = "0.10" chrono = "0.4" diff --git a/http-signature-normalization-actix/Cargo.toml b/http-signature-normalization-actix/Cargo.toml new file mode 100644 index 0000000..a095ece --- /dev/null +++ b/http-signature-normalization-actix/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-signature-normalization-actix" +description = "An HTTP Signatures library that leaves the signing to you" +version = "0.1.0" +authors = ["asonix "] +license-file = "../LICENSE" +readme = "../README.md" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "1.0" +http-signature-normalization = { version = "0.1.0", path = ".." } diff --git a/http-signature-normalization-actix/src/create.rs b/http-signature-normalization-actix/src/create.rs new file mode 100644 index 0000000..12033e7 --- /dev/null +++ b/http-signature-normalization-actix/src/create.rs @@ -0,0 +1,40 @@ +use actix_web::http::header::{ + HeaderMap, HeaderName, HeaderValue, InvalidHeaderValue, AUTHORIZATION, +}; + +pub struct Signed { + pub signed: http_signature_normalization::create::Signed, +} + +pub struct Unsigned { + pub unsigned: http_signature_normalization::create::Unsigned, +} + +impl Signed { + pub fn signature_header(self, hm: &mut HeaderMap) -> Result<(), InvalidHeaderValue> { + hm.insert( + HeaderName::from_static("Signature"), + HeaderValue::from_str(&self.signed.signature_header())?, + ); + + Ok(()) + } + + pub fn authorization_header(self, hm: &mut HeaderMap) -> Result<(), InvalidHeaderValue> { + hm.insert( + AUTHORIZATION, + HeaderValue::from_str(&self.signed.authorization_header())?, + ); + Ok(()) + } +} + +impl Unsigned { + pub fn sign(self, key_id: String, f: F) -> Result + where + F: FnOnce(&str) -> Result, E>, + { + let signed = self.unsigned.sign(key_id, f)?; + Ok(Signed { signed }) + } +} diff --git a/http-signature-normalization-actix/src/lib.rs b/http-signature-normalization-actix/src/lib.rs new file mode 100644 index 0000000..820b401 --- /dev/null +++ b/http-signature-normalization-actix/src/lib.rs @@ -0,0 +1,217 @@ +use actix_web::{ + client::ClientRequest, + dev::ServiceRequest, + http::{ + header::{HeaderMap, InvalidHeaderValue, ToStrError}, + uri::PathAndQuery, + Method, + }, + HttpRequest, +}; +use std::{ + collections::BTreeMap, + error::Error, + fmt::{self, Display}, +}; + +pub mod prelude { + pub use crate::{verify::Unverified, Config, Sign, Verify, VerifyError}; + + pub use actix_web::http::header::{InvalidHeaderValue, ToStrError}; +} + +pub mod create; + +pub mod verify { + pub use http_signature_normalization::verify::{ + Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified, + ValidateError, + }; +} + +use self::{ + create::{Signed, Unsigned}, + verify::Unverified, +}; + +pub trait Verify { + fn begin_verify(&self, config: &Config) -> Result; +} + +pub trait Sign { + fn authorization_signature(self, config: &Config, key_id: K, f: F) -> Result + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + Self: Sized; + + fn signature(self, config: &Config, key_id: K, f: F) -> Result + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + Self: Sized; +} + +#[derive(Clone, Default)] +pub struct Config { + pub config: http_signature_normalization::Config, +} + +#[derive(Debug)] +pub enum VerifyError { + Sig(http_signature_normalization::VerifyError), + Header(ToStrError), +} + +impl Config { + pub fn begin_sign( + &self, + method: &Method, + path_and_query: Option<&PathAndQuery>, + headers: HeaderMap, + ) -> Result { + let headers = headers + .iter() + .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) + .collect::, ToStrError>>()?; + + let path_and_query = path_and_query + .map(|p| p.to_string()) + .unwrap_or(String::from("/")); + + let unsigned = self + .config + .begin_sign(&method.to_string(), &path_and_query, headers); + + Ok(Unsigned { unsigned }) + } + + pub fn begin_verify( + &self, + method: &Method, + path_and_query: Option<&PathAndQuery>, + headers: HeaderMap, + ) -> Result { + let headers = headers + .iter() + .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) + .collect::, ToStrError>>()?; + + let path_and_query = path_and_query + .map(|p| p.to_string()) + .unwrap_or(String::from("/")); + + let unverified = self + .config + .begin_verify(&method.to_string(), &path_and_query, headers)?; + + Ok(unverified) + } +} + +impl Verify for HttpRequest { + fn begin_verify(&self, config: &Config) -> Result { + config.begin_verify( + self.method(), + self.uri().path_and_query(), + self.headers().clone(), + ) + } +} + +impl Verify for ServiceRequest { + fn begin_verify(&self, config: &Config) -> Result { + config.begin_verify( + self.method(), + self.uri().path_and_query(), + self.headers().clone(), + ) + } +} + +impl Sign for ClientRequest { + fn authorization_signature( + mut self, + config: &Config, + key_id: K, + f: F, + ) -> Result + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + { + let signed = prepare(&self, config, key_id, f)?; + signed.authorization_header(self.headers_mut())?; + Ok(self) + } + + fn signature(mut self, config: &Config, key_id: K, f: F) -> Result + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + { + let signed = prepare(&self, config, key_id, f)?; + signed.signature_header(self.headers_mut())?; + Ok(self) + } +} + +fn prepare(request: &ClientRequest, config: &Config, key_id: K, f: F) -> Result +where + F: FnOnce(&str) -> Result, E>, + E: From, + K: Display, +{ + let unsigned = config.begin_sign( + request.get_method(), + request.get_uri().path_and_query(), + request.headers().clone(), + )?; + + let key_id = key_id.to_string(); + + let signed = unsigned.sign(key_id, f)?; + + Ok(signed) +} + +impl fmt::Display for VerifyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + VerifyError::Sig(ref e) => write!(f, "Sig error, {}", e), + VerifyError::Header(ref e) => write!(f, "Header error, {}", e), + } + } +} + +impl Error for VerifyError { + fn description(&self) -> &str { + match *self { + VerifyError::Sig(ref e) => e.description(), + VerifyError::Header(ref e) => e.description(), + } + } + + fn source(&self) -> Option<&(dyn Error + 'static)> { + match *self { + VerifyError::Sig(ref e) => Some(e), + VerifyError::Header(ref e) => Some(e), + } + } +} + +impl From for VerifyError { + fn from(e: http_signature_normalization::VerifyError) -> Self { + VerifyError::Sig(e) + } +} + +impl From for VerifyError { + fn from(e: ToStrError) -> Self { + VerifyError::Header(e) + } +} diff --git a/http-signature-normalization-http/Cargo.toml b/http-signature-normalization-http/Cargo.toml new file mode 100644 index 0000000..24f8356 --- /dev/null +++ b/http-signature-normalization-http/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-signature-normalization-http" +description = "An HTTP Signatures library that leaves the signing to you" +version = "0.1.0" +authors = ["asonix "] +license-file = "../LICENSE" +readme = "../README.md" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +http = "0.1" +http-signature-normalization = { version = "0.1.0", path = ".." } diff --git a/http-signature-normalization-http/src/create.rs b/http-signature-normalization-http/src/create.rs new file mode 100644 index 0000000..1496d96 --- /dev/null +++ b/http-signature-normalization-http/src/create.rs @@ -0,0 +1,38 @@ +use http::header::{HeaderMap, HeaderName, HeaderValue, InvalidHeaderValue, AUTHORIZATION}; + +pub struct Signed { + pub signed: http_signature_normalization::create::Signed, +} + +pub struct Unsigned { + pub unsigned: http_signature_normalization::create::Unsigned, +} + +impl Signed { + pub fn signature_header(self, hm: &mut HeaderMap) -> Result<(), InvalidHeaderValue> { + hm.insert( + HeaderName::from_static("Signature"), + HeaderValue::from_str(&self.signed.signature_header())?, + ); + + Ok(()) + } + + pub fn authorization_header(self, hm: &mut HeaderMap) -> Result<(), InvalidHeaderValue> { + hm.insert( + AUTHORIZATION, + HeaderValue::from_str(&self.signed.authorization_header())?, + ); + Ok(()) + } +} + +impl Unsigned { + pub fn sign(self, key_id: String, f: F) -> Result + where + F: FnOnce(&str) -> Result, E>, + { + let signed = self.unsigned.sign(key_id, f)?; + Ok(Signed { signed }) + } +} diff --git a/http-signature-normalization-http/src/lib.rs b/http-signature-normalization-http/src/lib.rs new file mode 100644 index 0000000..9f71c67 --- /dev/null +++ b/http-signature-normalization-http/src/lib.rs @@ -0,0 +1,128 @@ +use http::{ + header::{HeaderMap, ToStrError}, + method::Method, + uri::PathAndQuery, +}; +use std::{collections::BTreeMap, error::Error, fmt}; + +use self::{create::Unsigned, verify::Unverified}; + +pub mod prelude { + pub use http::{ + header::{HeaderMap, InvalidHeaderValue, ToStrError}, + method::Method, + uri::PathAndQuery, + }; + + pub use crate::create::{Signed, Unsigned}; + + pub use crate::verify::{ + Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified, + ValidateError, + }; + + pub use crate::{Config, VerifyError}; +} + +pub mod create; + +pub mod verify { + pub use http_signature_normalization::verify::{ + Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified, + ValidateError, + }; +} + +#[derive(Clone, Default)] +pub struct Config { + pub config: http_signature_normalization::Config, +} + +#[derive(Debug)] +pub enum VerifyError { + Sig(http_signature_normalization::VerifyError), + Header(ToStrError), +} + +impl Config { + pub fn begin_sign( + &self, + method: &Method, + path_and_query: Option<&PathAndQuery>, + headers: HeaderMap, + ) -> Result { + let headers = headers + .iter() + .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) + .collect::, ToStrError>>()?; + + let path_and_query = path_and_query + .map(|p| p.to_string()) + .unwrap_or(String::from("/")); + + let unsigned = self + .config + .begin_sign(&method.to_string(), &path_and_query, headers); + + Ok(Unsigned { unsigned }) + } + + pub fn begin_verify( + &self, + method: &Method, + path_and_query: Option<&PathAndQuery>, + headers: HeaderMap, + ) -> Result { + let headers = headers + .iter() + .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) + .collect::, ToStrError>>()?; + + let path_and_query = path_and_query + .map(|p| p.to_string()) + .unwrap_or(String::from("/")); + + let unverified = self + .config + .begin_verify(&method.to_string(), &path_and_query, headers)?; + + Ok(unverified) + } +} + +impl fmt::Display for VerifyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + VerifyError::Sig(ref e) => write!(f, "Sig error, {}", e), + VerifyError::Header(ref e) => write!(f, "Header error, {}", e), + } + } +} + +impl Error for VerifyError { + fn description(&self) -> &str { + match *self { + VerifyError::Sig(ref e) => e.description(), + VerifyError::Header(ref e) => e.description(), + } + } + + fn source(&self) -> Option<&(dyn Error + 'static)> { + match *self { + VerifyError::Sig(ref e) => Some(e), + VerifyError::Header(ref e) => Some(e), + } + } +} + +impl From for VerifyError { + fn from(e: http_signature_normalization::VerifyError) -> Self { + VerifyError::Sig(e) + } +} + +impl From for VerifyError { + fn from(e: ToStrError) -> Self { + VerifyError::Header(e) + } +}