Fix handling of repeated headers in AWS request canonicalization (#2261)

This commit is contained in:
Nipunn Koorapati 2023-02-03 10:15:25 -08:00 committed by GitHub
parent a06f21b076
commit f8a799db2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 10 deletions

View File

@ -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"

View File

@ -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;
@ -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;
@ -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> {
@ -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
@ -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");