Backport AWS signature verification refactoring and fixes to v0.8.x #744
1 changed files with 58 additions and 23 deletions
|
@ -1,11 +1,14 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::HttpConnector;
|
||||||
|
use hyper::header::{
|
||||||
|
HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_ENCODING, CONTENT_LENGTH, HOST,
|
||||||
|
};
|
||||||
use hyper::{Body, Client, Method, Request, Response, Uri};
|
use hyper::{Body, Client, Method, Request, Response, Uri};
|
||||||
|
|
||||||
use super::garage::{Instance, Key};
|
use super::garage::{Instance, Key};
|
||||||
|
@ -168,54 +171,85 @@ impl<'a> RequestBuilder<'a> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let streaming_signer = signer.clone();
|
let streaming_signer = signer.clone();
|
||||||
|
|
||||||
let mut all_headers = self.signed_headers.clone();
|
let mut all_headers = self
|
||||||
|
.signed_headers
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
HeaderName::try_from(k).expect("invalid header name"),
|
||||||
|
HeaderValue::try_from(v).expect("invalid header value"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<HeaderMap>();
|
||||||
|
|
||||||
let date = now.format(signature::LONG_DATETIME).to_string();
|
let date = now.format(signature::LONG_DATETIME).to_string();
|
||||||
all_headers.insert("x-amz-date".to_owned(), date);
|
all_headers.insert(
|
||||||
all_headers.insert("host".to_owned(), host);
|
signature::payload::X_AMZ_DATE,
|
||||||
|
HeaderValue::from_str(&date).unwrap(),
|
||||||
|
);
|
||||||
|
all_headers.insert(HOST, HeaderValue::from_str(&host).unwrap());
|
||||||
|
|
||||||
let body_sha = match self.body_signature {
|
let body_sha = match self.body_signature {
|
||||||
BodySignature::Unsigned => "UNSIGNED-PAYLOAD".to_owned(),
|
BodySignature::Unsigned => "UNSIGNED-PAYLOAD".to_owned(),
|
||||||
BodySignature::Classic => hex::encode(garage_util::data::sha256sum(&self.body)),
|
BodySignature::Classic => hex::encode(garage_util::data::sha256sum(&self.body)),
|
||||||
BodySignature::Streaming(size) => {
|
BodySignature::Streaming(size) => {
|
||||||
all_headers.insert("content-encoding".to_owned(), "aws-chunked".to_owned());
|
|
||||||
all_headers.insert(
|
all_headers.insert(
|
||||||
"x-amz-decoded-content-length".to_owned(),
|
CONTENT_ENCODING,
|
||||||
self.body.len().to_string(),
|
HeaderValue::from_str("aws-chunked").unwrap(),
|
||||||
|
);
|
||||||
|
all_headers.insert(
|
||||||
|
HeaderName::from_static("x-amz-decoded-content-length"),
|
||||||
|
HeaderValue::from_str(&self.body.len().to_string()).unwrap(),
|
||||||
);
|
);
|
||||||
// Get lenght of body by doing the conversion to a streaming body with an
|
// Get lenght of body by doing the conversion to a streaming body with an
|
||||||
// invalid signature (we don't know the seed) just to get its length. This
|
// invalid signature (we don't know the seed) just to get its length. This
|
||||||
// is a pretty lazy and inefficient way to do it, but it's enought for test
|
// is a pretty lazy and inefficient way to do it, but it's enought for test
|
||||||
// code.
|
// code.
|
||||||
all_headers.insert(
|
all_headers.insert(
|
||||||
"content-length".to_owned(),
|
CONTENT_LENGTH,
|
||||||
to_streaming_body(&self.body, size, String::new(), signer.clone(), now, "")
|
to_streaming_body(&self.body, size, String::new(), signer.clone(), now, "")
|
||||||
.len()
|
.len()
|
||||||
.to_string(),
|
.to_string()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
"STREAMING-AWS4-HMAC-SHA256-PAYLOAD".to_owned()
|
"STREAMING-AWS4-HMAC-SHA256-PAYLOAD".to_owned()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
all_headers.insert("x-amz-content-sha256".to_owned(), body_sha.clone());
|
all_headers.insert(
|
||||||
|
signature::payload::X_AMZ_CONTENT_SH256,
|
||||||
|
HeaderValue::from_str(&body_sha).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut signed_headers = all_headers
|
let mut signed_headers = all_headers.keys().cloned().collect::<Vec<_>>();
|
||||||
|
signed_headers.sort_by(|h1, h2| h1.as_str().cmp(h2.as_str()));
|
||||||
|
let signed_headers_str = signed_headers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, _)| k.as_ref())
|
.map(ToString::to_string)
|
||||||
.collect::<Vec<&str>>();
|
.collect::<Vec<_>>()
|
||||||
signed_headers.sort();
|
.join(";");
|
||||||
let signed_headers = signed_headers.join(";");
|
|
||||||
|
|
||||||
all_headers.extend(self.unsigned_headers.clone());
|
all_headers.extend(self.unsigned_headers.iter().map(|(k, v)| {
|
||||||
|
(
|
||||||
|
HeaderName::try_from(k).expect("invalid header name"),
|
||||||
|
HeaderValue::try_from(v).expect("invalid header value"),
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let uri = Uri::try_from(&uri).unwrap();
|
||||||
|
let query = signature::payload::parse_query_map(&uri).unwrap();
|
||||||
|
|
||||||
let canonical_request = signature::payload::canonical_request(
|
let canonical_request = signature::payload::canonical_request(
|
||||||
self.service,
|
self.service,
|
||||||
&self.method,
|
&self.method,
|
||||||
&Uri::try_from(&uri).unwrap(),
|
uri.path(),
|
||||||
|
&query,
|
||||||
&all_headers,
|
&all_headers,
|
||||||
&signed_headers,
|
&signed_headers,
|
||||||
&body_sha,
|
&body_sha,
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let string_to_sign = signature::payload::string_to_sign(&now, &scope, &canonical_request);
|
let string_to_sign = signature::payload::string_to_sign(&now, &scope, &canonical_request);
|
||||||
|
|
||||||
|
@ -223,14 +257,15 @@ impl<'a> RequestBuilder<'a> {
|
||||||
let signature = hex::encode(signer.finalize().into_bytes());
|
let signature = hex::encode(signer.finalize().into_bytes());
|
||||||
let authorization = format!(
|
let authorization = format!(
|
||||||
"AWS4-HMAC-SHA256 Credential={}/{},SignedHeaders={},Signature={}",
|
"AWS4-HMAC-SHA256 Credential={}/{},SignedHeaders={},Signature={}",
|
||||||
self.requester.key.id, scope, signed_headers, signature
|
self.requester.key.id, scope, signed_headers_str, signature
|
||||||
|
);
|
||||||
|
all_headers.insert(
|
||||||
|
AUTHORIZATION,
|
||||||
|
HeaderValue::from_str(&authorization).unwrap(),
|
||||||
);
|
);
|
||||||
all_headers.insert("authorization".to_owned(), authorization);
|
|
||||||
|
|
||||||
let mut request = Request::builder();
|
let mut request = Request::builder();
|
||||||
for (k, v) in all_headers {
|
*request.headers_mut().unwrap() = all_headers;
|
||||||
request = request.header(k, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = if let BodySignature::Streaming(size) = self.body_signature {
|
let body = if let BodySignature::Streaming(size) = self.body_signature {
|
||||||
to_streaming_body(&self.body, size, signature, streaming_signer, now, &scope)
|
to_streaming_body(&self.body, size, signature, streaming_signer, now, &scope)
|
||||||
|
|
Loading…
Reference in a new issue