Skip to content

Commit

Permalink
add verify signature scheme verify method
Browse files Browse the repository at this point in the history
  • Loading branch information
robjtede committed Mar 24, 2022
1 parent 2859a21 commit ea411ab
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 30 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,22 +23,33 @@ async fn lookup_public_key_in_db<T>(_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<Vec<u8>> {
async fn get_base64_api_key(req: &HttpRequest) -> actix_web::Result<Vec<u8>> {
// 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::<DbPool>().unwrap();
let secret_key = lookup_public_key_in_db(&db, pub_key).await;

Ok(secret_key)
}

fn get_user_signature(req: &HttpRequest) -> actix_web::Result<Vec<u8>> {
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.
Expand All @@ -53,7 +65,7 @@ impl RequestSignatureScheme for ExampleApi {
type Error = Error;

async fn init(req: &HttpRequest) -> Result<Self, Self::Error> {
let key = cf_extract_key(req).await?;
let key = get_base64_api_key(req).await?;

let mut hasher = Sha256::new();

Expand All @@ -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())
}
Expand All @@ -75,20 +86,33 @@ impl RequestSignatureScheme for ExampleApi {
Ok(())
}

async fn finalize(
&mut self,
_req: &HttpRequest,
) -> Result<CtOutput<Self::Output>, Self::Error> {
async fn finalize(self, _req: &HttpRequest) -> Result<CtOutput<Self::Output>, Self::Error> {
println!("using key: {:X?}", &self.key);

let mut hmac = <SimpleHmac<Sha512>>::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<Self::Output>,
req: &HttpRequest,
) -> Result<CtOutput<Self::Output>, 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]
Expand All @@ -101,12 +125,6 @@ async fn main() -> io::Result<()> {
App::new().wrap(Logger::default().log_target("@")).route(
"/",
web::post().to(|body: RequestSignature<Bytes, ExampleApi>| 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?}")
}),
Expand Down
44 changes: 32 additions & 12 deletions actix-web-lab/src/request_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,23 @@ pub trait RequestSignatureScheme: Sized {
/// - post-body hash updates
/// - finalization
/// - hash output
async fn finalize(&mut self, req: &HttpRequest) -> Result<CtOutput<Self::Output>, Self::Error>;
async fn finalize(self, req: &HttpRequest) -> Result<CtOutput<Self::Output>, 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<Self::Output>,
req: &HttpRequest,
) -> Result<CtOutput<Self::Output>, 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.
Expand Down Expand Up @@ -114,24 +127,34 @@ 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);

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,
Expand Down Expand Up @@ -186,11 +209,8 @@ mod tests {
Ok(())
}

async fn finalize(
&mut self,
_req: &HttpRequest,
) -> Result<CtOutput<Self::Output>, Self::Error> {
let hash = self.0.finalize_reset();
async fn finalize(self, _req: &HttpRequest) -> Result<CtOutput<Self::Output>, Self::Error> {
let hash = self.0.finalize();
Ok(CtOutput::new(hash))
}
}
Expand Down

0 comments on commit ea411ab

Please sign in to comment.