Skip to content

Commit

Permalink
Fix handling of repeated headers in AWS request canonicalization (#2261)
Browse files Browse the repository at this point in the history
  • Loading branch information
nipunn1313 authored Feb 3, 2023
1 parent a06f21b commit f8a799d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 10 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,11 @@ The rationale behind this change is that the previous design was tailored toward
references = ["smithy-rs#2276"]
meta = { "breaking" = true, "tada" = false, "bug" = true, "target" = "server"}
author = "hlbarber"

[[aws-sdk-rust]]
message = """
Fix request canonicalization for HTTP requests with repeated headers (for example S3's `GetObjectAttributes`). Previously requests with repeated headers would fail with a 403 signature mismatch due to this bug.
"""
references = ["smithy-rs#2261", "aws-sdk-rust#720"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "nipunn1313"
55 changes: 45 additions & 10 deletions aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::http_request::url_escape::percent_encode_path;
use crate::http_request::PercentEncodingMode;
use crate::http_request::{PayloadChecksumKind, SignableBody, SignatureLocation, SigningParams};
use crate::sign::sha256_hex_string;
use http::header::{HeaderName, HOST};
use http::header::{AsHeaderName, HeaderName, HOST};
use http::{HeaderMap, HeaderValue, Method, Uri};
use std::borrow::Cow;
use std::cmp::Ordering;
Expand Down Expand Up @@ -225,7 +225,7 @@ impl<'a> CanonicalRequest<'a> {
}

let mut signed_headers = Vec::with_capacity(canonical_headers.len());
for (name, _) in &canonical_headers {
for name in canonical_headers.keys() {
if let Some(excluded_headers) = params.settings.excluded_headers.as_ref() {
if excluded_headers.contains(name) {
continue;
Expand Down Expand Up @@ -328,6 +328,19 @@ impl<'a> CanonicalRequest<'a> {
canonical_headers.insert(x_amz_date, date_header.clone());
date_header
}

fn header_values_for(&self, key: impl AsHeaderName) -> String {
let values: Vec<&str> = self
.headers
.get_all(key)
.into_iter()
.map(|value| {
std::str::from_utf8(value.as_bytes())
.expect("SDK request header values are valid UTF-8")
})
.collect();
values.join(",")
}
}

impl<'a> fmt::Display for CanonicalRequest<'a> {
Expand All @@ -337,15 +350,8 @@ impl<'a> fmt::Display for CanonicalRequest<'a> {
writeln!(f, "{}", self.params.as_deref().unwrap_or(""))?;
// write out _all_ the headers
for header in &self.values.signed_headers().headers {
// a missing header is a bug, so we should panic.
let value = &self.headers[&header.0];
write!(f, "{}:", header.0.as_str())?;
writeln!(
f,
"{}",
std::str::from_utf8(value.as_bytes())
.expect("SDK request header values are valid UTF-8")
)?;
writeln!(f, "{}", self.header_values_for(&header.0))?;
}
writeln!(f)?;
// write out the signed headers
Expand Down Expand Up @@ -538,6 +544,35 @@ mod tests {
}
}

#[test]
fn test_repeated_header() {
let mut req = test_request("get-vanilla-query-order-key-case");
req.headers_mut().append(
"x-amz-object-attributes",
HeaderValue::from_static("Checksum"),
);
req.headers_mut().append(
"x-amz-object-attributes",
HeaderValue::from_static("ObjectSize"),
);
let req = SignableRequest::from(&req);
let settings = SigningSettings {
payload_checksum_kind: PayloadChecksumKind::XAmzSha256,
..Default::default()
};
let signing_params = signing_params(settings);
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();

assert_eq!(
creq.values.signed_headers().to_string(),
"host;x-amz-content-sha256;x-amz-date;x-amz-object-attributes"
);
assert_eq!(
creq.header_values_for("x-amz-object-attributes"),
"Checksum,ObjectSize",
);
}

#[test]
fn test_set_xamz_sha_256() {
let req = test_request("get-vanilla-query-order-key-case");
Expand Down

0 comments on commit f8a799d

Please sign in to comment.