common_s3_headers/
aws_math.rs1use crate::aws_format::{query_params_string, security_token_string, to_short_datetime};
6use hmac::{Hmac, Mac};
7use sha2::{Digest, Sha256};
8use std::borrow::Cow;
9use time::OffsetDateTime;
10
11pub type HmacSha256 = Hmac<Sha256>;
14#[allow(dead_code)]
15type HeaderMap<'a> = Vec<(Cow<'a, str>, Cow<'a, str>)>;
16
17pub fn get_sha256(value: &[u8]) -> String {
36 let mut hasher = <Sha256 as Digest>::new();
38 hasher.update(value);
39 hex::encode(hasher.finalize().as_slice())
40}
41
42pub fn sign(key: &[u8], data: &[u8]) -> HmacSha256 {
44 let mut hmac: HmacSha256 = Hmac::new_from_slice(key).expect("HMAC can take key of any size");
46 hmac.update(data);
47 hmac
48}
49
50fn fold_hmacs(items: &[&[u8]]) -> Vec<u8> {
55 assert!(items.len() > 1);
56
57 let mut hmac: HmacSha256 = sign(items[0], items[1]);
58 for data in items[2..].iter() {
59 hmac = sign(&hmac.finalize().into_bytes(), data);
60 }
61 hmac.finalize().into_bytes().to_vec()
62}
63
64pub fn get_signature_key(datetime: &OffsetDateTime, secret_key: &str, region: &str, service: &str) -> Vec<u8> {
83 let secret = format!("AWS4{}", secret_key);
84 let formatted_datetime = to_short_datetime(datetime);
85
86 fold_hmacs(&[
87 secret.as_bytes(),
88 formatted_datetime.as_bytes(),
89 region.as_bytes(),
90 service.as_bytes(),
91 b"aws4_request",
92 ])
93}
94
95#[allow(dead_code)]
119pub fn authorization_query_params_no_sig(
120 access_key: &str,
121 datetime: &OffsetDateTime,
122 region: &str,
123 service: &str,
124 expires: u32,
125 custom_headers: Option<&HeaderMap>,
126 token: Option<&String>,
127) -> String {
128 let signed_headers = if let Some(custom_headers) = &custom_headers {
129 let mut list = Vec::with_capacity(custom_headers.len() + 1);
130 list.push("host");
131 custom_headers.iter().for_each(|(k, _)| list.push(k));
132 list.sort();
133 list
134 } else {
135 vec!["host"]
136 };
137
138 let mut query_params = query_params_string(&signed_headers, access_key, datetime, region, service, expires);
139
140 if let Some(token) = token {
141 query_params += &security_token_string(token);
142 }
143
144 query_params
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::{aws_canonical::to_canonical_headers, aws_format};
151 use common_testing::assert;
152 use hmac::{Hmac, Mac};
153 use sha2::Sha256;
154 use time::Date;
155 use url::Url;
156
157 #[test]
158 fn test_signing_key() {
159 let datetime = &Date::from_calendar_date(2015, 8.try_into().unwrap(), 30)
160 .unwrap()
161 .with_hms(0, 0, 0)
162 .unwrap()
163 .assume_utc();
164 let secret_key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
165 let region = "us-east-1";
166 let service = "iam";
167
168 let result = get_signature_key(datetime, secret_key, region, service);
169
170 assert::equal_hex_bytes(
171 &result,
172 "c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9",
173 );
174 }
175
176 const EXPECTED_SHA: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
177
178 const EXPECTED_CANONICAL_REQUEST: &str = "GET\n\
179 /test.txt\n\
180 \n\
181 host:examplebucket.s3.amazonaws.com\n\
182 range:bytes=0-9\n\
183 x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n\
184 x-amz-date:20130524T000000Z\n\
185 \n\
186 host;range;x-amz-content-sha256;x-amz-date\n\
187 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
188
189 const EXPECTED_STRING_TO_SIGN: &str = "AWS4-HMAC-SHA256\n\
190 20130524T000000Z\n\
191 20130524/us-east-1/s3/aws4_request\n\
192 7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972";
193
194 #[test]
195 fn test_signing() {
196 let url = Url::parse("https://examplebucket.s3.amazonaws.com/test.txt").unwrap();
197 let headers = vec![
198 ("x-amz-date", "20130524T000000Z"),
199 ("Range", "bytes=0-9"),
200 ("Host", "examplebucket.s3.amazonaws.com"),
201 ("x-amz-content-sha256", EXPECTED_SHA),
202 ];
203 let service = "s3";
204 let canonical_headers = to_canonical_headers(&headers);
205 let canonical_string = aws_format::canonical_request_string("GET", &url, &canonical_headers, EXPECTED_SHA);
206 assert_eq!(EXPECTED_CANONICAL_REQUEST, canonical_string);
207
208 let datetime = Date::from_calendar_date(2013, 5.try_into().unwrap(), 24)
209 .unwrap()
210 .with_hms(0, 0, 0)
211 .unwrap()
212 .assume_utc();
213 let string_to_sign = aws_format::string_to_sign(&datetime, "us-east-1", service, &canonical_string);
214 assert_eq!(EXPECTED_STRING_TO_SIGN, string_to_sign);
215
216 let expected = "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41";
217 let secret = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
218 let signing_key = get_signature_key(&datetime, secret, "us-east-1", "s3");
219 let mut hmac = Hmac::<Sha256>::new_from_slice(&signing_key).unwrap();
220 hmac.update(string_to_sign.as_bytes());
221 assert_eq!(expected, hex::encode(hmac.finalize().into_bytes()));
222 }
223}