From bb42dd3f9a0c0864b5e52a8837854c45c5bae8eb Mon Sep 17 00:00:00 2001 From: "Aode (Lion)" Date: Thu, 23 Sep 2021 17:05:11 -0500 Subject: [PATCH] actix: Split Server and Client into features --- http-signature-normalization-actix/Cargo.toml | 17 +- .../src/create.rs | 2 +- .../src/digest/mod.rs | 227 ++++++----- .../src/digest/sha2.rs | 259 ++++++------ .../src/digest/sha3.rs | 377 ++++++++++-------- .../src/digest/sign.rs | 10 +- http-signature-normalization-actix/src/lib.rs | 277 +++++++------ .../src/sign.rs | 4 +- 8 files changed, 640 insertions(+), 533 deletions(-) diff --git a/http-signature-normalization-actix/Cargo.toml b/http-signature-normalization-actix/Cargo.toml index b240084..62d1b54 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.5.0-beta.9" +version = "0.5.0-beta.10" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -11,22 +11,26 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["sha-2", "sha-3"] +default = ["server", "sha-2", "sha-3"] +client = ["awc"] digest = ["base64"] +server = ["actix-web"] sha-2 = ["digest", "sha2"] sha-3 = ["digest", "sha3"] [[example]] name = "server" -required-features = ["sha-2"] +required-features = ["server", "sha-2"] [[example]] name = "client" -required-features = ["sha-2"] +required-features = ["client", "sha-2"] [dependencies] -actix-web = { version = "4.0.0-beta.8", default-features = false } -awc = { version = "3.0.0-beta.7", default-features = false } +actix-http = { version = "3.0.0-beta.10", default-features = false } +actix-rt = "2.2.0" +actix-web = { version = "4.0.0-beta.8", default-features = false, optional = true } +awc = { version = "3.0.0-beta.7", default-features = false, optional = true } base64 = { version = "0.13", optional = true } chrono = "0.4.6" futures-util = { version = "0.3", default-features = false } @@ -40,6 +44,5 @@ tracing-error = "0.1" tracing-futures = "0.2" [dev-dependencies] -actix-rt = "2.1.0" tracing-actix-web = { version = "0.4.0-beta.13" } tracing-subscriber = { version = "0.2", features = ["fmt"] } diff --git a/http-signature-normalization-actix/src/create.rs b/http-signature-normalization-actix/src/create.rs index df628e7..eb135fc 100644 --- a/http-signature-normalization-actix/src/create.rs +++ b/http-signature-normalization-actix/src/create.rs @@ -1,6 +1,6 @@ //! Types for signing requests with Actix Web -use actix_web::http::header::{ +use actix_http::http::header::{ HeaderMap, HeaderName, HeaderValue, InvalidHeaderValue, AUTHORIZATION, }; diff --git a/http-signature-normalization-actix/src/digest/mod.rs b/http-signature-normalization-actix/src/digest/mod.rs index 7cadf20..709f921 100644 --- a/http-signature-normalization-actix/src/digest/mod.rs +++ b/http-signature-normalization-actix/src/digest/mod.rs @@ -3,128 +3,147 @@ //! Digest headers are commonly used in conjunction with HTTP Signatures to verify the whole //! request when request bodies are present -use actix_web::{error::BlockingError, http::header::InvalidHeaderValue}; -use awc::{ClientRequest, SendClientRequest}; -use std::{fmt::Display, future::Future, pin::Pin}; - -use crate::{Config, PrepareSignError, Sign}; - +#[cfg(feature = "server")] pub mod middleware; #[cfg(feature = "sha-2")] mod sha2; #[cfg(feature = "sha-3")] mod sha3; - +#[cfg(feature = "client")] mod sign; -/// A trait for creating digests of an array of bytes -pub trait DigestCreate { +#[cfg(feature = "client")] +pub use self::client::{DigestClient, DigestCreate, SignExt}; + +#[cfg(feature = "server")] +pub use self::server::{DigestPart, DigestVerify}; + +/// Giving names to Digest implementations +pub trait DigestName { /// The name of the digest algorithm const NAME: &'static str; - - /// Compute the digest of the input bytes - fn compute(&mut self, input: &[u8]) -> String; } -/// A trait for verifying digests -pub trait DigestVerify { - /// Update the verifier with bytes from the request body - fn update(&mut self, part: &[u8]); +#[cfg(feature = "client")] +mod client { + use crate::{Config, PrepareSignError, Sign}; + use actix_http::{error::BlockingError, http::header::InvalidHeaderValue}; + use awc::{ClientRequest, SendClientRequest}; + use std::{fmt::Display, future::Future, pin::Pin}; - /// Verify the request body against the digests from the request headers - fn verify(&mut self, digests: &[DigestPart]) -> bool; -} + use super::DigestName; -/// Extend the Sign trait with support for adding Digest Headers to the request -/// -/// 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 -/// a malicious entity -pub trait SignExt: Sign { - /// Set the Digest and Authorization headers on the request - fn authorization_signature_with_digest( - self, - config: Config, - key_id: K, - digest: D, - v: V, - f: F, - ) -> Pin, E>>>> - where - F: FnOnce(&str) -> Result + Send + 'static, - E: From - + From - + From - + std::fmt::Debug - + Send - + 'static, - K: Display + 'static, - D: DigestCreate + Send + 'static, - V: AsRef<[u8]> + Send + 'static, - Self: Sized; - - /// Set the Digest and Signature headers on the request - fn signature_with_digest( - self, - config: Config, - key_id: K, - digest: D, - v: V, - f: F, - ) -> Pin, E>>>> - where - F: FnOnce(&str) -> Result + Send + 'static, - E: From - + From - + From - + std::fmt::Debug - + Send - + 'static, - K: Display + 'static, - D: DigestCreate + Send + 'static, - V: AsRef<[u8]> + Send + 'static, - Self: Sized; -} - -/// A parsed digest from the request -#[derive(Debug)] -pub struct DigestPart { - /// The alrogithm used to produce the digest - pub algorithm: String, - - /// The digest itself - pub digest: String, -} - -/// An intermediate type between setting the Digest and Signature or Authorization headers, and -/// actually sending the request -/// -/// This exists so that the return type for the [`SignExt`] trait can be named -pub struct DigestClient { - req: ClientRequest, - body: V, -} - -impl DigestClient -where - V: AsRef<[u8]>, -{ - fn new(req: ClientRequest, body: V) -> Self { - DigestClient { req, body } + /// A trait for creating digests of an array of bytes + pub trait DigestCreate: DigestName { + /// Compute the digest of the input bytes + fn compute(&mut self, input: &[u8]) -> String; } - /// Send the request + /// Extend the Sign trait with support for adding Digest Headers to the request /// - /// This is analogous to `ClientRequest::send_body` and uses the body provided when producing - /// the digest - pub fn send(self) -> SendClientRequest { - self.req.send_body(self.body.as_ref().to_vec()) + /// 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 + /// a malicious entity + pub trait SignExt: Sign { + /// Set the Digest and Authorization headers on the request + fn authorization_signature_with_digest( + self, + config: Config, + key_id: K, + digest: D, + v: V, + f: F, + ) -> Pin, E>>>> + where + F: FnOnce(&str) -> Result + Send + 'static, + E: From + + From + + From + + std::fmt::Debug + + Send + + 'static, + K: Display + 'static, + D: DigestCreate + Send + 'static, + V: AsRef<[u8]> + Send + 'static, + Self: Sized; + + /// Set the Digest and Signature headers on the request + fn signature_with_digest( + self, + config: Config, + key_id: K, + digest: D, + v: V, + f: F, + ) -> Pin, E>>>> + where + F: FnOnce(&str) -> Result + Send + 'static, + E: From + + From + + From + + std::fmt::Debug + + Send + + 'static, + K: Display + 'static, + D: DigestCreate + Send + 'static, + V: AsRef<[u8]> + Send + 'static, + Self: Sized; } - /// Split the parts of the request + /// An intermediate type between setting the Digest and Signature or Authorization headers, and + /// actually sending the request /// - /// In case the caller needs to interrogate the ClientRequest before sending - pub fn split(self) -> (ClientRequest, V) { - (self.req, self.body) + /// This exists so that the return type for the [`SignExt`] trait can be named + pub struct DigestClient { + req: ClientRequest, + body: V, + } + + impl DigestClient + where + V: AsRef<[u8]>, + { + pub(super) fn new(req: ClientRequest, body: V) -> Self { + DigestClient { req, body } + } + + /// Send the request + /// + /// This is analogous to `ClientRequest::send_body` and uses the body provided when producing + /// the digest + pub fn send(self) -> SendClientRequest { + self.req.send_body(self.body.as_ref().to_vec()) + } + + /// Split the parts of the request + /// + /// In case the caller needs to interrogate the ClientRequest before sending + pub fn split(self) -> (ClientRequest, V) { + (self.req, self.body) + } + } +} + +#[cfg(feature = "server")] +mod server { + use super::DigestName; + + /// A trait for verifying digests + pub trait DigestVerify: DigestName { + /// Update the verifier with bytes from the request body + fn update(&mut self, part: &[u8]); + + /// Verify the request body against the digests from the request headers + fn verify(&mut self, digests: &[DigestPart]) -> bool; + } + + /// A parsed digest from the request + #[derive(Debug)] + pub struct DigestPart { + /// The alrogithm used to produce the digest + pub algorithm: String, + + /// The digest itself + pub digest: String, } } diff --git a/http-signature-normalization-actix/src/digest/sha2.rs b/http-signature-normalization-actix/src/digest/sha2.rs index 48bf3f3..2edd8d2 100644 --- a/http-signature-normalization-actix/src/digest/sha2.rs +++ b/http-signature-normalization-actix/src/digest/sha2.rs @@ -1,143 +1,166 @@ +use crate::digest::DigestName; use sha2::{Sha224, Sha256, Sha384, Sha512, Sha512Trunc224, Sha512Trunc256}; -use tracing::{debug, warn}; -use super::{DigestCreate, DigestPart, DigestVerify}; - -fn create(digest: &mut impl sha2::Digest, input: &[u8]) -> String { - digest.update(input); - base64::encode(&digest.finalize_reset()) +impl DigestName for Sha224 { + const NAME: &'static str = "SHA-244"; } -fn verify(digest: &mut impl sha2::Digest, name: &str, parts: &[DigestPart]) -> bool { - if let Some(part) = parts - .iter() - .find(|p| p.algorithm.to_lowercase() == name.to_lowercase()) - { - debug!("Verifying digest type, {}", name); - let encoded = base64::encode(&digest.finalize_reset()); - - return part.digest == encoded; - } - warn!("No matching digest algorithm found for {}", name); - warn!( - "Provided: [{}]", - parts.iter().fold(String::new(), |mut acc, item| { - if acc.is_empty() { - } else { - acc.push_str(", "); - } - acc.push_str(&item.algorithm); - acc - }) - ); - - false -} - -impl DigestCreate for Sha224 { - const NAME: &'static str = "SHA-224"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } -} - -impl DigestVerify for Sha224 { - fn update(&mut self, part: &[u8]) { - sha2::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha256 { +impl DigestName for Sha256 { const NAME: &'static str = "SHA-256"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Sha256 { - fn update(&mut self, part: &[u8]) { - sha2::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha384 { +impl DigestName for Sha384 { const NAME: &'static str = "SHA-384"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Sha384 { - fn update(&mut self, part: &[u8]) { - sha2::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha512 { +impl DigestName for Sha512 { const NAME: &'static str = "SHA-512"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Sha512 { - fn update(&mut self, part: &[u8]) { - sha2::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha512Trunc224 { +impl DigestName for Sha512Trunc224 { const NAME: &'static str = "SHA-512-224"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Sha512Trunc224 { - fn update(&mut self, part: &[u8]) { - sha2::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha512Trunc256 { +impl DigestName for Sha512Trunc256 { const NAME: &'static str = "SHA-512-256"; +} - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) +#[cfg(feature = "client")] +mod client { + use super::*; + use crate::digest::DigestCreate; + + fn create(digest: &mut impl sha2::Digest, input: &[u8]) -> String { + digest.update(input); + base64::encode(&digest.finalize_reset()) + } + + impl DigestCreate for Sha224 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha256 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha384 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha512 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha512Trunc224 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha512Trunc256 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } } } -impl DigestVerify for Sha512Trunc256 { - fn update(&mut self, part: &[u8]) { - sha2::Digest::update(self, part); +#[cfg(feature = "server")] +mod server { + use super::*; + use crate::digest::{DigestPart, DigestVerify}; + use tracing::{debug, warn}; + + fn verify(digest: &mut impl sha2::Digest, name: &str, parts: &[DigestPart]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm.to_lowercase() == name.to_lowercase()) + { + debug!("Verifying digest type, {}", name); + let encoded = base64::encode(&digest.finalize_reset()); + + return part.digest == encoded; + } + warn!("No matching digest algorithm found for {}", name); + warn!( + "Provided: [{}]", + parts.iter().fold(String::new(), |mut acc, item| { + if acc.is_empty() { + } else { + acc.push_str(", "); + } + acc.push_str(&item.algorithm); + acc + }) + ); + + false } - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) + impl DigestVerify for Sha224 { + fn update(&mut self, part: &[u8]) { + sha2::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha256 { + fn update(&mut self, part: &[u8]) { + sha2::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha384 { + fn update(&mut self, part: &[u8]) { + sha2::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha512 { + fn update(&mut self, part: &[u8]) { + sha2::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha512Trunc224 { + fn update(&mut self, part: &[u8]) { + sha2::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha512Trunc256 { + fn update(&mut self, part: &[u8]) { + sha2::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } } } diff --git a/http-signature-normalization-actix/src/digest/sha3.rs b/http-signature-normalization-actix/src/digest/sha3.rs index decff3f..5959f3f 100644 --- a/http-signature-normalization-actix/src/digest/sha3.rs +++ b/http-signature-normalization-actix/src/digest/sha3.rs @@ -1,200 +1,229 @@ +use crate::digest::DigestName; use sha3::{ Keccak224, Keccak256, Keccak256Full, Keccak384, Keccak512, Sha3_224, Sha3_256, Sha3_384, Sha3_512, }; -use tracing::{debug, warn}; -use super::{DigestCreate, DigestPart, DigestVerify}; - -fn create(digest: &mut impl sha3::Digest, input: &[u8]) -> String { - digest.update(input); - base64::encode(&digest.finalize_reset()) -} - -fn verify(digest: &mut impl sha3::Digest, name: &str, parts: &[DigestPart]) -> bool { - if let Some(part) = parts - .iter() - .find(|p| p.algorithm.to_lowercase() == name.to_lowercase()) - { - debug!("Verifying digest type, {}", name); - let encoded = base64::encode(&digest.finalize_reset()); - - return part.digest == encoded; - } - warn!("No matching digest algorithm found for {}", name); - warn!( - "Provided: [{}]", - parts.iter().fold(String::new(), |mut acc, item| { - if acc.is_empty() { - } else { - acc.push_str(", "); - } - acc.push_str(&item.algorithm); - acc - }) - ); - - false -} - -impl DigestCreate for Sha3_224 { - const NAME: &'static str = "SHA3-224"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } -} - -impl DigestVerify for Sha3_224 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha3_256 { - const NAME: &'static str = "SHA3-256"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } -} - -impl DigestVerify for Sha3_256 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha3_384 { - const NAME: &'static str = "SHA3-384"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } -} - -impl DigestVerify for Sha3_384 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Sha3_512 { - const NAME: &'static str = "SHA3-512"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } -} - -impl DigestVerify for Sha3_512 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Keccak224 { +impl DigestName for Keccak224 { const NAME: &'static str = "keccak-224"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Keccak224 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Keccak256 { +impl DigestName for Keccak256 { const NAME: &'static str = "keccak-256"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Keccak256 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Keccak256Full { +impl DigestName for Keccak256Full { const NAME: &'static str = "keccak-256-full"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Keccak256Full { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Keccak384 { +impl DigestName for Keccak384 { const NAME: &'static str = "keccak-384"; - - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) - } } -impl DigestVerify for Keccak384 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); - } - - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) - } -} - -impl DigestCreate for Keccak512 { +impl DigestName for Keccak512 { const NAME: &'static str = "keccak-512"; +} - fn compute(&mut self, input: &[u8]) -> String { - create(self, input) +impl DigestName for Sha3_224 { + const NAME: &'static str = "SHA3-224"; +} + +impl DigestName for Sha3_256 { + const NAME: &'static str = "SHA3-256"; +} + +impl DigestName for Sha3_384 { + const NAME: &'static str = "SHA3-384"; +} + +impl DigestName for Sha3_512 { + const NAME: &'static str = "SHA3-512"; +} + +#[cfg(features = "client")] +mod client { + use super::*; + use crate::digest::DigestCreate; + + fn create(digest: &mut impl sha3::Digest, input: &[u8]) -> String { + digest.update(input); + base64::encode(&digest.finalize_reset()) + } + + impl DigestCreate for Sha3_224 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha3_256 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha3_384 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Sha3_512 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Keccak224 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Keccak256 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Keccak256Full { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Keccak384 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } + } + + impl DigestCreate for Keccak512 { + fn compute(&mut self, input: &[u8]) -> String { + create(self, input) + } } } -impl DigestVerify for Keccak512 { - fn update(&mut self, part: &[u8]) { - sha3::Digest::update(self, part); +#[cfg(feature = "server")] +mod server { + use super::*; + use crate::digest::{DigestPart, DigestVerify}; + use tracing::{debug, warn}; + + fn verify(digest: &mut impl sha3::Digest, name: &str, parts: &[DigestPart]) -> bool { + if let Some(part) = parts + .iter() + .find(|p| p.algorithm.to_lowercase() == name.to_lowercase()) + { + debug!("Verifying digest type, {}", name); + let encoded = base64::encode(&digest.finalize_reset()); + + return part.digest == encoded; + } + warn!("No matching digest algorithm found for {}", name); + warn!( + "Provided: [{}]", + parts.iter().fold(String::new(), |mut acc, item| { + if acc.is_empty() { + } else { + acc.push_str(", "); + } + acc.push_str(&item.algorithm); + acc + }) + ); + + false } - fn verify(&mut self, parts: &[DigestPart]) -> bool { - verify(self, ::NAME, parts) + impl DigestVerify for Sha3_224 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha3_256 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha3_384 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Sha3_512 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Keccak224 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Keccak256 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Keccak256Full { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Keccak384 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } + } + + impl DigestVerify for Keccak512 { + fn update(&mut self, part: &[u8]) { + sha3::Digest::update(self, part); + } + + fn verify(&mut self, parts: &[DigestPart]) -> bool { + verify(self, Self::NAME, parts) + } } } diff --git a/http-signature-normalization-actix/src/digest/sign.rs b/http-signature-normalization-actix/src/digest/sign.rs index e20edf0..c3385d3 100644 --- a/http-signature-normalization-actix/src/digest/sign.rs +++ b/http-signature-normalization-actix/src/digest/sign.rs @@ -1,4 +1,4 @@ -use actix_web::{error::BlockingError, http::header::InvalidHeaderValue, web}; +use actix_http::{error::BlockingError, http::header::InvalidHeaderValue}; use awc::ClientRequest; use std::{fmt::Display, future::Future, pin::Pin}; @@ -30,11 +30,11 @@ impl SignExt for ClientRequest { Self: Sized, { Box::pin(async move { - let (d, v) = web::block(move || { + let (d, v) = actix_rt::task::spawn_blocking(move || { let d = digest.compute(v.as_ref()); Ok((d, v)) as Result<(String, V), E> }) - .await??; + .await.map_err(|_| BlockingError)??; let c = self .insert_header(("Digest", format!("{}={}", D::NAME, d))) @@ -67,11 +67,11 @@ impl SignExt for ClientRequest { Self: Sized, { Box::pin(async move { - let (d, v) = web::block(move || { + let (d, v) = actix_rt::task::spawn_blocking(move || { let d = digest.compute(v.as_ref()); Ok((d, v)) as Result<(String, V), E> }) - .await??; + .await.map_err(|_| BlockingError)??; let c = self .insert_header(("Digest", format!("{}={}", D::NAME, d))) diff --git a/http-signature-normalization-actix/src/lib.rs b/http-signature-normalization-actix/src/lib.rs index c68ae8b..81e8491 100644 --- a/http-signature-normalization-actix/src/lib.rs +++ b/http-signature-normalization-actix/src/lib.rs @@ -159,44 +159,57 @@ //! } //! ``` -use actix_web::{ - error::BlockingError, - http::{ - header::{HeaderMap, InvalidHeaderValue, ToStrError}, - uri::PathAndQuery, - Method, - }, -}; use chrono::Duration; -use std::{collections::BTreeMap, fmt::Display, future::Future, pin::Pin}; +#[cfg(any(feature = "client", feature = "server"))] +use actix_http::http::{ + header::{HeaderMap, ToStrError}, + uri::PathAndQuery, + Method, +}; +#[cfg(any(feature = "client", feature = "server"))] +use std::collections::BTreeMap; + +#[cfg(feature = "client")] mod sign; #[cfg(feature = "digest")] pub mod digest; +#[cfg(feature = "client")] pub mod create; +#[cfg(feature = "server")] pub mod middleware; pub use http_signature_normalization::RequiredError; /// Useful types and traits for using this library in Actix Web pub mod prelude { + pub use crate::{Config, RequiredError}; + + #[cfg(feature = "client")] + pub use crate::{PrepareSignError, Sign}; + + #[cfg(feature = "server")] pub use crate::{ middleware::{SignatureVerified, VerifySignature}, verify::{Algorithm, DeprecatedAlgorithm, Unverified}, - Config, PrepareSignError, PrepareVerifyError, RequiredError, Sign, SignatureVerify, + PrepareVerifyError, SignatureVerify, }; - #[cfg(feature = "digest")] + #[cfg(all(feature = "digest", feature = "client"))] + pub use crate::digest::{DigestClient, DigestCreate, SignExt}; + + #[cfg(all(feature = "digest", feature = "server"))] pub use crate::digest::{ middleware::{DigestVerified, VerifyDigest}, - DigestClient, DigestCreate, DigestPart, DigestVerify, SignExt, + DigestPart, DigestVerify, }; - pub use actix_web::http::header::{InvalidHeaderValue, ToStrError}; + pub use actix_http::http::header::{InvalidHeaderValue, ToStrError}; } +#[cfg(feature = "server")] /// Types for Verifying an HTTP Signature pub mod verify { pub use http_signature_normalization::verify::{ @@ -205,69 +218,11 @@ pub mod verify { }; } -use self::{ - create::Unsigned, - verify::{Algorithm, Unverified}, -}; +#[cfg(feature = "client")] +pub use self::client::{PrepareSignError, Sign}; -/// A trait for verifying signatures -pub trait SignatureVerify { - /// An error produced while attempting to verify the signature. This can be anything - /// implementing ResponseError - type Error: actix_web::ResponseError; - - /// The future that resolves to the verification state of the signature - type Future: Future>; - - /// Given the algorithm, key_id, signature, and signing_string, produce a future that resulves - /// to a the verification status - fn signature_verify( - &mut self, - algorithm: Option, - key_id: String, - signature: String, - signing_string: String, - ) -> Self::Future; -} - -/// A trait implemented by the awc ClientRequest type to add an HTTP signature to the request -pub trait Sign { - /// Add an Authorization Signature to the request - fn authorization_signature( - self, - config: Config, - key_id: K, - f: F, - ) -> Pin>>> - where - F: FnOnce(&str) -> Result + Send + 'static, - E: From - + From - + From - + std::fmt::Debug - + Send - + 'static, - K: Display + 'static, - Self: Sized; - - /// Add a Signature to the request - fn signature( - self, - config: Config, - key_id: K, - f: F, - ) -> Pin>>> - where - F: FnOnce(&str) -> Result + Send + 'static, - E: From - + From - + From - + std::fmt::Debug - + Send - + 'static, - K: Display + 'static, - Self: Sized; -} +#[cfg(feature = "server")] +pub use self::server::{PrepareVerifyError, SignatureVerify}; #[derive(Clone, Debug, Default)] /// Configuration for signing and verifying signatures @@ -282,60 +237,136 @@ pub struct Config { set_host: bool, } -#[derive(Debug, thiserror::Error)] -/// An error when preparing to verify a request -pub enum PrepareVerifyError { - #[error("Header is missing")] - /// Header is missing - Missing, +#[cfg(feature = "client")] +mod client { + use super::{Config, RequiredError}; + use actix_http::{ + error::BlockingError, + http::header::{InvalidHeaderValue, ToStrError}, + }; + use std::{fmt::Display, future::Future, pin::Pin}; - #[error("Header is expired")] - /// Header is expired - Expired, + /// A trait implemented by the awc ClientRequest type to add an HTTP signature to the request + pub trait Sign { + /// Add an Authorization Signature to the request + fn authorization_signature( + self, + config: Config, + key_id: K, + f: F, + ) -> Pin>>> + where + F: FnOnce(&str) -> Result + Send + 'static, + E: From + + From + + From + + std::fmt::Debug + + Send + + 'static, + K: Display + 'static, + Self: Sized; - #[error("Couldn't parse required field, {0}")] - /// Couldn't parse required field - ParseField(&'static str), + /// Add a Signature to the request + fn signature( + self, + config: Config, + key_id: K, + f: F, + ) -> Pin>>> + where + F: FnOnce(&str) -> Result + Send + 'static, + E: From + + From + + From + + std::fmt::Debug + + Send + + 'static, + K: Display + 'static, + Self: Sized; + } - #[error("Failed to read header, {0}")] - /// An error converting the header to a string for validation - Header(#[from] ToStrError), + #[derive(Debug, thiserror::Error)] + /// An error when preparing to sign a request + pub enum PrepareSignError { + #[error("Failed to read header, {0}")] + /// An error occurred when reading the request's headers + Header(#[from] ToStrError), - #[error("{0}")] - /// Required headers were missing from request - Required(#[from] RequiredError), + #[error("{0}")] + /// Some headers were marked as required, but are missing + RequiredError(#[from] RequiredError), + + #[error("No host provided for URL, {0}")] + /// Missing host + Host(String), + } } -#[derive(Debug, thiserror::Error)] -/// An error when preparing to sign a request -pub enum PrepareSignError { - #[error("Failed to read header, {0}")] - /// An error occurred when reading the request's headers - Header(#[from] ToStrError), +#[cfg(feature = "server")] +mod server { + use super::RequiredError; + use actix_http::http::header::ToStrError; + use std::future::Future; - #[error("{0}")] - /// Some headers were marked as required, but are missing - RequiredError(#[from] RequiredError), + /// A trait for verifying signatures + pub trait SignatureVerify { + /// An error produced while attempting to verify the signature. This can be anything + /// implementing ResponseError + type Error: actix_web::ResponseError; - #[error("No host provided for URL, {0}")] - /// Missing host - Host(String), -} + /// The future that resolves to the verification state of the signature + type Future: Future>; -impl From for PrepareVerifyError { - fn from(e: http_signature_normalization::PrepareVerifyError) -> Self { - use http_signature_normalization as hsn; + /// Given the algorithm, key_id, signature, and signing_string, produce a future that resulves + /// to a the verification status + fn signature_verify( + &mut self, + algorithm: Option, + key_id: String, + signature: String, + signing_string: String, + ) -> Self::Future; + } - match e { - hsn::PrepareVerifyError::Parse(parse_error) => { - PrepareVerifyError::ParseField(parse_error.missing_field()) - } - hsn::PrepareVerifyError::Validate(validate_error) => match validate_error { - hsn::verify::ValidateError::Missing => PrepareVerifyError::Missing, - hsn::verify::ValidateError::Expired => PrepareVerifyError::Expired, - }, - hsn::PrepareVerifyError::Required(required_error) => { - PrepareVerifyError::Required(required_error) + #[derive(Debug, thiserror::Error)] + /// An error when preparing to verify a request + pub enum PrepareVerifyError { + #[error("Header is missing")] + /// Header is missing + Missing, + + #[error("Header is expired")] + /// Header is expired + Expired, + + #[error("Couldn't parse required field, {0}")] + /// Couldn't parse required field + ParseField(&'static str), + + #[error("Failed to read header, {0}")] + /// An error converting the header to a string for validation + Header(#[from] ToStrError), + + #[error("{0}")] + /// Required headers were missing from request + Required(#[from] RequiredError), + } + + impl From for PrepareVerifyError { + fn from(e: http_signature_normalization::PrepareVerifyError) -> Self { + use http_signature_normalization as hsn; + + match e { + hsn::PrepareVerifyError::Parse(parse_error) => { + PrepareVerifyError::ParseField(parse_error.missing_field()) + } + hsn::PrepareVerifyError::Validate(validate_error) => match validate_error { + hsn::verify::ValidateError::Missing => PrepareVerifyError::Missing, + hsn::verify::ValidateError::Expired => PrepareVerifyError::Expired, + }, + hsn::PrepareVerifyError::Required(required_error) => { + PrepareVerifyError::Required(required_error) + } } } } @@ -405,13 +436,14 @@ impl Config { } } + #[cfg(feature = "client")] /// Begin the process of singing a request pub fn begin_sign( &self, method: &Method, path_and_query: Option<&PathAndQuery>, headers: HeaderMap, - ) -> Result { + ) -> Result { let headers = headers .iter() .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) @@ -425,16 +457,17 @@ impl Config { .config .begin_sign(&method.to_string(), &path_and_query, headers)?; - Ok(Unsigned { unsigned }) + Ok(self::create::Unsigned { unsigned }) } + #[cfg(feature = "server")] /// Begin the proess of verifying a request pub fn begin_verify( &self, method: &Method, path_and_query: Option<&PathAndQuery>, headers: HeaderMap, - ) -> Result { + ) -> Result { let headers = headers .iter() .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) diff --git a/http-signature-normalization-actix/src/sign.rs b/http-signature-normalization-actix/src/sign.rs index db9bd44..f9e65f5 100644 --- a/http-signature-normalization-actix/src/sign.rs +++ b/http-signature-normalization-actix/src/sign.rs @@ -1,4 +1,4 @@ -use actix_web::{error::BlockingError, http::header::InvalidHeaderValue, web}; +use actix_http::{error::BlockingError, http::header::InvalidHeaderValue}; use awc::ClientRequest; use std::{fmt::Display, future::Future, pin::Pin}; @@ -92,7 +92,7 @@ where let key_id = key_id.to_string(); - let signed = web::block(move || unsigned.sign(key_id, f)).await??; + let signed = actix_rt::task::spawn_blocking(move || unsigned.sign(key_id, f)).await.map_err(|_| BlockingError)??; Ok(signed) }