From ea411abf09ef515635aa02d644c492d9d5d75f04 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Mar 2022 01:01:21 +0000 Subject: [PATCH] add verify signature scheme verify method --- README.md | 1 + .../examples/{hmac.rs => req-sig.rs} | 54 ++++++++++++------- actix-web-lab/src/request_signature.rs | 44 ++++++++++----- 3 files changed, 69 insertions(+), 30 deletions(-) create mode 120000 README.md rename actix-web-lab/examples/{hmac.rs => req-sig.rs} (67%) diff --git a/README.md b/README.md new file mode 120000 index 00000000..2e749a85 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +actix-web-lab/README.md \ No newline at end of file diff --git a/actix-web-lab/examples/hmac.rs b/actix-web-lab/examples/req-sig.rs similarity index 67% rename from actix-web-lab/examples/hmac.rs rename to actix-web-lab/examples/req-sig.rs index d5471a00..017881b6 100644 --- a/actix-web-lab/examples/hmac.rs +++ b/actix-web-lab/examples/req-sig.rs @@ -10,6 +10,7 @@ use actix_web::{ use actix_web_lab::extract::{RequestSignature, RequestSignatureScheme}; use async_trait::async_trait; use digest::{CtOutput, Digest, Mac}; +use generic_array::GenericArray; use hmac::SimpleHmac; use sha2::{Sha256, Sha512}; use tracing::info; @@ -22,15 +23,16 @@ async fn lookup_public_key_in_db(_db: &(), val: T) -> T { } /// Extracts user's public key from request and pretends to look up secret key in the DB. -async fn cf_extract_key(req: &HttpRequest) -> actix_web::Result> { +async fn get_base64_api_key(req: &HttpRequest) -> actix_web::Result> { // public key, not encryption key - let hdr = req.headers().get("Api-Key"); - let pub_key = hdr + let pub_key = req + .headers() + .get("Api-Key") .map(HeaderValue::as_bytes) .map(base64::decode) .transpose() - .map_err(error::ErrorInternalServerError)? - .ok_or_else(|| error::ErrorUnauthorized("key not provided"))?; + .map_err(|_| error::ErrorInternalServerError("invalid api key"))? + .ok_or_else(|| error::ErrorUnauthorized("api key not provided"))?; // let db = req.app_data::().unwrap(); let secret_key = lookup_public_key_in_db(&db, pub_key).await; @@ -38,6 +40,16 @@ async fn cf_extract_key(req: &HttpRequest) -> actix_web::Result> { Ok(secret_key) } +fn get_user_signature(req: &HttpRequest) -> actix_web::Result> { + req.headers() + .get("Signature") + .map(HeaderValue::as_bytes) + .map(base64::decode) + .transpose() + .map_err(|_| error::ErrorInternalServerError("invalid signature"))? + .ok_or_else(|| error::ErrorUnauthorized("signature not provided")) +} + #[derive(Debug, Default)] struct ExampleApi { /// Key derived from fetching user's API private key from database. @@ -53,7 +65,7 @@ impl RequestSignatureScheme for ExampleApi { type Error = Error; async fn init(req: &HttpRequest) -> Result { - let key = cf_extract_key(req).await?; + let key = get_base64_api_key(req).await?; let mut hasher = Sha256::new(); @@ -62,7 +74,6 @@ impl RequestSignatureScheme for ExampleApi { Digest::update(&mut hasher, nonce.as_bytes()); } - // path is not optional but easier to write like this if let Some(path) = req.uri().path_and_query() { Digest::update(&mut hasher, path.as_str().as_bytes()) } @@ -75,20 +86,33 @@ impl RequestSignatureScheme for ExampleApi { Ok(()) } - async fn finalize( - &mut self, - _req: &HttpRequest, - ) -> Result, Self::Error> { + async fn finalize(self, _req: &HttpRequest) -> Result, Self::Error> { println!("using key: {:X?}", &self.key); let mut hmac = >::new_from_slice(&self.key).unwrap(); - let payload_hash = self.hasher.finalize_reset(); + let payload_hash = self.hasher.finalize(); println!("payload hash: {payload_hash:X?}"); Mac::update(&mut hmac, &payload_hash); Ok(hmac.finalize()) } + + fn verify( + signature: CtOutput, + req: &HttpRequest, + ) -> Result, Self::Error> { + let user_sig = get_user_signature(req)?; + let user_sig = CtOutput::new(GenericArray::from_slice(&user_sig).to_owned()); + + if signature == user_sig { + Ok(signature) + } else { + Err(error::ErrorUnauthorized( + "given signature does not match calculated signature", + )) + } + } } #[actix_web::main] @@ -101,12 +125,6 @@ async fn main() -> io::Result<()> { App::new().wrap(Logger::default().log_target("@")).route( "/", web::post().to(|body: RequestSignature| async move { - // if !body.verify_slice(b"correct-signature") { - // return "HMAC signature not correct"; - // } - - // "OK" - let (body, sig) = body.into_parts(); format!("{body:?}\n\n{sig:x?}") }), diff --git a/actix-web-lab/src/request_signature.rs b/actix-web-lab/src/request_signature.rs index 049cdf37..b697e57e 100644 --- a/actix-web-lab/src/request_signature.rs +++ b/actix-web-lab/src/request_signature.rs @@ -35,10 +35,23 @@ pub trait RequestSignatureScheme: Sized { /// - post-body hash updates /// - finalization /// - hash output - async fn finalize(&mut self, req: &HttpRequest) -> Result, Self::Error>; + async fn finalize(self, req: &HttpRequest) -> Result, Self::Error>; + + /// todo + /// + /// - return signature if valid + /// - return error if not + /// - by default the signature not checked and returned as-is + #[allow(unused_variables)] + fn verify( + signature: CtOutput, + req: &HttpRequest, + ) -> Result, Self::Error> { + Ok(signature) + } } -/// Wraps an extractor and calculates a request checksum hash alongside. +/// Wraps an extractor and calculates a request signature hash alongside. /// /// Warning: Currently, this will always take the body meaning that if a body extractor is used, /// this needs to wrap it or else it will not work. @@ -114,17 +127,24 @@ where let body_fut = T::from_request(&req, &mut proxy_payload).map_err(RequestSignatureError::Extractor); + trace!("initializing signature scheme"); let mut sig_scheme = S::init(&req) .await .map_err(RequestSignatureError::Signature)?; // run update function as chunks are yielded from channel - let hash_fut = actix_web::rt::spawn(async move { - while let Some(chunk) = rx.recv().await { - sig_scheme.digest_chunk(&req, chunk).await?; - } + let hash_fut = actix_web::rt::spawn({ + let req = req.clone(); - sig_scheme.finalize(&req).await + async move { + while let Some(chunk) = rx.recv().await { + trace!("digesting chunk"); + sig_scheme.digest_chunk(&req, chunk).await?; + } + + trace!("finalizing signature"); + sig_scheme.finalize(&req).await + } }) .map(Result::unwrap) .map_err(RequestSignatureError::Signature); @@ -132,6 +152,9 @@ where trace!("driving both futures"); let (body, signature) = try_join!(body_fut, hash_fut)?; + trace!("verifying signature"); + let signature = S::verify(signature, &req).map_err(RequestSignatureError::Signature)?; + let out = Self { extractor: body, signature, @@ -186,11 +209,8 @@ mod tests { Ok(()) } - async fn finalize( - &mut self, - _req: &HttpRequest, - ) -> Result, Self::Error> { - let hash = self.0.finalize_reset(); + async fn finalize(self, _req: &HttpRequest) -> Result, Self::Error> { + let hash = self.0.finalize(); Ok(CtOutput::new(hash)) } }