From 671d08a66815f6445b2da86f2e045ff402a2a83f Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Mon, 5 Aug 2024 10:23:29 -0700 Subject: [PATCH] funds-manager: funds-manager-api: Publish API auth helpers --- funds-manager/funds-manager-api/Cargo.toml | 4 ++ funds-manager/funds-manager-api/src/auth.rs | 53 +++++++++++++++++++ funds-manager/funds-manager-api/src/lib.rs | 2 + funds-manager/funds-manager-server/Cargo.toml | 2 - .../funds-manager-server/src/middleware.rs | 44 +-------------- 5 files changed, 61 insertions(+), 44 deletions(-) create mode 100644 funds-manager/funds-manager-api/src/auth.rs diff --git a/funds-manager/funds-manager-api/Cargo.toml b/funds-manager/funds-manager-api/Cargo.toml index 7406965..8553c2a 100644 --- a/funds-manager/funds-manager-api/Cargo.toml +++ b/funds-manager/funds-manager-api/Cargo.toml @@ -6,6 +6,10 @@ edition = "2021" [dependencies] renegade-api = { package = "external-api", workspace = true } +hmac = "0.12.1" +http = "0.2.12" +itertools = "0.13.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.117" +sha2 = "0.10.7" uuid = "1.7.1" diff --git a/funds-manager/funds-manager-api/src/auth.rs b/funds-manager/funds-manager-api/src/auth.rs new file mode 100644 index 0000000..4d6f7e7 --- /dev/null +++ b/funds-manager/funds-manager-api/src/auth.rs @@ -0,0 +1,53 @@ +//! Auth helpers for the API +use hmac::{Hmac, Mac}; +use http::HeaderMap; +use itertools::Itertools; +use sha2::Sha256; + +/// The header key for the HMAC signature +pub const X_SIGNATURE_HEADER: &str = "X-Signature"; +/// The prefix for Renegade headers, these headers are included in the HMAC +/// signature +pub const RENEGADE_HEADER_PREFIX: &str = "x-renegade-"; + +/// Compute an hmac for the given request +pub fn compute_hmac( + hmac_key: &[u8], + method: &str, + path: &str, + headers: &HeaderMap, + body: &[u8], +) -> Vec { + // Construct the MAC + let mut mac = Hmac::::new_from_slice(hmac_key).expect("HMAC error"); + + // Update with method, path, headers and body in order + mac.update(method.as_bytes()); + mac.update(path.as_bytes()); + add_headers_to_hmac(&mut mac, headers); + mac.update(body); + + // Check the signature + mac.finalize().into_bytes().to_vec() +} + +/// Hash headers into an HMAC +fn add_headers_to_hmac(mac: &mut Hmac, headers: &HeaderMap) { + let mut renegade_headers = headers + .iter() + .filter_map(|(k, v)| { + let key = k.to_string().to_lowercase(); + if key.starts_with(RENEGADE_HEADER_PREFIX) { + Some((key, v)) + } else { + None + } + }) + .collect_vec(); + renegade_headers.sort_by(|a, b| a.0.cmp(&b.0)); + + for (key, value) in renegade_headers { + mac.update(key.as_bytes()); + mac.update(value.as_bytes()); + } +} diff --git a/funds-manager/funds-manager-api/src/lib.rs b/funds-manager/funds-manager-api/src/lib.rs index eda2147..8c452fb 100644 --- a/funds-manager/funds-manager-api/src/lib.rs +++ b/funds-manager/funds-manager-api/src/lib.rs @@ -2,6 +2,8 @@ #![deny(missing_docs)] #![deny(clippy::missing_docs_in_private_items)] +pub mod auth; + use renegade_api::types::ApiWallet; use serde::{Deserialize, Serialize}; use uuid::Uuid; diff --git a/funds-manager/funds-manager-server/Cargo.toml b/funds-manager/funds-manager-server/Cargo.toml index 880e639..aa0b9c0 100644 --- a/funds-manager/funds-manager-server/Cargo.toml +++ b/funds-manager/funds-manager-server/Cargo.toml @@ -15,9 +15,7 @@ check-revoke = false clap = { version = "4.5.3", features = ["derive", "env"] } funds-manager-api = { path = "../funds-manager-api" } hex = "0.4.3" -hmac = "0.12.1" http-body-util = "0.1.0" -sha2 = "0.10.6" tokio = { version = "1.10", features = ["full"] } warp = "0.3" diff --git a/funds-manager/funds-manager-server/src/middleware.rs b/funds-manager/funds-manager-server/src/middleware.rs index 49684dd..ad234d1 100644 --- a/funds-manager/funds-manager-server/src/middleware.rs +++ b/funds-manager/funds-manager-server/src/middleware.rs @@ -3,19 +3,11 @@ use crate::error::ApiError; use crate::Server; use bytes::Bytes; -use hmac::{Hmac, Mac}; -use itertools::Itertools; +use funds_manager_api::auth::{compute_hmac, X_SIGNATURE_HEADER}; use serde::de::DeserializeOwned; -use sha2::Sha256; use std::sync::Arc; use warp::Filter; -/// The header key for the HMAC signature -const X_SIGNATURE_HEADER: &str = "X-Signature"; -/// The prefix for Renegade headers, these headers are included in the HMAC -/// signature -const RENEGADE_HEADER_PREFIX: &str = "x-renegade-"; - /// Add HMAC authentication to a route pub(crate) fn with_hmac_auth( server: Arc, @@ -54,18 +46,7 @@ async fn verify_hmac( }, }; - // Construct the MAC - let mut mac = Hmac::::new_from_slice(hmac_key) - .map_err(|_| warp::reject::custom(ApiError::InternalError("HMAC error".to_string())))?; - - // Update with method, path, headers and body in order - mac.update(method.as_str().as_bytes()); - mac.update(path.as_str().as_bytes()); - add_headers_to_hmac(&mut mac, &headers); - mac.update(&body); - - // Check the signature - let expected = mac.finalize().into_bytes(); + let expected = compute_hmac(hmac_key, method.as_str(), path.as_str(), &headers, &body); let provided = hex::decode(signature) .map_err(|_| warp::reject::custom(ApiError::BadRequest("Invalid signature".to_string())))?; if expected.as_slice() != provided.as_slice() { @@ -77,27 +58,6 @@ async fn verify_hmac( Ok(body) } -/// Hash headers into an HMAC -fn add_headers_to_hmac(mac: &mut Hmac, headers: &warp::http::HeaderMap) { - let mut renegade_headers = headers - .iter() - .filter_map(|(k, v)| { - let key = k.as_str().to_lowercase(); - if key.starts_with(RENEGADE_HEADER_PREFIX) { - Some((key, v.to_str().unwrap_or("").to_string())) - } else { - None - } - }) - .collect_vec(); - renegade_headers.sort_by(|a, b| a.0.cmp(&b.0)); - - for (key, value) in renegade_headers { - mac.update(key.as_bytes()); - mac.update(value.as_bytes()); - } -} - /// Extract a JSON body from a request #[allow(clippy::needless_pass_by_value)] pub fn with_json_body(body: Bytes) -> Result {