1use super::error::SigningError;
7use super::{PayloadChecksumKind, SignatureLocation};
8use crate::http_request::canonical_request::header;
9use crate::http_request::canonical_request::param;
10use crate::http_request::canonical_request::{CanonicalRequest, StringToSign};
11use crate::http_request::error::CanonicalRequestError;
12use crate::http_request::SigningParams;
13use crate::sign::v4;
14#[cfg(feature = "sigv4a")]
15use crate::sign::v4a;
16use crate::{SignatureVersion, SigningOutput};
17use http0::Uri;
18use std::borrow::Cow;
19use std::fmt::{Debug, Formatter};
20use std::str;
21
22#[derive(Debug)]
24#[non_exhaustive]
25pub struct SignableRequest<'a> {
26 method: &'a str,
27 uri: Uri,
28 headers: Vec<(&'a str, &'a str)>,
29 body: SignableBody<'a>,
30}
31
32impl<'a> SignableRequest<'a> {
33 pub fn new(
35 method: &'a str,
36 uri: impl Into<Cow<'a, str>>,
37 headers: impl Iterator<Item = (&'a str, &'a str)>,
38 body: SignableBody<'a>,
39 ) -> Result<Self, SigningError> {
40 let uri = uri
41 .into()
42 .parse()
43 .map_err(|e| SigningError::from(CanonicalRequestError::from(e)))?;
44 let headers = headers.collect();
45 Ok(Self {
46 method,
47 uri,
48 headers,
49 body,
50 })
51 }
52
53 pub(crate) fn uri(&self) -> &Uri {
55 &self.uri
56 }
57
58 pub(crate) fn method(&self) -> &str {
60 self.method
61 }
62
63 pub(crate) fn headers(&self) -> &[(&str, &str)] {
65 self.headers.as_slice()
66 }
67
68 pub fn body(&self) -> &SignableBody<'_> {
70 &self.body
71 }
72}
73
74#[derive(Debug, Clone, Eq, PartialEq)]
76#[non_exhaustive]
77pub enum SignableBody<'a> {
78 Bytes(&'a [u8]),
80
81 UnsignedPayload,
86
87 Precomputed(String),
91
92 StreamingUnsignedPayloadTrailer,
94}
95
96#[derive(Debug)]
98pub struct SigningInstructions {
99 headers: Vec<Header>,
100 params: Vec<(&'static str, Cow<'static, str>)>,
101}
102
103pub struct Header {
105 key: &'static str,
106 value: String,
107 sensitive: bool,
108}
109
110impl Debug for Header {
111 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112 let mut fmt = f.debug_struct("Header");
113 fmt.field("key", &self.key);
114 let value = if self.sensitive {
115 "** REDACTED **"
116 } else {
117 &self.value
118 };
119 fmt.field("value", &value);
120 fmt.finish()
121 }
122}
123
124impl Header {
125 pub fn name(&self) -> &'static str {
127 self.key
128 }
129
130 pub fn value(&self) -> &str {
132 &self.value
133 }
134
135 pub fn sensitive(&self) -> bool {
137 self.sensitive
138 }
139}
140
141impl SigningInstructions {
142 fn new(headers: Vec<Header>, params: Vec<(&'static str, Cow<'static, str>)>) -> Self {
143 Self { headers, params }
144 }
145
146 pub fn into_parts(self) -> (Vec<Header>, Vec<(&'static str, Cow<'static, str>)>) {
148 (self.headers, self.params)
149 }
150
151 pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
153 self.headers
154 .iter()
155 .map(|header| (header.key, header.value.as_str()))
156 }
157
158 pub fn params(&self) -> &[(&str, Cow<'static, str>)] {
160 self.params.as_slice()
161 }
162
163 #[cfg(any(feature = "http0-compat", test))]
164 pub fn apply_to_request_http0x<B>(self, request: &mut http0::Request<B>) {
166 let (new_headers, new_query) = self.into_parts();
167 for header in new_headers.into_iter() {
168 let mut value = http0::HeaderValue::from_str(&header.value).unwrap();
169 value.set_sensitive(header.sensitive);
170 request.headers_mut().insert(header.key, value);
171 }
172
173 if !new_query.is_empty() {
174 let mut query = aws_smithy_http::query_writer::QueryWriter::new(request.uri());
175 for (name, value) in new_query {
176 query.insert(name, &value);
177 }
178 *request.uri_mut() = query.build_uri();
179 }
180 }
181
182 #[cfg(any(feature = "http1", test))]
183 pub fn apply_to_request_http1x<B>(self, request: &mut http::Request<B>) {
185 let (new_headers, new_query) = self.into_parts();
188 for header in new_headers.into_iter() {
189 let mut value = http::HeaderValue::from_str(&header.value).unwrap();
190 value.set_sensitive(header.sensitive);
191 request.headers_mut().insert(header.key, value);
192 }
193
194 if !new_query.is_empty() {
195 let mut query = aws_smithy_http::query_writer::QueryWriter::new_from_string(
196 &request.uri().to_string(),
197 )
198 .expect("unreachable: URI is valid");
199 for (name, value) in new_query {
200 query.insert(name, &value);
201 }
202 *request.uri_mut() = query
203 .build_uri()
204 .to_string()
205 .parse()
206 .expect("unreachable: URI is valid");
207 }
208 }
209}
210
211pub fn sign<'a>(
214 request: SignableRequest<'a>,
215 params: &'a SigningParams<'a>,
216) -> Result<SigningOutput<SigningInstructions>, SigningError> {
217 tracing::trace!(request = ?request, params = ?params, "signing request");
218 match params.settings().signature_location {
219 SignatureLocation::Headers => {
220 let (signing_headers, signature) =
221 calculate_signing_headers(&request, params)?.into_parts();
222 Ok(SigningOutput::new(
223 SigningInstructions::new(signing_headers, vec![]),
224 signature,
225 ))
226 }
227 SignatureLocation::QueryParams => {
228 let (params, signature) = calculate_signing_params(&request, params)?;
229 Ok(SigningOutput::new(
230 SigningInstructions::new(vec![], params),
231 signature,
232 ))
233 }
234 }
235}
236
237type CalculatedParams = Vec<(&'static str, Cow<'static, str>)>;
238
239fn calculate_signing_params<'a>(
240 request: &'a SignableRequest<'a>,
241 params: &'a SigningParams<'a>,
242) -> Result<(CalculatedParams, String), SigningError> {
243 let creds = params.credentials()?;
244 let creq = CanonicalRequest::from(request, params)?;
245 let encoded_creq = &v4::sha256_hex_string(creq.to_string().as_bytes());
246
247 let (signature, string_to_sign) = match params {
248 SigningParams::V4(params) => {
249 let string_to_sign =
250 StringToSign::new_v4(params.time, params.region, params.name, encoded_creq)
251 .to_string();
252 let signing_key = v4::generate_signing_key(
253 creds.secret_access_key(),
254 params.time,
255 params.region,
256 params.name,
257 );
258 let signature = v4::calculate_signature(signing_key, string_to_sign.as_bytes());
259 (signature, string_to_sign)
260 }
261 #[cfg(feature = "sigv4a")]
262 SigningParams::V4a(params) => {
263 let string_to_sign =
264 StringToSign::new_v4a(params.time, params.region_set, params.name, encoded_creq)
265 .to_string();
266
267 let secret_key =
268 v4a::generate_signing_key(creds.access_key_id(), creds.secret_access_key());
269 let signature = v4a::calculate_signature(&secret_key, string_to_sign.as_bytes());
270 (signature, string_to_sign)
271 }
272 };
273 tracing::trace!(canonical_request = %creq, string_to_sign = %string_to_sign, "calculated signing parameters");
274
275 let values = creq.values.into_query_params().expect("signing with query");
276 let mut signing_params = vec![
277 (param::X_AMZ_ALGORITHM, Cow::Borrowed(values.algorithm)),
278 (param::X_AMZ_CREDENTIAL, Cow::Owned(values.credential)),
279 (param::X_AMZ_DATE, Cow::Owned(values.date_time)),
280 (param::X_AMZ_EXPIRES, Cow::Owned(values.expires)),
281 (
282 param::X_AMZ_SIGNED_HEADERS,
283 Cow::Owned(values.signed_headers.as_str().into()),
284 ),
285 (param::X_AMZ_SIGNATURE, Cow::Owned(signature.clone())),
286 ];
287
288 #[cfg(feature = "sigv4a")]
289 if let Some(region_set) = params.region_set() {
290 if params.signature_version() == SignatureVersion::V4a {
291 signing_params.push((
292 crate::http_request::canonical_request::sigv4a::param::X_AMZ_REGION_SET,
293 Cow::Owned(region_set.to_owned()),
294 ));
295 }
296 }
297
298 if let Some(security_token) = creds.session_token() {
299 signing_params.push((
300 params
301 .settings()
302 .session_token_name_override
303 .unwrap_or(param::X_AMZ_SECURITY_TOKEN),
304 Cow::Owned(security_token.to_string()),
305 ));
306 }
307
308 Ok((signing_params, signature))
309}
310
311fn calculate_signing_headers<'a>(
318 request: &'a SignableRequest<'a>,
319 params: &'a SigningParams<'a>,
320) -> Result<SigningOutput<Vec<Header>>, SigningError> {
321 let creds = params.credentials()?;
322
323 let creq = CanonicalRequest::from(request, params)?;
325 let encoded_creq = v4::sha256_hex_string(creq.to_string().as_bytes());
327 tracing::trace!(canonical_request = %creq);
328 let mut headers = vec![];
329
330 let signature = match params {
331 SigningParams::V4(params) => {
332 let sts = StringToSign::new_v4(
333 params.time,
334 params.region,
335 params.name,
336 encoded_creq.as_str(),
337 );
338
339 let signing_key = v4::generate_signing_key(
341 creds.secret_access_key(),
342 params.time,
343 params.region,
344 params.name,
345 );
346 let signature = v4::calculate_signature(signing_key, sts.to_string().as_bytes());
347
348 let values = creq.values.as_headers().expect("signing with headers");
350 add_header(&mut headers, header::X_AMZ_DATE, &values.date_time, false);
351 headers.push(Header {
352 key: "authorization",
353 value: build_authorization_header(
354 creds.access_key_id(),
355 &creq,
356 sts,
357 &signature,
358 SignatureVersion::V4,
359 ),
360 sensitive: false,
361 });
362 if params.settings.payload_checksum_kind == PayloadChecksumKind::XAmzSha256 {
363 add_header(
364 &mut headers,
365 header::X_AMZ_CONTENT_SHA_256,
366 &values.content_sha256,
367 false,
368 );
369 }
370
371 if let Some(security_token) = creds.session_token() {
372 add_header(
373 &mut headers,
374 params
375 .settings
376 .session_token_name_override
377 .unwrap_or(header::X_AMZ_SECURITY_TOKEN),
378 security_token,
379 true,
380 );
381 }
382 signature
383 }
384 #[cfg(feature = "sigv4a")]
385 SigningParams::V4a(params) => {
386 let sts = StringToSign::new_v4a(
387 params.time,
388 params.region_set,
389 params.name,
390 encoded_creq.as_str(),
391 );
392
393 let signing_key =
394 v4a::generate_signing_key(creds.access_key_id(), creds.secret_access_key());
395 let signature = v4a::calculate_signature(&signing_key, sts.to_string().as_bytes());
396
397 let values = creq.values.as_headers().expect("signing with headers");
398 add_header(&mut headers, header::X_AMZ_DATE, &values.date_time, false);
399 add_header(
400 &mut headers,
401 crate::http_request::canonical_request::sigv4a::header::X_AMZ_REGION_SET,
402 params.region_set,
403 false,
404 );
405
406 headers.push(Header {
407 key: "authorization",
408 value: build_authorization_header(
409 creds.access_key_id(),
410 &creq,
411 sts,
412 &signature,
413 SignatureVersion::V4a,
414 ),
415 sensitive: false,
416 });
417 if params.settings.payload_checksum_kind == PayloadChecksumKind::XAmzSha256 {
418 add_header(
419 &mut headers,
420 header::X_AMZ_CONTENT_SHA_256,
421 &values.content_sha256,
422 false,
423 );
424 }
425
426 if let Some(security_token) = creds.session_token() {
427 add_header(
428 &mut headers,
429 header::X_AMZ_SECURITY_TOKEN,
430 security_token,
431 true,
432 );
433 }
434 signature
435 }
436 };
437
438 Ok(SigningOutput::new(headers, signature))
439}
440
441fn add_header(map: &mut Vec<Header>, key: &'static str, value: &str, sensitive: bool) {
442 map.push(Header {
443 key,
444 value: value.to_string(),
445 sensitive,
446 });
447}
448
449fn build_authorization_header(
452 access_key: &str,
453 creq: &CanonicalRequest<'_>,
454 sts: StringToSign<'_>,
455 signature: &str,
456 signature_version: SignatureVersion,
457) -> String {
458 let scope = match signature_version {
459 SignatureVersion::V4 => sts.scope.to_string(),
460 SignatureVersion::V4a => sts.scope.v4a_display(),
461 };
462 format!(
463 "{} Credential={}/{}, SignedHeaders={}, Signature={}",
464 sts.algorithm,
465 access_key,
466 scope,
467 creq.values.signed_headers().as_str(),
468 signature
469 )
470}
471#[cfg(test)]
472mod tests {
473 use crate::date_time::test_parsers::parse_date_time;
474 use crate::http_request::sign::{add_header, SignableRequest};
475 use crate::http_request::{
476 sign, test, SessionTokenMode, SignableBody, SignatureLocation, SigningInstructions,
477 SigningSettings,
478 };
479 use crate::sign::v4;
480 use aws_credential_types::Credentials;
481 use http0::{HeaderValue, Request};
482 use pretty_assertions::assert_eq;
483 use proptest::proptest;
484 use std::borrow::Cow;
485 use std::iter;
486 use std::time::Duration;
487
488 macro_rules! assert_req_eq {
489 (http: $expected:expr, $actual:expr) => {
490 let mut expected = ($expected).map(|_b|"body");
491 let mut actual = ($actual).map(|_b|"body");
492 make_headers_comparable(&mut expected);
493 make_headers_comparable(&mut actual);
494 assert_eq!(format!("{:?}", expected), format!("{:?}", actual));
495 };
496 ($expected:tt, $actual:tt) => {
497 assert_req_eq!(http: ($expected).as_http_request(), $actual);
498 };
499 }
500
501 pub(crate) fn make_headers_comparable<B>(request: &mut Request<B>) {
502 for (_name, value) in request.headers_mut() {
503 value.set_sensitive(false);
504 }
505 }
506
507 #[test]
508 fn test_sign_vanilla_with_headers() {
509 let settings = SigningSettings::default();
510 let identity = &Credentials::for_tests().into();
511 let params = v4::SigningParams {
512 identity,
513 region: "us-east-1",
514 name: "service",
515 time: parse_date_time("20150830T123600Z").unwrap(),
516 settings,
517 }
518 .into();
519
520 let original = test::v4::test_request("get-vanilla-query-order-key-case");
521 let signable = SignableRequest::from(&original);
522 let out = sign(signable, ¶ms).unwrap();
523 assert_eq!(
524 "5557820e7380d585310524bd93d51a08d7757fb5efd7344ee12088f2b0860947",
525 out.signature
526 );
527
528 let mut signed = original.as_http_request();
529 out.output.apply_to_request_http0x(&mut signed);
530
531 let expected = test::v4::test_signed_request("get-vanilla-query-order-key-case");
532 assert_req_eq!(expected, signed);
533 }
534
535 #[cfg(feature = "sigv4a")]
536 mod sigv4a_tests {
537 use super::*;
538 use crate::http_request::canonical_request::{CanonicalRequest, StringToSign};
539 use crate::http_request::{sign, test, SigningParams};
540 use crate::sign::v4a;
541 use p256::ecdsa::signature::{Signature, Verifier};
542 use p256::ecdsa::{DerSignature, SigningKey};
543 use pretty_assertions::assert_eq;
544
545 fn new_v4a_signing_params_from_context(
546 test_context: &'_ test::v4a::TestContext,
547 signature_location: SignatureLocation,
548 ) -> SigningParams<'_> {
549 let mut params = v4a::SigningParams::from(test_context);
550 params.settings.signature_location = signature_location;
551
552 params.into()
553 }
554
555 fn run_v4a_test_suite(test_name: &str, signature_location: SignatureLocation) {
556 let tc = test::v4a::test_context(test_name);
557 let params = new_v4a_signing_params_from_context(&tc, signature_location);
558
559 let req = test::v4a::test_request(test_name);
560 let expected_creq = test::v4a::test_canonical_request(test_name, signature_location);
561 let signable_req = SignableRequest::from(&req);
562 let actual_creq = CanonicalRequest::from(&signable_req, ¶ms).unwrap();
563
564 assert_eq!(expected_creq, actual_creq.to_string(), "creq didn't match");
565
566 let expected_string_to_sign =
567 test::v4a::test_string_to_sign(test_name, signature_location);
568 let hashed_creq = &v4::sha256_hex_string(actual_creq.to_string().as_bytes());
569 let actual_string_to_sign = StringToSign::new_v4a(
570 *params.time(),
571 params.region_set().unwrap(),
572 params.name(),
573 hashed_creq,
574 )
575 .to_string();
576
577 assert_eq!(
578 expected_string_to_sign, actual_string_to_sign,
579 "'string to sign' didn't match"
580 );
581
582 let out = sign(signable_req, ¶ms).unwrap();
583 out.output
585 .apply_to_request_http0x(&mut req.as_http_request());
586
587 let creds = params.credentials().unwrap();
588 let signing_key =
589 v4a::generate_signing_key(creds.access_key_id(), creds.secret_access_key());
590 let sig = DerSignature::from_bytes(&hex::decode(out.signature).unwrap()).unwrap();
591 let sig = sig
592 .try_into()
593 .expect("DER-style signatures are always convertible into fixed-size signatures");
594
595 let signing_key = SigningKey::from_bytes(signing_key.as_ref()).unwrap();
596 let peer_public_key = signing_key.verifying_key();
597 let sts = actual_string_to_sign.as_bytes();
598 peer_public_key.verify(sts, &sig).unwrap();
599 }
600
601 #[test]
602 fn test_get_header_key_duplicate() {
603 run_v4a_test_suite("get-header-key-duplicate", SignatureLocation::Headers);
604 }
605
606 #[test]
607 fn test_get_header_value_order() {
608 run_v4a_test_suite("get-header-value-order", SignatureLocation::Headers);
609 }
610
611 #[test]
612 fn test_get_header_value_trim() {
613 run_v4a_test_suite("get-header-value-trim", SignatureLocation::Headers);
614 }
615
616 #[test]
617 fn test_get_relative_normalized() {
618 run_v4a_test_suite("get-relative-normalized", SignatureLocation::Headers);
619 }
620
621 #[test]
622 fn test_get_relative_relative_normalized() {
623 run_v4a_test_suite(
624 "get-relative-relative-normalized",
625 SignatureLocation::Headers,
626 );
627 }
628
629 #[test]
630 fn test_get_relative_relative_unnormalized() {
631 run_v4a_test_suite(
632 "get-relative-relative-unnormalized",
633 SignatureLocation::Headers,
634 );
635 }
636
637 #[test]
638 fn test_get_relative_unnormalized() {
639 run_v4a_test_suite("get-relative-unnormalized", SignatureLocation::Headers);
640 }
641
642 #[test]
643 fn test_get_slash_dot_slash_normalized() {
644 run_v4a_test_suite("get-slash-dot-slash-normalized", SignatureLocation::Headers);
645 }
646
647 #[test]
648 fn test_get_slash_dot_slash_unnormalized() {
649 run_v4a_test_suite(
650 "get-slash-dot-slash-unnormalized",
651 SignatureLocation::Headers,
652 );
653 }
654
655 #[test]
656 fn test_get_slash_normalized() {
657 run_v4a_test_suite("get-slash-normalized", SignatureLocation::Headers);
658 }
659
660 #[test]
661 fn test_get_slash_pointless_dot_normalized() {
662 run_v4a_test_suite(
663 "get-slash-pointless-dot-normalized",
664 SignatureLocation::Headers,
665 );
666 }
667
668 #[test]
669 fn test_get_slash_pointless_dot_unnormalized() {
670 run_v4a_test_suite(
671 "get-slash-pointless-dot-unnormalized",
672 SignatureLocation::Headers,
673 );
674 }
675
676 #[test]
677 fn test_get_slash_unnormalized() {
678 run_v4a_test_suite("get-slash-unnormalized", SignatureLocation::Headers);
679 }
680
681 #[test]
682 fn test_get_slashes_normalized() {
683 run_v4a_test_suite("get-slashes-normalized", SignatureLocation::Headers);
684 }
685
686 #[test]
687 fn test_get_slashes_unnormalized() {
688 run_v4a_test_suite("get-slashes-unnormalized", SignatureLocation::Headers);
689 }
690
691 #[test]
692 fn test_get_unreserved() {
693 run_v4a_test_suite("get-unreserved", SignatureLocation::Headers);
694 }
695
696 #[test]
697 fn test_get_vanilla() {
698 run_v4a_test_suite("get-vanilla", SignatureLocation::Headers);
699 }
700
701 #[test]
702 fn test_get_vanilla_empty_query_key() {
703 run_v4a_test_suite(
704 "get-vanilla-empty-query-key",
705 SignatureLocation::QueryParams,
706 );
707 }
708
709 #[test]
710 fn test_get_vanilla_query() {
711 run_v4a_test_suite("get-vanilla-query", SignatureLocation::QueryParams);
712 }
713
714 #[test]
715 fn test_get_vanilla_query_order_key_case() {
716 run_v4a_test_suite(
717 "get-vanilla-query-order-key-case",
718 SignatureLocation::QueryParams,
719 );
720 }
721
722 #[test]
723 fn test_get_vanilla_query_unreserved() {
724 run_v4a_test_suite(
725 "get-vanilla-query-unreserved",
726 SignatureLocation::QueryParams,
727 );
728 }
729
730 #[test]
731 fn test_get_vanilla_with_session_token() {
732 run_v4a_test_suite("get-vanilla-with-session-token", SignatureLocation::Headers);
733 }
734
735 #[test]
736 fn test_post_header_key_case() {
737 run_v4a_test_suite("post-header-key-case", SignatureLocation::Headers);
738 }
739
740 #[test]
741 fn test_post_header_key_sort() {
742 run_v4a_test_suite("post-header-key-sort", SignatureLocation::Headers);
743 }
744
745 #[test]
746 fn test_post_header_value_case() {
747 run_v4a_test_suite("post-header-value-case", SignatureLocation::Headers);
748 }
749
750 #[test]
751 fn test_post_sts_header_after() {
752 run_v4a_test_suite("post-sts-header-after", SignatureLocation::Headers);
753 }
754
755 #[test]
756 fn test_post_sts_header_before() {
757 run_v4a_test_suite("post-sts-header-before", SignatureLocation::Headers);
758 }
759
760 #[test]
761 fn test_post_vanilla() {
762 run_v4a_test_suite("post-vanilla", SignatureLocation::Headers);
763 }
764
765 #[test]
766 fn test_post_vanilla_empty_query_value() {
767 run_v4a_test_suite(
768 "post-vanilla-empty-query-value",
769 SignatureLocation::QueryParams,
770 );
771 }
772
773 #[test]
774 fn test_post_vanilla_query() {
775 run_v4a_test_suite("post-vanilla-query", SignatureLocation::QueryParams);
776 }
777
778 #[test]
779 fn test_post_x_www_form_urlencoded() {
780 run_v4a_test_suite("post-x-www-form-urlencoded", SignatureLocation::Headers);
781 }
782
783 #[test]
784 fn test_post_x_www_form_urlencoded_parameters() {
785 run_v4a_test_suite(
786 "post-x-www-form-urlencoded-parameters",
787 SignatureLocation::QueryParams,
788 );
789 }
790 }
791
792 #[test]
793 fn test_sign_url_escape() {
794 let test = "double-encode-path";
795 let settings = SigningSettings::default();
796 let identity = &Credentials::for_tests().into();
797 let params = v4::SigningParams {
798 identity,
799 region: "us-east-1",
800 name: "service",
801 time: parse_date_time("20150830T123600Z").unwrap(),
802 settings,
803 }
804 .into();
805
806 let original = test::v4::test_request(test);
807 let signable = SignableRequest::from(&original);
808 let out = sign(signable, ¶ms).unwrap();
809 assert_eq!(
810 "57d157672191bac40bae387e48bbe14b15303c001fdbb01f4abf295dccb09705",
811 out.signature
812 );
813
814 let mut signed = original.as_http_request();
815 out.output.apply_to_request_http0x(&mut signed);
816
817 let expected = test::v4::test_signed_request(test);
818 assert_req_eq!(expected, signed);
819 }
820
821 #[test]
822 fn test_sign_vanilla_with_query_params() {
823 let settings = SigningSettings {
824 signature_location: SignatureLocation::QueryParams,
825 expires_in: Some(Duration::from_secs(35)),
826 ..Default::default()
827 };
828 let identity = &Credentials::for_tests().into();
829 let params = v4::SigningParams {
830 identity,
831 region: "us-east-1",
832 name: "service",
833 time: parse_date_time("20150830T123600Z").unwrap(),
834 settings,
835 }
836 .into();
837
838 let original = test::v4::test_request("get-vanilla-query-order-key-case");
839 let signable = SignableRequest::from(&original);
840 let out = sign(signable, ¶ms).unwrap();
841 assert_eq!(
842 "ecce208e4b4f7d7e3a4cc22ced6acc2ad1d170ee8ba87d7165f6fa4b9aff09ab",
843 out.signature
844 );
845
846 let mut signed = original.as_http_request();
847 out.output.apply_to_request_http0x(&mut signed);
848
849 let expected =
850 test::v4::test_signed_request_query_params("get-vanilla-query-order-key-case");
851 assert_req_eq!(expected, signed);
852 }
853
854 #[test]
855 fn test_sign_headers_utf8() {
856 let settings = SigningSettings::default();
857 let identity = &Credentials::for_tests().into();
858 let params = v4::SigningParams {
859 identity,
860 region: "us-east-1",
861 name: "service",
862 time: parse_date_time("20150830T123600Z").unwrap(),
863 settings,
864 }
865 .into();
866
867 let original = http0::Request::builder()
868 .uri("https://some-endpoint.some-region.amazonaws.com")
869 .header("some-header", HeaderValue::from_str("テスト").unwrap())
870 .body("")
871 .unwrap()
872 .into();
873 let signable = SignableRequest::from(&original);
874 let out = sign(signable, ¶ms).unwrap();
875 assert_eq!(
876 "55e16b31f9bde5fd04f9d3b780dd2b5e5f11a5219001f91a8ca9ec83eaf1618f",
877 out.signature
878 );
879
880 let mut signed = original.as_http_request();
881 out.output.apply_to_request_http0x(&mut signed);
882
883 let expected = http0::Request::builder()
884 .uri("https://some-endpoint.some-region.amazonaws.com")
885 .header("some-header", HeaderValue::from_str("テスト").unwrap())
886 .header(
887 "x-amz-date",
888 HeaderValue::from_str("20150830T123600Z").unwrap(),
889 )
890 .header(
891 "authorization",
892 HeaderValue::from_str(
893 "AWS4-HMAC-SHA256 \
894 Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, \
895 SignedHeaders=host;some-header;x-amz-date, \
896 Signature=55e16b31f9bde5fd04f9d3b780dd2b5e5f11a5219001f91a8ca9ec83eaf1618f",
897 )
898 .unwrap(),
899 )
900 .body("")
901 .unwrap();
902 assert_req_eq!(http: expected, signed);
903 }
904
905 #[test]
906 fn test_sign_headers_excluding_session_token() {
907 let settings = SigningSettings {
908 session_token_mode: SessionTokenMode::Exclude,
909 ..Default::default()
910 };
911 let identity = &Credentials::for_tests_with_session_token().into();
912 let params = v4::SigningParams {
913 identity,
914 region: "us-east-1",
915 name: "service",
916 time: parse_date_time("20150830T123600Z").unwrap(),
917 settings,
918 }
919 .into();
920
921 let original = http0::Request::builder()
922 .uri("https://some-endpoint.some-region.amazonaws.com")
923 .body("")
924 .unwrap()
925 .into();
926 let out_without_session_token = sign(SignableRequest::from(&original), ¶ms).unwrap();
927
928 let out_with_session_token_but_excluded =
929 sign(SignableRequest::from(&original), ¶ms).unwrap();
930 assert_eq!(
931 "ab32de057edf094958d178b3c91f3c8d5c296d526b11da991cd5773d09cea560",
932 out_with_session_token_but_excluded.signature
933 );
934 assert_eq!(
935 out_with_session_token_but_excluded.signature,
936 out_without_session_token.signature
937 );
938
939 let mut signed = original.as_http_request();
940 out_with_session_token_but_excluded
941 .output
942 .apply_to_request_http0x(&mut signed);
943
944 let expected = http0::Request::builder()
945 .uri("https://some-endpoint.some-region.amazonaws.com")
946 .header(
947 "x-amz-date",
948 HeaderValue::from_str("20150830T123600Z").unwrap(),
949 )
950 .header(
951 "authorization",
952 HeaderValue::from_str(
953 "AWS4-HMAC-SHA256 \
954 Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, \
955 SignedHeaders=host;x-amz-date, \
956 Signature=ab32de057edf094958d178b3c91f3c8d5c296d526b11da991cd5773d09cea560",
957 )
958 .unwrap(),
959 )
960 .header(
961 "x-amz-security-token",
962 HeaderValue::from_str("notarealsessiontoken").unwrap(),
963 )
964 .body(b"")
965 .unwrap();
966 assert_req_eq!(http: expected, signed);
967 }
968
969 #[test]
970 fn test_sign_headers_space_trimming() {
971 let settings = SigningSettings::default();
972 let identity = &Credentials::for_tests().into();
973 let params = v4::SigningParams {
974 identity,
975 region: "us-east-1",
976 name: "service",
977 time: parse_date_time("20150830T123600Z").unwrap(),
978 settings,
979 }
980 .into();
981
982 let original = http0::Request::builder()
983 .uri("https://some-endpoint.some-region.amazonaws.com")
984 .header(
985 "some-header",
986 HeaderValue::from_str(" test test ").unwrap(),
987 )
988 .body("")
989 .unwrap()
990 .into();
991 let signable = SignableRequest::from(&original);
992 let out = sign(signable, ¶ms).unwrap();
993 assert_eq!(
994 "244f2a0db34c97a528f22715fe01b2417b7750c8a95c7fc104a3c48d81d84c08",
995 out.signature
996 );
997
998 let mut signed = original.as_http_request();
999 out.output.apply_to_request_http0x(&mut signed);
1000
1001 let expected = http0::Request::builder()
1002 .uri("https://some-endpoint.some-region.amazonaws.com")
1003 .header(
1004 "some-header",
1005 HeaderValue::from_str(" test test ").unwrap(),
1006 )
1007 .header(
1008 "x-amz-date",
1009 HeaderValue::from_str("20150830T123600Z").unwrap(),
1010 )
1011 .header(
1012 "authorization",
1013 HeaderValue::from_str(
1014 "AWS4-HMAC-SHA256 \
1015 Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, \
1016 SignedHeaders=host;some-header;x-amz-date, \
1017 Signature=244f2a0db34c97a528f22715fe01b2417b7750c8a95c7fc104a3c48d81d84c08",
1018 )
1019 .unwrap(),
1020 )
1021 .body("")
1022 .unwrap();
1023 assert_req_eq!(http: expected, signed);
1024 }
1025
1026 proptest! {
1027 #[test]
1028 fn test_sign_headers_no_panic(
1031 header in ".*"
1032 ) {
1033 let settings = SigningSettings::default();
1034 let identity = &Credentials::for_tests().into();
1035 let params = v4::SigningParams {
1036 identity,
1037 region: "us-east-1",
1038 name: "foo",
1039 time: std::time::SystemTime::UNIX_EPOCH,
1040 settings,
1041 }.into();
1042
1043 let req = SignableRequest::new(
1044 "GET",
1045 "https://foo.com",
1046 iter::once(("x-sign-me", header.as_str())),
1047 SignableBody::Bytes(&[])
1048 );
1049
1050 if let Ok(req) = req {
1051 let _creq = crate::http_request::sign(req, ¶ms);
1053 }
1054 }
1055 }
1056
1057 #[test]
1058 fn apply_signing_instructions_headers() {
1059 let mut headers = vec![];
1060 add_header(&mut headers, "some-header", "foo", false);
1061 add_header(&mut headers, "some-other-header", "bar", false);
1062 let instructions = SigningInstructions::new(headers, vec![]);
1063
1064 let mut request = http0::Request::builder()
1065 .uri("https://some-endpoint.some-region.amazonaws.com")
1066 .body("")
1067 .unwrap();
1068
1069 instructions.apply_to_request_http0x(&mut request);
1070
1071 let get_header = |n: &str| request.headers().get(n).unwrap().to_str().unwrap();
1072 assert_eq!("foo", get_header("some-header"));
1073 assert_eq!("bar", get_header("some-other-header"));
1074 }
1075
1076 #[test]
1077 fn apply_signing_instructions_query_params() {
1078 let params = vec![
1079 ("some-param", Cow::Borrowed("f&o?o")),
1080 ("some-other-param?", Cow::Borrowed("bar")),
1081 ];
1082 let instructions = SigningInstructions::new(vec![], params);
1083
1084 let mut request = http0::Request::builder()
1085 .uri("https://some-endpoint.some-region.amazonaws.com/some/path")
1086 .body("")
1087 .unwrap();
1088
1089 instructions.apply_to_request_http0x(&mut request);
1090
1091 assert_eq!(
1092 "/some/path?some-param=f%26o%3Fo&some-other-param%3F=bar",
1093 request.uri().path_and_query().unwrap().to_string()
1094 );
1095 }
1096
1097 #[test]
1098 fn apply_signing_instructions_query_params_http_1x() {
1099 let params = vec![
1100 ("some-param", Cow::Borrowed("f&o?o")),
1101 ("some-other-param?", Cow::Borrowed("bar")),
1102 ];
1103 let instructions = SigningInstructions::new(vec![], params);
1104
1105 let mut request = http::Request::builder()
1106 .uri("https://some-endpoint.some-region.amazonaws.com/some/path")
1107 .body("")
1108 .unwrap();
1109
1110 instructions.apply_to_request_http1x(&mut request);
1111
1112 assert_eq!(
1113 "/some/path?some-param=f%26o%3Fo&some-other-param%3F=bar",
1114 request.uri().path_and_query().unwrap().to_string()
1115 );
1116 }
1117}