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