From 4c1d42cc5fcaa69818ec177f19577ad57952a117 Mon Sep 17 00:00:00 2001 From: Alex Auvolat <alex@adnab.me> Date: Tue, 27 Feb 2024 23:33:26 +0100 Subject: [PATCH] [fix-presigned] add back anonymous request code path + refactoring --- src/api/signature/payload.rs | 57 ++++++++++++++++++---------------- src/api/signature/streaming.rs | 13 +++++--- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/api/signature/payload.rs b/src/api/signature/payload.rs index 8841a5e5..c88bb144 100644 --- a/src/api/signature/payload.rs +++ b/src/api/signature/payload.rs @@ -40,17 +40,26 @@ pub async fn check_payload_signature( ) -> Result<(Option<Key>, Option<Hash>), Error> { let query = parse_query_map(request.uri())?; - let res = if query.contains_key(X_AMZ_ALGORITHM.as_str()) { + if query.contains_key(X_AMZ_ALGORITHM.as_str()) { check_presigned_signature(garage, service, request, query).await - } else { + } else if request.headers().contains_key(AUTHORIZATION) { check_standard_signature(garage, service, request, query).await - }; - - if let Err(e) = &res { - error!("ERROR IN SIGNATURE\n{:?}\n{}", request, e); + } else { + // Unsigned (anonymous) request + let content_sha256 = request + .headers() + .get("x-amz-content-sha256") + .filter(|c| c.as_bytes() != UNSIGNED_PAYLOAD.as_bytes()); + if let Some(content_sha256) = content_sha256 { + let sha256 = hex::decode(content_sha256) + .ok() + .and_then(|bytes| Hash::try_from(&bytes)) + .ok_or_bad_request("Invalid content sha256 hash")?; + Ok((None, Some(sha256))) + } else { + Ok((None, None)) + } } - - res } async fn check_standard_signature( @@ -63,8 +72,11 @@ async fn check_standard_signature( // Verify that all necessary request headers are signed let signed_headers = split_signed_headers(&authorization)?; + if !signed_headers.contains(&HOST) { + return Err(Error::bad_request("Header `Host` should be signed")); + } for (name, _) in request.headers().iter() { - if name.as_str().starts_with("x-amz-") || name == CONTENT_TYPE { + if name == CONTENT_TYPE || name.as_str().starts_with("x-amz-") { if !signed_headers.contains(name) { return Err(Error::bad_request(format!( "Header `{}` should be signed", @@ -73,9 +85,6 @@ async fn check_standard_signature( } } } - if !signed_headers.contains(&HOST) { - return Err(Error::bad_request("Header `Host` should be signed")); - } let canonical_request = canonical_request( service, @@ -122,6 +131,9 @@ async fn check_presigned_signature( // Check that all mandatory signed headers are included let signed_headers = split_signed_headers(&authorization)?; + if !signed_headers.contains(&HOST) { + return Err(Error::bad_request("Header `Host` should be signed")); + } for (name, _) in request.headers().iter() { if name.as_str().starts_with("x-amz-") { if !signed_headers.contains(name) { @@ -132,9 +144,6 @@ async fn check_presigned_signature( } } } - if !signed_headers.contains(&HOST) { - return Err(Error::bad_request("Header `Host` should be signed")); - } query.remove(X_AMZ_SIGNATURE.as_str()); let canonical_request = canonical_request( @@ -254,21 +263,17 @@ pub fn canonical_request( // Canonical header string calculated from signed headers signed_headers.sort_by(|h1, h2| h1.as_str().cmp(h2.as_str())); - let canonical_header_string = { - let mut items = Vec::with_capacity(signed_headers.len()); - for name in signed_headers.iter() { + let canonical_header_string = signed_headers + .iter() + .map(|name| { let value = headers .get(name) .ok_or_bad_request(format!("signed header `{}` is not present", name))? .to_str()?; - items.push((name, value)); - } - items - .iter() - .map(|(key, value)| format!("{}:{}", key.as_str(), value.trim())) - .collect::<Vec<_>>() - .join("\n") - }; + Ok(format!("{}:{}", name.as_str(), value.trim())) + }) + .collect::<Result<Vec<String>, Error>>()? + .join("\n"); let signed_headers = signed_headers.join(";"); let list = [ diff --git a/src/api/signature/streaming.rs b/src/api/signature/streaming.rs index a2a71f6b..e223d1b1 100644 --- a/src/api/signature/streaming.rs +++ b/src/api/signature/streaming.rs @@ -15,6 +15,11 @@ use super::{compute_scope, sha256sum, HmacSha256, LONG_DATETIME}; use crate::helpers::*; use crate::signature::error::*; +use crate::signature::payload::{ + STREAMING_AWS4_HMAC_SHA256_PAYLOAD, X_AMZ_CONTENT_SH256, X_AMZ_DATE, +}; + +pub const AWS4_HMAC_SHA256_PAYLOAD: &str = "AWS4-HMAC-SHA256-PAYLOAD"; pub type ReqBody = BoxBody<Error>; @@ -25,8 +30,8 @@ pub fn parse_streaming_body( region: &str, service: &str, ) -> Result<Request<ReqBody>, Error> { - match req.headers().get("x-amz-content-sha256") { - Some(header) if header == "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" => { + match req.headers().get(X_AMZ_CONTENT_SH256) { + Some(header) if header == STREAMING_AWS4_HMAC_SHA256_PAYLOAD => { let signature = content_sha256 .take() .ok_or_bad_request("No signature provided")?; @@ -39,7 +44,7 @@ pub fn parse_streaming_body( let date = req .headers() - .get("x-amz-date") + .get(X_AMZ_DATE) .ok_or_bad_request("Missing X-Amz-Date field")? .to_str()?; let date: NaiveDateTime = NaiveDateTime::parse_from_str(date, LONG_DATETIME) @@ -75,7 +80,7 @@ fn compute_streaming_payload_signature( content_sha256: Hash, ) -> Result<Hash, Error> { let string_to_sign = [ - "AWS4-HMAC-SHA256-PAYLOAD", + AWS4_HMAC_SHA256_PAYLOAD, &date.format(LONG_DATETIME).to_string(), scope, &hex::encode(previous_signature),