Coverage Report

Created: 2025-05-07 06:59

/rust/registry/src/index.crates.io-6f17d22bba15001f/reqsign-0.16.3/src/aws/v4.rs
Line
Count
Source (jump to first uncovered line)
1
//! AWS service sigv4 signer
2
3
use std::fmt::Debug;
4
use std::fmt::Write;
5
use std::time::Duration;
6
7
use anyhow::Result;
8
use http::header;
9
use http::HeaderValue;
10
use log::debug;
11
use percent_encoding::percent_decode_str;
12
use percent_encoding::utf8_percent_encode;
13
14
use super::constants::AWS_QUERY_ENCODE_SET;
15
use super::constants::X_AMZ_CONTENT_SHA_256;
16
use super::constants::X_AMZ_DATE;
17
use super::constants::X_AMZ_SECURITY_TOKEN;
18
use super::credential::Credential;
19
use crate::ctx::SigningContext;
20
use crate::ctx::SigningMethod;
21
use crate::hash::hex_hmac_sha256;
22
use crate::hash::hex_sha256;
23
use crate::hash::hmac_sha256;
24
use crate::request::SignableRequest;
25
use crate::time::format_date;
26
use crate::time::format_iso8601;
27
use crate::time::now;
28
use crate::time::DateTime;
29
30
/// Singer that implement AWS SigV4.
31
///
32
/// - [Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
33
#[derive(Debug)]
34
pub struct Signer {
35
    service: String,
36
    region: String,
37
38
    time: Option<DateTime>,
39
}
40
41
impl Signer {
42
    /// Create a builder.
43
0
    pub fn new(service: &str, region: &str) -> Self {
44
0
        Self {
45
0
            service: service.to_string(),
46
0
            region: region.to_string(),
47
0
            time: None,
48
0
        }
49
0
    }
50
51
    /// Specify the signing time.
52
    ///
53
    /// # Note
54
    ///
55
    /// We should always take current time to sign requests.
56
    /// Only use this function for testing.
57
    #[cfg(test)]
58
    pub fn time(mut self, time: DateTime) -> Self {
59
        self.time = Some(time);
60
        self
61
    }
62
63
0
    fn build(
64
0
        &self,
65
0
        req: &mut impl SignableRequest,
66
0
        method: SigningMethod,
67
0
        cred: &Credential,
68
0
    ) -> Result<SigningContext> {
69
0
        let now = self.time.unwrap_or_else(now);
70
0
        let mut ctx = req.build()?;
71
72
        // canonicalize context
73
0
        canonicalize_header(&mut ctx, method, cred, now)?;
74
0
        canonicalize_query(&mut ctx, method, cred, now, &self.service, &self.region)?;
75
76
        // build canonical request and string to sign.
77
0
        let creq = canonical_request_string(&mut ctx)?;
78
0
        let encoded_req = hex_sha256(creq.as_bytes());
79
0
80
0
        // Scope: "20220313/<region>/<service>/aws4_request"
81
0
        let scope = format!(
82
0
            "{}/{}/{}/aws4_request",
83
0
            format_date(now),
84
0
            self.region,
85
0
            self.service
86
0
        );
87
0
        debug!("calculated scope: {scope}");
88
89
        // StringToSign:
90
        //
91
        // AWS4-HMAC-SHA256
92
        // 20220313T072004Z
93
        // 20220313/<region>/<service>/aws4_request
94
        // <hashed_canonical_request>
95
0
        let string_to_sign = {
96
0
            let mut f = String::new();
97
0
            writeln!(f, "AWS4-HMAC-SHA256")?;
98
0
            writeln!(f, "{}", format_iso8601(now))?;
99
0
            writeln!(f, "{}", &scope)?;
100
0
            write!(f, "{}", &encoded_req)?;
101
0
            f
102
0
        };
103
0
        debug!("calculated string to sign: {string_to_sign}");
104
105
0
        let signing_key =
106
0
            generate_signing_key(&cred.secret_access_key, now, &self.region, &self.service);
107
0
        let signature = hex_hmac_sha256(&signing_key, string_to_sign.as_bytes());
108
0
109
0
        match method {
110
            SigningMethod::Header => {
111
0
                let mut authorization = HeaderValue::from_str(&format!(
112
0
                    "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}",
113
0
                    cred.access_key_id,
114
0
                    scope,
115
0
                    ctx.header_name_to_vec_sorted().join(";"),
116
0
                    signature
117
0
                ))?;
118
0
                authorization.set_sensitive(true);
119
0
120
0
                ctx.headers
121
0
                    .insert(http::header::AUTHORIZATION, authorization);
122
            }
123
0
            SigningMethod::Query(_) => {
124
0
                ctx.query.push(("X-Amz-Signature".into(), signature));
125
0
            }
126
        }
127
128
0
        Ok(ctx)
129
0
    }
Unexecuted instantiation: <reqsign::aws::v4::Signer>::build::<http::request::Request<opendal::types::buffer::Buffer>>
Unexecuted instantiation: <reqsign::aws::v4::Signer>::build::<reqwest::async_impl::request::Request>
130
131
    /// Get the region of this signer.
132
0
    pub fn region(&self) -> &str {
133
0
        &self.region
134
0
    }
135
136
    /// Signing request with header.
137
    ///
138
    /// # Example
139
    ///
140
    /// ```rust,no_run
141
    /// use anyhow::Result;
142
    /// use reqsign::AwsConfig;
143
    /// use reqsign::AwsDefaultLoader;
144
    /// use reqsign::AwsV4Signer;
145
    /// use reqwest::Client;
146
    /// use reqwest::Request;
147
    /// use reqwest::Url;
148
    ///
149
    /// #[tokio::main]
150
    /// async fn main() -> Result<()> {
151
    ///     let client = Client::new();
152
    ///     let config = AwsConfig::default().from_profile().from_env();
153
    ///     let loader = AwsDefaultLoader::new(client.clone(), config);
154
    ///     let signer = AwsV4Signer::new("s3", "us-east-1");
155
    ///     // Construct request
156
    ///     let url = Url::parse("https://s3.amazonaws.com/testbucket")?;
157
    ///     let mut req = reqwest::Request::new(http::Method::GET, url);
158
    ///     // Signing request with Signer
159
    ///     let credential = loader.load().await?.unwrap();
160
    ///     signer.sign(&mut req, &credential)?;
161
    ///     // Sending already signed request.
162
    ///     let resp = client.execute(req).await?;
163
    ///     println!("resp got status: {}", resp.status());
164
    ///     Ok(())
165
    /// }
166
    /// ```
167
0
    pub fn sign(&self, req: &mut impl SignableRequest, cred: &Credential) -> Result<()> {
168
0
        let ctx = self.build(req, SigningMethod::Header, cred)?;
169
0
        req.apply(ctx)
170
0
    }
Unexecuted instantiation: <reqsign::aws::v4::Signer>::sign::<http::request::Request<opendal::types::buffer::Buffer>>
Unexecuted instantiation: <reqsign::aws::v4::Signer>::sign::<reqwest::async_impl::request::Request>
171
172
    /// Signing request with query.
173
    ///
174
    /// # Example
175
    ///
176
    /// ```rust,no_run
177
    /// use std::time::Duration;
178
    ///
179
    /// use anyhow::Result;
180
    /// use reqsign::AwsConfig;
181
    /// use reqsign::AwsDefaultLoader;
182
    /// use reqsign::AwsV4Signer;
183
    /// use reqwest::Client;
184
    /// use reqwest::Request;
185
    /// use reqwest::Url;
186
    ///
187
    /// #[tokio::main]
188
    /// async fn main() -> Result<()> {
189
    ///     let client = Client::new();
190
    ///     let config = AwsConfig::default().from_profile().from_env();
191
    ///     let loader = AwsDefaultLoader::new(client.clone(), config);
192
    ///     let signer = AwsV4Signer::new("s3", "us-east-1");
193
    ///     // Construct request
194
    ///     let url = Url::parse("https://s3.amazonaws.com/testbucket")?;
195
    ///     let mut req = reqwest::Request::new(http::Method::GET, url);
196
    ///     // Signing request with Signer
197
    ///     let credential = loader.load().await?.unwrap();
198
    ///     signer.sign_query(&mut req, Duration::from_secs(3600), &credential)?;
199
    ///     // Sending already signed request.
200
    ///     let resp = client.execute(req).await?;
201
    ///     println!("resp got status: {}", resp.status());
202
    ///     Ok(())
203
    /// }
204
    /// ```
205
0
    pub fn sign_query(
206
0
        &self,
207
0
        req: &mut impl SignableRequest,
208
0
        expire: Duration,
209
0
        cred: &Credential,
210
0
    ) -> Result<()> {
211
0
        let ctx = self.build(req, SigningMethod::Query(expire), cred)?;
212
0
        req.apply(ctx)
213
0
    }
Unexecuted instantiation: <reqsign::aws::v4::Signer>::sign_query::<http::request::Request<opendal::types::buffer::Buffer>>
Unexecuted instantiation: <reqsign::aws::v4::Signer>::sign_query::<_>
214
}
215
216
0
fn canonical_request_string(ctx: &mut SigningContext) -> Result<String> {
217
0
    // 256 is specially chosen to avoid reallocation for most requests.
218
0
    let mut f = String::with_capacity(256);
219
0
220
0
    // Insert method
221
0
    writeln!(f, "{}", ctx.method)?;
222
    // Insert encoded path
223
0
    let path = percent_decode_str(&ctx.path).decode_utf8()?;
224
0
    writeln!(
225
0
        f,
226
0
        "{}",
227
0
        utf8_percent_encode(&path, &super::constants::AWS_URI_ENCODE_SET)
228
0
    )?;
229
    // Insert query
230
0
    writeln!(
231
0
        f,
232
0
        "{}",
233
0
        ctx.query
234
0
            .iter()
235
0
            .map(|(k, v)| { format!("{k}={v}") })
236
0
            .collect::<Vec<_>>()
237
0
            .join("&")
238
0
    )?;
239
    // Insert signed headers
240
0
    let signed_headers = ctx.header_name_to_vec_sorted();
241
0
    for header in signed_headers.iter() {
242
0
        let value = &ctx.headers[*header];
243
0
        writeln!(
244
0
            f,
245
0
            "{}:{}",
246
0
            header,
247
0
            value.to_str().expect("header value must be valid")
248
0
        )?;
249
    }
250
0
    writeln!(f)?;
251
0
    writeln!(f, "{}", signed_headers.join(";"))?;
252
253
0
    if ctx.headers.get(X_AMZ_CONTENT_SHA_256).is_none() {
254
0
        write!(f, "UNSIGNED-PAYLOAD")?;
255
    } else {
256
0
        write!(f, "{}", ctx.headers[X_AMZ_CONTENT_SHA_256].to_str()?)?;
257
    }
258
259
0
    Ok(f)
260
0
}
261
262
0
fn canonicalize_header(
263
0
    ctx: &mut SigningContext,
264
0
    method: SigningMethod,
265
0
    cred: &Credential,
266
0
    now: DateTime,
267
0
) -> Result<()> {
268
    // Header names and values need to be normalized according to Step 4 of https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
269
0
    for (_, value) in ctx.headers.iter_mut() {
270
0
        SigningContext::header_value_normalize(value)
271
    }
272
273
    // Insert HOST header if not present.
274
0
    if ctx.headers.get(header::HOST).is_none() {
275
0
        ctx.headers
276
0
            .insert(header::HOST, ctx.authority.as_str().parse()?);
277
0
    }
278
279
0
    if method == SigningMethod::Header {
280
        // Insert DATE header if not present.
281
0
        if ctx.headers.get(X_AMZ_DATE).is_none() {
282
0
            let date_header = HeaderValue::try_from(format_iso8601(now))?;
283
0
            ctx.headers.insert(X_AMZ_DATE, date_header);
284
0
        }
285
286
        // Insert X_AMZ_CONTENT_SHA_256 header if not present.
287
0
        if ctx.headers.get(X_AMZ_CONTENT_SHA_256).is_none() {
288
0
            ctx.headers.insert(
289
0
                X_AMZ_CONTENT_SHA_256,
290
0
                HeaderValue::from_static("UNSIGNED-PAYLOAD"),
291
0
            );
292
0
        }
293
294
        // Insert X_AMZ_SECURITY_TOKEN header if security token exists.
295
0
        if let Some(token) = &cred.session_token {
296
0
            let mut value = HeaderValue::from_str(token)?;
297
            // Set token value sensitive to valid leaking.
298
0
            value.set_sensitive(true);
299
0
300
0
            ctx.headers.insert(X_AMZ_SECURITY_TOKEN, value);
301
0
        }
302
0
    }
303
304
0
    Ok(())
305
0
}
306
307
0
fn canonicalize_query(
308
0
    ctx: &mut SigningContext,
309
0
    method: SigningMethod,
310
0
    cred: &Credential,
311
0
    now: DateTime,
312
0
    service: &str,
313
0
    region: &str,
314
0
) -> Result<()> {
315
0
    if let SigningMethod::Query(expire) = method {
316
0
        ctx.query
317
0
            .push(("X-Amz-Algorithm".into(), "AWS4-HMAC-SHA256".into()));
318
0
        ctx.query.push((
319
0
            "X-Amz-Credential".into(),
320
0
            format!(
321
0
                "{}/{}/{}/{}/aws4_request",
322
0
                cred.access_key_id,
323
0
                format_date(now),
324
0
                region,
325
0
                service
326
0
            ),
327
0
        ));
328
0
        ctx.query.push(("X-Amz-Date".into(), format_iso8601(now)));
329
0
        ctx.query
330
0
            .push(("X-Amz-Expires".into(), expire.as_secs().to_string()));
331
0
        ctx.query.push((
332
0
            "X-Amz-SignedHeaders".into(),
333
0
            ctx.header_name_to_vec_sorted().join(";"),
334
0
        ));
335
336
0
        if let Some(token) = &cred.session_token {
337
0
            ctx.query
338
0
                .push(("X-Amz-Security-Token".into(), token.into()));
339
0
        }
340
0
    }
341
342
    // Return if query is empty.
343
0
    if ctx.query.is_empty() {
344
0
        return Ok(());
345
0
    }
346
0
347
0
    // Sort by param name
348
0
    ctx.query.sort();
349
0
350
0
    ctx.query = ctx
351
0
        .query
352
0
        .iter()
353
0
        .map(|(k, v)| {
354
0
            (
355
0
                utf8_percent_encode(k, &AWS_QUERY_ENCODE_SET).to_string(),
356
0
                utf8_percent_encode(v, &AWS_QUERY_ENCODE_SET).to_string(),
357
0
            )
358
0
        })
359
0
        .collect();
360
0
361
0
    Ok(())
362
0
}
363
364
0
fn generate_signing_key(secret: &str, time: DateTime, region: &str, service: &str) -> Vec<u8> {
365
0
    // Sign secret
366
0
    let secret = format!("AWS4{secret}");
367
0
    // Sign date
368
0
    let sign_date = hmac_sha256(secret.as_bytes(), format_date(time).as_bytes());
369
0
    // Sign region
370
0
    let sign_region = hmac_sha256(sign_date.as_slice(), region.as_bytes());
371
0
    // Sign service
372
0
    let sign_service = hmac_sha256(sign_region.as_slice(), service.as_bytes());
373
0
    // Sign request
374
0
    let sign_request = hmac_sha256(sign_service.as_slice(), "aws4_request".as_bytes());
375
0
376
0
    sign_request
377
0
}
378
379
#[cfg(test)]
380
mod tests {
381
    use std::time::SystemTime;
382
383
    use anyhow::Result;
384
    use aws_credential_types::Credentials;
385
    use aws_sigv4::http_request::PayloadChecksumKind;
386
    use aws_sigv4::http_request::PercentEncodingMode;
387
    use aws_sigv4::http_request::SignableBody;
388
    use aws_sigv4::http_request::SignableRequest;
389
    use aws_sigv4::http_request::SignatureLocation;
390
    use aws_sigv4::http_request::SigningSettings;
391
    use aws_sigv4::sign::v4;
392
    use http::header;
393
    use macro_rules_attribute::apply;
394
    use reqwest::Client;
395
396
    use super::super::AwsDefaultLoader;
397
    use super::*;
398
    use crate::aws::AwsConfig;
399
400
    fn test_get_request() -> http::Request<&'static str> {
401
        let mut req = http::Request::new("");
402
        *req.method_mut() = http::Method::GET;
403
        *req.uri_mut() = "http://127.0.0.1:9000/hello"
404
            .parse()
405
            .expect("url must be valid");
406
407
        req
408
    }
409
410
    fn test_get_request_with_sse() -> http::Request<&'static str> {
411
        let mut req = http::Request::new("");
412
        *req.method_mut() = http::Method::GET;
413
        *req.uri_mut() = "http://127.0.0.1:9000/hello"
414
            .parse()
415
            .expect("url must be valid");
416
        req.headers_mut().insert(
417
            "x-amz-server-side-encryption",
418
            "a".parse().expect("must be valid"),
419
        );
420
        req.headers_mut().insert(
421
            "x-amz-server-side-encryption-customer-algorithm",
422
            "b".parse().expect("must be valid"),
423
        );
424
        req.headers_mut().insert(
425
            "x-amz-server-side-encryption-customer-key",
426
            "c".parse().expect("must be valid"),
427
        );
428
        req.headers_mut().insert(
429
            "x-amz-server-side-encryption-customer-key-md5",
430
            "d".parse().expect("must be valid"),
431
        );
432
        req.headers_mut().insert(
433
            "x-amz-server-side-encryption-aws-kms-key-id",
434
            "e".parse().expect("must be valid"),
435
        );
436
437
        req
438
    }
439
440
    fn test_get_request_with_query() -> http::Request<&'static str> {
441
        let mut req = http::Request::new("");
442
        *req.method_mut() = http::Method::GET;
443
        *req.uri_mut() = "http://127.0.0.1:9000/hello?list-type=2&max-keys=3&prefix=CI/&start-after=ExampleGuide.pdf"
444
            .parse()
445
            .expect("url must be valid");
446
447
        req
448
    }
449
450
    fn test_get_request_virtual_host() -> http::Request<&'static str> {
451
        let mut req = http::Request::new("");
452
        *req.method_mut() = http::Method::GET;
453
        *req.uri_mut() = "http://hello.s3.test.example.com"
454
            .parse()
455
            .expect("url must be valid");
456
457
        req
458
    }
459
460
    fn test_get_request_with_query_virtual_host() -> http::Request<&'static str> {
461
        let mut req = http::Request::new("");
462
        *req.method_mut() = http::Method::GET;
463
        *req.uri_mut() = "http://hello.s3.test.example.com?list-type=2&max-keys=3&prefix=CI/&start-after=ExampleGuide.pdf"
464
            .parse()
465
            .expect("url must be valid");
466
467
        req
468
    }
469
470
    fn test_put_request() -> http::Request<&'static str> {
471
        let content = "Hello,World!";
472
        let mut req = http::Request::new(content);
473
        *req.method_mut() = http::Method::PUT;
474
        *req.uri_mut() = "http://127.0.0.1:9000/hello"
475
            .parse()
476
            .expect("url must be valid");
477
478
        req.headers_mut().insert(
479
            http::header::CONTENT_LENGTH,
480
            HeaderValue::from_str(&content.len().to_string()).expect("must be valid"),
481
        );
482
483
        req
484
    }
485
486
    fn test_put_request_with_body_digest() -> http::Request<&'static str> {
487
        let content = "Hello,World!";
488
        let mut req = http::Request::new(content);
489
        *req.method_mut() = http::Method::PUT;
490
        *req.uri_mut() = "http://127.0.0.1:9000/hello"
491
            .parse()
492
            .expect("url must be valid");
493
494
        req.headers_mut().insert(
495
            header::CONTENT_LENGTH,
496
            HeaderValue::from_str(&content.len().to_string()).expect("must be valid"),
497
        );
498
499
        let body = hex_sha256(content.as_bytes());
500
        req.headers_mut().insert(
501
            "x-amz-content-sha256",
502
            HeaderValue::from_str(&body).expect("must be valid"),
503
        );
504
505
        req
506
    }
507
508
    fn test_put_request_virtual_host() -> http::Request<&'static str> {
509
        let content = "Hello,World!";
510
        let mut req = http::Request::new(content);
511
        *req.method_mut() = http::Method::PUT;
512
        *req.uri_mut() = "http://hello.s3.test.example.com"
513
            .parse()
514
            .expect("url must be valid");
515
516
        req.headers_mut().insert(
517
            header::CONTENT_LENGTH,
518
            HeaderValue::from_str(&content.len().to_string()).expect("must be valid"),
519
        );
520
521
        req
522
    }
523
524
    macro_rules! test_cases {
525
        ($($tt:tt)*) => {
526
            #[test_case::test_case(test_get_request)]
527
            #[test_case::test_case(test_get_request_with_sse)]
528
            #[test_case::test_case(test_get_request_with_query)]
529
            #[test_case::test_case(test_get_request_virtual_host)]
530
            #[test_case::test_case(test_get_request_with_query_virtual_host)]
531
            #[test_case::test_case(test_put_request)]
532
            #[test_case::test_case(test_put_request_virtual_host)]
533
            #[test_case::test_case(test_put_request_with_body_digest)]
534
            $($tt)*
535
        };
536
    }
537
538
    fn compare_request(name: &str, l: &http::Request<&str>, r: &http::Request<&str>) {
539
        fn format_headers(req: &http::Request<&str>) -> Vec<String> {
540
            let mut hs = req
541
                .headers()
542
                .iter()
543
                .map(|(k, v)| format!("{}:{}", k, v.to_str().expect("must be valid")))
544
                .collect::<Vec<_>>();
545
546
            // Insert host if original request doesn't have it.
547
            if !hs.contains(&format!("host:{}", req.uri().authority().unwrap())) {
548
                hs.push(format!("host:{}", req.uri().authority().unwrap()))
549
            }
550
551
            hs.sort();
552
            hs
553
        }
554
555
        assert_eq!(
556
            format_headers(l),
557
            format_headers(r),
558
            "{name} header mismatch"
559
        );
560
561
        fn format_query(req: &http::Request<&str>) -> Vec<String> {
562
            let query = req.uri().query().unwrap_or_default();
563
            let mut query = form_urlencoded::parse(query.as_bytes())
564
                .map(|(k, v)| format!("{}={}", &k, &v))
565
                .collect::<Vec<_>>();
566
            query.sort();
567
            query
568
        }
569
570
        assert_eq!(format_query(l), format_query(r), "{name} query mismatch");
571
    }
572
573
    #[apply(test_cases)]
574
    #[tokio::test]
575
    async fn test_calculate(req_fn: fn() -> http::Request<&'static str>) -> Result<()> {
576
        let _ = env_logger::builder().is_test(true).try_init();
577
578
        let mut req = req_fn();
579
        let name = format!(
580
            "{} {} {:?}",
581
            req.method(),
582
            req.uri().path(),
583
            req.uri().query(),
584
        );
585
        let now = now();
586
587
        let mut ss = SigningSettings::default();
588
        ss.percent_encoding_mode = PercentEncodingMode::Double;
589
        ss.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
590
        let id = Credentials::new(
591
            "access_key_id",
592
            "secret_access_key",
593
            None,
594
            None,
595
            "hardcoded-credentials",
596
        )
597
        .into();
598
        let sp = v4::SigningParams::builder()
599
            .identity(&id)
600
            .region("test")
601
            .name("s3")
602
            .time(SystemTime::from(now))
603
            .settings(ss)
604
            .build()
605
            .expect("signing params must be valid");
606
607
        let mut body = SignableBody::UnsignedPayload;
608
        if req.headers().get(X_AMZ_CONTENT_SHA_256).is_some() {
609
            body = SignableBody::Bytes(req.body().as_bytes());
610
        }
611
612
        let output = aws_sigv4::http_request::sign(
613
            SignableRequest::new(
614
                req.method().as_str(),
615
                req.uri().to_string(),
616
                req.headers()
617
                    .iter()
618
                    .map(|(k, v)| (k.as_str(), std::str::from_utf8(v.as_bytes()).unwrap())),
619
                body,
620
            )
621
            .unwrap(),
622
            &sp.into(),
623
        )
624
        .expect("signing must succeed");
625
        let (aws_sig, _) = output.into_parts();
626
        aws_sig.apply_to_request_http1x(&mut req);
627
        let expected_req = req;
628
629
        let mut req = req_fn();
630
631
        let loader = AwsDefaultLoader::new(
632
            Client::new(),
633
            AwsConfig {
634
                access_key_id: Some("access_key_id".to_string()),
635
                secret_access_key: Some("secret_access_key".to_string()),
636
                ..Default::default()
637
            },
638
        );
639
        let cred = loader.load().await?.unwrap();
640
641
        let signer = Signer::new("s3", "test").time(now);
642
        signer.sign(&mut req, &cred).expect("must apply success");
643
644
        let actual_req = req;
645
646
        compare_request(&name, &expected_req, &actual_req);
647
648
        Ok(())
649
    }
650
651
    #[apply(test_cases)]
652
    #[tokio::test]
653
    async fn test_calculate_in_query(req_fn: fn() -> http::Request<&'static str>) -> Result<()> {
654
        let _ = env_logger::builder().is_test(true).try_init();
655
656
        let mut req = req_fn();
657
        let name = format!(
658
            "{} {} {:?}",
659
            req.method(),
660
            req.uri().path(),
661
            req.uri().query(),
662
        );
663
        let now = now();
664
665
        let mut ss = SigningSettings::default();
666
        ss.percent_encoding_mode = PercentEncodingMode::Double;
667
        ss.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
668
        ss.signature_location = SignatureLocation::QueryParams;
669
        ss.expires_in = Some(std::time::Duration::from_secs(3600));
670
        let id = Credentials::new(
671
            "access_key_id",
672
            "secret_access_key",
673
            None,
674
            None,
675
            "hardcoded-credentials",
676
        )
677
        .into();
678
        let sp = v4::SigningParams::builder()
679
            .identity(&id)
680
            .region("test")
681
            .name("s3")
682
            .time(SystemTime::from(now))
683
            .settings(ss)
684
            .build()
685
            .expect("signing params must be valid");
686
687
        let mut body = SignableBody::UnsignedPayload;
688
        if req.headers().get(X_AMZ_CONTENT_SHA_256).is_some() {
689
            body = SignableBody::Bytes(req.body().as_bytes());
690
        }
691
692
        let output = aws_sigv4::http_request::sign(
693
            SignableRequest::new(
694
                req.method().as_str(),
695
                req.uri().to_string(),
696
                req.headers()
697
                    .iter()
698
                    .map(|(k, v)| (k.as_str(), std::str::from_utf8(v.as_bytes()).unwrap())),
699
                body,
700
            )
701
            .unwrap(),
702
            &sp.into(),
703
        )
704
        .expect("signing must succeed");
705
        let (aws_sig, _) = output.into_parts();
706
        aws_sig.apply_to_request_http1x(&mut req);
707
        let expected_req = req;
708
709
        let mut req = req_fn();
710
711
        let loader = AwsDefaultLoader::new(
712
            Client::new(),
713
            AwsConfig {
714
                access_key_id: Some("access_key_id".to_string()),
715
                secret_access_key: Some("secret_access_key".to_string()),
716
                ..Default::default()
717
            },
718
        );
719
        let cred = loader.load().await?.unwrap();
720
721
        let signer = Signer::new("s3", "test").time(now);
722
723
        signer.sign_query(&mut req, Duration::from_secs(3600), &cred)?;
724
        let actual_req = req;
725
726
        compare_request(&name, &expected_req, &actual_req);
727
728
        Ok(())
729
    }
730
731
    #[apply(test_cases)]
732
    #[tokio::test]
733
    async fn test_calculate_with_token(req_fn: fn() -> http::Request<&'static str>) -> Result<()> {
734
        let _ = env_logger::builder().is_test(true).try_init();
735
736
        let mut req = req_fn();
737
        let name = format!(
738
            "{} {} {:?}",
739
            req.method(),
740
            req.uri().path(),
741
            req.uri().query(),
742
        );
743
        let now = now();
744
745
        let mut ss = SigningSettings::default();
746
        ss.percent_encoding_mode = PercentEncodingMode::Double;
747
        ss.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
748
        let id = Credentials::new(
749
            "access_key_id",
750
            "secret_access_key",
751
            Some("security_token".to_string()),
752
            None,
753
            "hardcoded-credentials",
754
        )
755
        .into();
756
        let sp = v4::SigningParams::builder()
757
            .identity(&id)
758
            .region("test")
759
            .name("s3")
760
            .time(SystemTime::from(now))
761
            .settings(ss)
762
            .build()
763
            .expect("signing params must be valid");
764
765
        let mut body = SignableBody::UnsignedPayload;
766
        if req.headers().get(X_AMZ_CONTENT_SHA_256).is_some() {
767
            body = SignableBody::Bytes(req.body().as_bytes());
768
        }
769
770
        let output = aws_sigv4::http_request::sign(
771
            SignableRequest::new(
772
                req.method().as_str(),
773
                req.uri().to_string(),
774
                req.headers()
775
                    .iter()
776
                    .map(|(k, v)| (k.as_str(), std::str::from_utf8(v.as_bytes()).unwrap())),
777
                body,
778
            )
779
            .unwrap(),
780
            &sp.into(),
781
        )
782
        .expect("signing must succeed");
783
        let (aws_sig, _) = output.into_parts();
784
        aws_sig.apply_to_request_http1x(&mut req);
785
        let expected_req = req;
786
787
        let mut req = req_fn();
788
789
        let loader = AwsDefaultLoader::new(
790
            Client::new(),
791
            AwsConfig {
792
                access_key_id: Some("access_key_id".to_string()),
793
                secret_access_key: Some("secret_access_key".to_string()),
794
                session_token: Some("security_token".to_string()),
795
                ..Default::default()
796
            },
797
        );
798
        let cred = loader.load().await?.unwrap();
799
800
        let signer = Signer::new("s3", "test").time(now);
801
802
        signer.sign(&mut req, &cred).expect("must apply success");
803
        let actual_req = req;
804
805
        compare_request(&name, &expected_req, &actual_req);
806
807
        Ok(())
808
    }
809
810
    #[apply(test_cases)]
811
    #[tokio::test]
812
    async fn test_calculate_with_token_in_query(
813
        req_fn: fn() -> http::Request<&'static str>,
814
    ) -> Result<()> {
815
        let _ = env_logger::builder().is_test(true).try_init();
816
817
        let mut req = req_fn();
818
        let name = format!(
819
            "{} {} {:?}",
820
            req.method(),
821
            req.uri().path(),
822
            req.uri().query(),
823
        );
824
        let now = now();
825
826
        let mut ss = SigningSettings::default();
827
        ss.percent_encoding_mode = PercentEncodingMode::Double;
828
        ss.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
829
        ss.signature_location = SignatureLocation::QueryParams;
830
        ss.expires_in = Some(std::time::Duration::from_secs(3600));
831
        let id = Credentials::new(
832
            "access_key_id",
833
            "secret_access_key",
834
            Some("security_token".to_string()),
835
            None,
836
            "hardcoded-credentials",
837
        )
838
        .into();
839
        let sp = v4::SigningParams::builder()
840
            .identity(&id)
841
            .region("test")
842
            // .security_token("security_token")
843
            .name("s3")
844
            .time(SystemTime::from(now))
845
            .settings(ss)
846
            .build()
847
            .expect("signing params must be valid");
848
849
        let mut body = SignableBody::UnsignedPayload;
850
        if req.headers().get(X_AMZ_CONTENT_SHA_256).is_some() {
851
            body = SignableBody::Bytes(req.body().as_bytes());
852
        }
853
854
        let output = aws_sigv4::http_request::sign(
855
            SignableRequest::new(
856
                req.method().as_str(),
857
                req.uri().to_string(),
858
                req.headers()
859
                    .iter()
860
                    .map(|(k, v)| (k.as_str(), std::str::from_utf8(v.as_bytes()).unwrap())),
861
                body,
862
            )
863
            .unwrap(),
864
            &sp.into(),
865
        )
866
        .expect("signing must succeed");
867
        let (aws_sig, _) = output.into_parts();
868
        aws_sig.apply_to_request_http1x(&mut req);
869
        let expected_req = req;
870
871
        let mut req = req_fn();
872
873
        let loader = AwsDefaultLoader::new(
874
            Client::new(),
875
            AwsConfig {
876
                access_key_id: Some("access_key_id".to_string()),
877
                secret_access_key: Some("secret_access_key".to_string()),
878
                session_token: Some("security_token".to_string()),
879
                ..Default::default()
880
            },
881
        );
882
        let cred = loader.load().await?.unwrap();
883
884
        let signer = Signer::new("s3", "test").time(now);
885
        signer
886
            .sign_query(&mut req, Duration::from_secs(3600), &cred)
887
            .expect("must apply success");
888
        let actual_req = req;
889
890
        compare_request(&name, &expected_req, &actual_req);
891
892
        Ok(())
893
    }
894
}