diff --git a/http-signature-normalization-actix/Cargo.toml b/http-signature-normalization-actix/Cargo.toml index 83bcffe..8885e48 100644 --- a/http-signature-normalization-actix/Cargo.toml +++ b/http-signature-normalization-actix/Cargo.toml @@ -11,12 +11,25 @@ edition = "2018" [features] default = [] digest = ["base64", "futures"] +sha-2 = ["digest", "sha2"] +sha-3 = ["digest", "sha3"] + +[[example]] +name = "server" +required-features = ["sha-2"] + +[[example]] +name = "client" +required-features = ["sha-2"] [dependencies] actix-web = "1.0" base64 = { version = "0.10", optional = true } -http-signature-normalization = { version = "0.1.0", path = ".." } +failure = "0.1" futures = { version = "0.1", optional = true } +http-signature-normalization = { version = "0.1.0", path = ".." } +sha2 = { version = "0.8", optional = true } +sha3 = { version = "0.8", optional = true } [dev-dependencies] actix = "0.8" diff --git a/http-signature-normalization-actix/examples/client.rs b/http-signature-normalization-actix/examples/client.rs index 15178cb..a1ee1b9 100644 --- a/http-signature-normalization-actix/examples/client.rs +++ b/http-signature-normalization-actix/examples/client.rs @@ -2,18 +2,24 @@ use actix::System; use actix_web::client::Client; use futures::future::{lazy, Future}; use http_signature_normalization_actix::prelude::*; +use sha2::{Digest, Sha256}; fn main() { System::new("client-example") .block_on(lazy(|| { let config = Config::default(); + let mut digest = Sha256::new(); Client::default() - .get("http://127.0.0.1:8010/") + .post("http://127.0.0.1:8010/") .header("User-Agent", "Actix Web") - .authorization_signature::<_, MyError, _>(&config, "my-key-id", |s| { - Ok(s.as_bytes().to_vec()) - }) + .authorization_signature_with_digest::<_, MyError, _, _, _>( + &config, + "my-key-id", + &mut digest, + "Hewwo owo", + |s| Ok(s.as_bytes().to_vec()), + ) .unwrap() .send() .map_err(|_| ()) diff --git a/http-signature-normalization-actix/examples/server.rs b/http-signature-normalization-actix/examples/server.rs index c11f6b4..7eb91e6 100644 --- a/http-signature-normalization-actix/examples/server.rs +++ b/http-signature-normalization-actix/examples/server.rs @@ -1,6 +1,7 @@ use actix::System; use actix_web::{web, App, HttpRequest, HttpServer, ResponseError}; use http_signature_normalization_actix::{prelude::*, verify::Algorithm}; +use sha2::{Digest, Sha256}; use std::fmt; fn index((req, config): (HttpRequest, web::Data)) -> Result<&'static str, MyError> { @@ -26,7 +27,8 @@ fn main() -> Result<(), Box> { HttpServer::new(move || { App::new() .data(Config::default()) - .route("/", web::get().to(index)) + .wrap(VerifyDigest::new(Sha256::new())) + .route("/", web::post().to(index)) }) .bind("127.0.0.1:8010")? .start(); diff --git a/http-signature-normalization-actix/src/digest/middleware.rs b/http-signature-normalization-actix/src/digest/middleware.rs new file mode 100644 index 0000000..5f568ea --- /dev/null +++ b/http-signature-normalization-actix/src/digest/middleware.rs @@ -0,0 +1,165 @@ +use actix_web::{ + dev::{Body, Service, ServiceRequest, ServiceResponse, Transform}, + error::PayloadError, + http::header::HeaderValue, + web::Bytes, + HttpMessage, HttpResponse, ResponseError, +}; +use failure::Fail; +use futures::{ + future::{err, ok, Either, FutureResult}, + stream::once, + Future, Poll, Stream, +}; +use std::{cell::RefCell, rc::Rc}; + +use super::{DigestPart, DigestVerify}; + +pub struct VerifyDigest(bool, T); +pub struct VerifyMiddleware(Rc>, bool, T); + +#[derive(Debug, Fail)] +#[fail(display = "Error in upstream middleware")] +pub struct UpstreamError; + +#[derive(Debug, Fail)] +#[fail(display = "Error verifying digest")] +pub struct VerifyError; + +impl VerifyDigest +where + T: DigestVerify + Clone, +{ + pub fn new(verify_digest: T) -> Self { + VerifyDigest(true, verify_digest) + } + + pub fn optional(self) -> Self { + VerifyDigest(false, self.1) + } +} + +impl Transform for VerifyDigest +where + T: DigestVerify + Clone + 'static, + S: Service> + 'static, + S::Error: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = actix_web::Error; + type Transform = VerifyMiddleware; + type InitError = (); + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(VerifyMiddleware( + Rc::new(RefCell::new(service)), + self.0, + self.1.clone(), + )) + } +} + +impl Service for VerifyMiddleware +where + T: DigestVerify + Clone + 'static, + S: Service> + 'static, + S::Error: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = actix_web::Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.0 + .borrow_mut() + .poll_ready() + .map_err(|_| UpstreamError.into()) + } + + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + let mut verify_digest = self.2.clone(); + if let Some(digest) = req.headers().get("Digest") { + let vec = match parse_digest(digest) { + Some(vec) => vec, + None => return Box::new(err(VerifyError.into())), + }; + let payload = req.take_payload(); + let service = self.0.clone(); + + Box::new(payload.concat2().from_err().and_then(move |bytes| { + if verify_digest.verify(&vec, &bytes.as_ref()) { + req.set_payload( + (Box::new(once(Ok(bytes))) + as Box>) + .into(), + ); + + Either::A( + service + .borrow_mut() + .call(req) + .map_err(|_| UpstreamError.into()), + ) + } else { + Either::B(err(VerifyError.into())) + } + })) + } else { + if self.1 { + Box::new(err(VerifyError.into())) + } else { + Box::new( + self.0 + .borrow_mut() + .call(req) + .map_err(|_| UpstreamError.into()), + ) + } + } + } +} + +fn parse_digest(h: &HeaderValue) -> Option> { + let h = h.to_str().ok()?.split(";").next()?; + let v: Vec<_> = h + .split(",") + .filter_map(|p| { + let mut iter = p.splitn(2, "="); + iter.next() + .and_then(|alg| iter.next().map(|value| (alg, value))) + }) + .map(|(alg, value)| DigestPart { + algorithm: alg.to_owned(), + digest: value.to_owned(), + }) + .collect(); + + if v.is_empty() { + None + } else { + Some(v) + } +} + +impl ResponseError for UpstreamError { + fn error_response(&self) -> HttpResponse { + HttpResponse::InternalServerError().finish() + } + + fn render_response(&self) -> HttpResponse { + Self::error_response(self) + } +} + +impl ResponseError for VerifyError { + fn error_response(&self) -> HttpResponse { + HttpResponse::BadRequest().finish() + } + + fn render_response(&self) -> HttpResponse { + Self::error_response(self) + } +} diff --git a/http-signature-normalization-actix/src/digest/mod.rs b/http-signature-normalization-actix/src/digest/mod.rs new file mode 100644 index 0000000..51c5244 --- /dev/null +++ b/http-signature-normalization-actix/src/digest/mod.rs @@ -0,0 +1,87 @@ +use actix_web::{ + client::{ClientRequest, ClientResponse}, + error::PayloadError, + http::header::{InvalidHeaderValue, ToStrError}, + web::Bytes, +}; +use futures::{Future, Stream}; +use std::fmt::Display; + +use crate::{Config, Sign}; + +pub mod middleware; +#[cfg(feature = "sha-2")] +pub mod sha2; +#[cfg(feature = "sha-3")] +pub mod sha3; + +mod sign; + +pub trait DigestCreate { + const NAME: &'static str; + + fn compute(&mut self, input: &[u8]) -> String; +} + +pub trait DigestVerify { + fn verify(&mut self, digests: &[DigestPart], payload: &[u8]) -> bool; +} + +pub trait SignExt: Sign { + fn authorization_signature_with_digest( + self, + config: &Config, + key_id: K, + digest: &mut D, + v: V, + f: F, + ) -> Result, E> + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + D: DigestCreate, + V: AsRef<[u8]>, + Self: Sized; + + fn signature_with_digest( + self, + config: &Config, + key_id: K, + digest: &mut D, + v: V, + f: F, + ) -> Result, E> + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + D: DigestCreate, + V: AsRef<[u8]>, + Self: Sized; +} + +pub struct DigestPart { + pub algorithm: String, + pub digest: String, +} + +pub struct DigestClient { + req: ClientRequest, + body: V, +} + +impl DigestClient +where + V: AsRef<[u8]>, +{ + pub fn new(req: ClientRequest, body: V) -> Self { + DigestClient { req, body } + } + + pub fn send( + self, + ) -> impl Future>> { + self.req.send_body(self.body.as_ref().to_vec()) + } +} diff --git a/http-signature-normalization-actix/src/digest/sha2.rs b/http-signature-normalization-actix/src/digest/sha2.rs new file mode 100644 index 0000000..aa8c58d --- /dev/null +++ b/http-signature-normalization-actix/src/digest/sha2.rs @@ -0,0 +1,153 @@ +use sha2::{Digest as _, Sha224, Sha256, Sha384, Sha512, Sha512Trunc224, Sha512Trunc256}; + +use super::{DigestCreate, DigestPart, DigestVerify}; + +impl DigestCreate for Sha224 { + const NAME: &'static str = "sha-224"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha224 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha256 { + const NAME: &'static str = "sha-256"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha256 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha384 { + const NAME: &'static str = "sha-384"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha384 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha512 { + const NAME: &'static str = "sha-512"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha512 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha512Trunc224 { + const NAME: &'static str = "sha-512-224"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha512Trunc224 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha512Trunc256 { + const NAME: &'static str = "sha-512-256"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha512Trunc256 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} diff --git a/http-signature-normalization-actix/src/digest/sha3.rs b/http-signature-normalization-actix/src/digest/sha3.rs new file mode 100644 index 0000000..e671032 --- /dev/null +++ b/http-signature-normalization-actix/src/digest/sha3.rs @@ -0,0 +1,231 @@ +use sha3::{ + Digest as _, Keccak224, Keccak256, Keccak256Full, Keccak384, Keccak512, Sha3_224, Sha3_256, + Sha3_384, Sha3_512, +}; + +use super::{DigestCreate, DigestPart, DigestVerify}; + +impl DigestCreate for Sha3_224 { + const NAME: &'static str = "sha3-224"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha3_224 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha3_256 { + const NAME: &'static str = "sha3-256"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha3_256 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha3_384 { + const NAME: &'static str = "sha3-384"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha3_384 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Sha3_512 { + const NAME: &'static str = "sha3-512"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Sha3_512 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Keccak224 { + const NAME: &'static str = "keccak-224"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Keccak224 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Keccak256 { + const NAME: &'static str = "keccak-256"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Keccak256 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Keccak256Full { + const NAME: &'static str = "keccak-256-full"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Keccak256Full { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Keccak384 { + const NAME: &'static str = "keccak-384"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Keccak384 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} + +impl DigestCreate for Keccak512 { + const NAME: &'static str = "keccak-512"; + + fn compute(&mut self, input: &[u8]) -> String { + self.input(input); + base64::encode(&self.result_reset()) + } +} + +impl DigestVerify for Keccak512 { + fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm == ::NAME) + { + self.input(bytes); + let digest = base64::encode(&self.result_reset()); + + return part.digest == digest; + } + + false + } +} diff --git a/http-signature-normalization-actix/src/digest/sign.rs b/http-signature-normalization-actix/src/digest/sign.rs new file mode 100644 index 0000000..ce6e0d8 --- /dev/null +++ b/http-signature-normalization-actix/src/digest/sign.rs @@ -0,0 +1,58 @@ +use actix_web::{ + client::ClientRequest, + http::header::{InvalidHeaderValue, ToStrError}, +}; +use std::fmt::Display; + +use crate::{ + digest::{DigestClient, DigestCreate, SignExt}, + Config, Sign, +}; + +impl SignExt for ClientRequest { + fn authorization_signature_with_digest( + self, + config: &Config, + key_id: K, + digest: &mut D, + v: V, + f: F, + ) -> Result, E> + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + D: DigestCreate, + V: AsRef<[u8]>, + Self: Sized, + { + let digest = digest.compute(v.as_ref()); + + self.set_header("Digest", format!("{}={}", D::NAME, digest)) + .authorization_signature(config, key_id, f) + .map(|c| DigestClient::new(c, v)) + } + + fn signature_with_digest( + self, + config: &Config, + key_id: K, + digest: &mut D, + v: V, + f: F, + ) -> Result, E> + where + F: FnOnce(&str) -> Result, E>, + E: From + From, + K: Display, + D: DigestCreate, + V: AsRef<[u8]>, + Self: Sized, + { + let digest = digest.compute(v.as_ref()); + + self.set_header("Digest", format!("{}={}", D::NAME, digest)) + .signature(config, key_id, f) + .map(|c| DigestClient::new(c, v)) + } +} diff --git a/http-signature-normalization-actix/src/lib.rs b/http-signature-normalization-actix/src/lib.rs index a25cd3c..c362a8d 100644 --- a/http-signature-normalization-actix/src/lib.rs +++ b/http-signature-normalization-actix/src/lib.rs @@ -1,12 +1,7 @@ -use actix_web::{ - client::ClientRequest, - dev::ServiceRequest, - http::{ - header::{HeaderMap, InvalidHeaderValue, ToStrError}, - uri::PathAndQuery, - Method, - }, - HttpRequest, +use actix_web::http::{ + header::{HeaderMap, InvalidHeaderValue, ToStrError}, + uri::PathAndQuery, + Method, }; use std::{ collections::BTreeMap, @@ -14,40 +9,26 @@ use std::{ fmt::{self, Display}, }; -#[cfg(feature = "digest")] -use actix_web::{client::ClientResponse, error::PayloadError, web::Bytes}; -#[cfg(feature = "digest")] -use futures::{Future, Stream}; +mod sign; +#[cfg(feature = "digest")] +pub mod digest; + +pub mod verify; pub mod prelude { pub use crate::{verify::Unverified, Config, Sign, Verify, VerifyError}; #[cfg(feature = "digest")] - pub use crate::Digest; + pub use crate::digest::{ + middleware::VerifyDigest, DigestClient, DigestCreate, DigestPart, DigestVerify, SignExt, + }; 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, -}; - -#[cfg(feature = "digest")] -pub trait Digest { - const NAME: &'static str; - - fn compute(input: &[u8]) -> Vec; -} +use self::{create::Unsigned, verify::Unverified}; pub trait Verify { fn begin_verify(&self, config: &Config) -> Result; @@ -67,38 +48,6 @@ pub trait Sign { E: From + From, K: Display, Self: Sized; - - #[cfg(feature = "digest")] - fn authorization_signature_with_digest( - self, - config: &Config, - key_id: K, - f: F, - v: V, - ) -> Result, E> - where - F: FnOnce(&str) -> Result, E>, - E: From + From, - K: Display, - D: Digest, - V: AsRef<[u8]>, - Self: Sized; - - #[cfg(feature = "digest")] - fn signature_with_digest( - self, - config: &Config, - key_id: K, - f: F, - v: V, - ) -> Result, E> - where - F: FnOnce(&str) -> Result, E>, - E: From + From, - K: Display, - D: Digest, - V: AsRef<[u8]>, - Self: Sized; } #[derive(Clone, Default)] @@ -106,28 +55,6 @@ pub struct Config { pub config: http_signature_normalization::Config, } -#[cfg(feature = "digest")] -pub struct DigestClient { - req: ClientRequest, - body: V, -} - -#[cfg(feature = "digest")] -impl DigestClient -where - V: AsRef<[u8]>, -{ - pub fn new(req: ClientRequest, body: V) -> Self { - DigestClient { req, body } - } - - pub fn send( - self, - ) -> impl Future>> { - self.req.send_body(self.body.as_ref().to_vec()) - } -} - #[derive(Debug)] pub enum VerifyError { Sig(http_signature_normalization::VerifyError), @@ -180,120 +107,6 @@ impl Config { } } -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) - } - - #[cfg(feature = "digest")] - fn authorization_signature_with_digest( - self, - config: &Config, - key_id: K, - f: F, - v: V, - ) -> Result, E> - where - F: FnOnce(&str) -> Result, E>, - E: From + From, - K: Display, - D: Digest, - V: AsRef<[u8]>, - Self: Sized, - { - let digest = base64::encode(D::compute(v.as_ref()).as_slice()); - - self.set_header("Digest", format!("{}={}", D::NAME, digest)) - .authorization_signature(config, key_id, f) - .map(|c| DigestClient::new(c, v)) - } - - #[cfg(feature = "digest")] - fn signature_with_digest( - self, - config: &Config, - key_id: K, - f: F, - v: V, - ) -> Result, E> - where - F: FnOnce(&str) -> Result, E>, - E: From + From, - K: Display, - D: Digest, - V: AsRef<[u8]>, - Self: Sized, - { - let digest = base64::encode(D::compute(v.as_ref()).as_slice()); - - self.set_header("Digest", format!("{}={}", D::NAME, digest)) - .signature(config, key_id, f) - .map(|c| DigestClient::new(c, v)) - } -} - -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 { diff --git a/http-signature-normalization-actix/src/sign.rs b/http-signature-normalization-actix/src/sign.rs new file mode 100644 index 0000000..4e7a592 --- /dev/null +++ b/http-signature-normalization-actix/src/sign.rs @@ -0,0 +1,55 @@ +use actix_web::{ + client::ClientRequest, + http::header::{InvalidHeaderValue, ToStrError}, +}; +use std::fmt::Display; + +use crate::{create::Signed, Config, Sign}; + +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) +} diff --git a/http-signature-normalization-actix/src/verify.rs b/http-signature-normalization-actix/src/verify.rs new file mode 100644 index 0000000..80fa580 --- /dev/null +++ b/http-signature-normalization-actix/src/verify.rs @@ -0,0 +1,27 @@ +use actix_web::{dev::ServiceRequest, HttpRequest}; +pub use http_signature_normalization::verify::{ + Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified, + ValidateError, +}; + +use crate::{Config, Verify, VerifyError}; + +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(), + ) + } +}