Skip to content

Commit f8a799d

Browse files
authored
Fix handling of repeated headers in AWS request canonicalization (#2261)
1 parent a06f21b commit f8a799d

File tree

2 files changed

+53
-10
lines changed

2 files changed

+53
-10
lines changed

CHANGELOG.next.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,11 @@ The rationale behind this change is that the previous design was tailored toward
124124
references = ["smithy-rs#2276"]
125125
meta = { "breaking" = true, "tada" = false, "bug" = true, "target" = "server"}
126126
author = "hlbarber"
127+
128+
[[aws-sdk-rust]]
129+
message = """
130+
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.
131+
"""
132+
references = ["smithy-rs#2261", "aws-sdk-rust#720"]
133+
meta = { "breaking" = false, "tada" = false, "bug" = true }
134+
author = "nipunn1313"

aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::http_request::url_escape::percent_encode_path;
1313
use crate::http_request::PercentEncodingMode;
1414
use crate::http_request::{PayloadChecksumKind, SignableBody, SignatureLocation, SigningParams};
1515
use crate::sign::sha256_hex_string;
16-
use http::header::{HeaderName, HOST};
16+
use http::header::{AsHeaderName, HeaderName, HOST};
1717
use http::{HeaderMap, HeaderValue, Method, Uri};
1818
use std::borrow::Cow;
1919
use std::cmp::Ordering;
@@ -225,7 +225,7 @@ impl<'a> CanonicalRequest<'a> {
225225
}
226226

227227
let mut signed_headers = Vec::with_capacity(canonical_headers.len());
228-
for (name, _) in &canonical_headers {
228+
for name in canonical_headers.keys() {
229229
if let Some(excluded_headers) = params.settings.excluded_headers.as_ref() {
230230
if excluded_headers.contains(name) {
231231
continue;
@@ -328,6 +328,19 @@ impl<'a> CanonicalRequest<'a> {
328328
canonical_headers.insert(x_amz_date, date_header.clone());
329329
date_header
330330
}
331+
332+
fn header_values_for(&self, key: impl AsHeaderName) -> String {
333+
let values: Vec<&str> = self
334+
.headers
335+
.get_all(key)
336+
.into_iter()
337+
.map(|value| {
338+
std::str::from_utf8(value.as_bytes())
339+
.expect("SDK request header values are valid UTF-8")
340+
})
341+
.collect();
342+
values.join(",")
343+
}
331344
}
332345

333346
impl<'a> fmt::Display for CanonicalRequest<'a> {
@@ -337,15 +350,8 @@ impl<'a> fmt::Display for CanonicalRequest<'a> {
337350
writeln!(f, "{}", self.params.as_deref().unwrap_or(""))?;
338351
// write out _all_ the headers
339352
for header in &self.values.signed_headers().headers {
340-
// a missing header is a bug, so we should panic.
341-
let value = &self.headers[&header.0];
342353
write!(f, "{}:", header.0.as_str())?;
343-
writeln!(
344-
f,
345-
"{}",
346-
std::str::from_utf8(value.as_bytes())
347-
.expect("SDK request header values are valid UTF-8")
348-
)?;
354+
writeln!(f, "{}", self.header_values_for(&header.0))?;
349355
}
350356
writeln!(f)?;
351357
// write out the signed headers
@@ -538,6 +544,35 @@ mod tests {
538544
}
539545
}
540546

547+
#[test]
548+
fn test_repeated_header() {
549+
let mut req = test_request("get-vanilla-query-order-key-case");
550+
req.headers_mut().append(
551+
"x-amz-object-attributes",
552+
HeaderValue::from_static("Checksum"),
553+
);
554+
req.headers_mut().append(
555+
"x-amz-object-attributes",
556+
HeaderValue::from_static("ObjectSize"),
557+
);
558+
let req = SignableRequest::from(&req);
559+
let settings = SigningSettings {
560+
payload_checksum_kind: PayloadChecksumKind::XAmzSha256,
561+
..Default::default()
562+
};
563+
let signing_params = signing_params(settings);
564+
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
565+
566+
assert_eq!(
567+
creq.values.signed_headers().to_string(),
568+
"host;x-amz-content-sha256;x-amz-date;x-amz-object-attributes"
569+
);
570+
assert_eq!(
571+
creq.header_values_for("x-amz-object-attributes"),
572+
"Checksum,ObjectSize",
573+
);
574+
}
575+
541576
#[test]
542577
fn test_set_xamz_sha_256() {
543578
let req = test_request("get-vanilla-query-order-key-case");

0 commit comments

Comments
 (0)