scratchstack_aws_signature/signature.rs
1//! AWS API request signatures verification routines.
2//!
3//! This is essentially the server-side complement of [rusoto_signature](https://crates.io/crates/rusoto_signature)
4//! but follows the implementation of [python-aws-sig](https://github.com/dacut/python-aws-sig).
5//!
6//! This implements the AWS [SigV4](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
7//! and [SigV4S3](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html)
8//! algorithms.
9//!
10use {
11 chrono::{DateTime, Duration, NaiveDate, Utc},
12 http::{
13 header::{HeaderMap, HeaderValue},
14 request::Parts,
15 Uri,
16 },
17 lazy_static::lazy_static,
18 log::trace,
19 regex::Regex,
20 ring::digest::{digest, SHA256},
21 scratchstack_aws_principal::PrincipalActor,
22 std::{
23 any::type_name,
24 collections::{BTreeMap, HashMap},
25 convert::{From, Into},
26 error::Error,
27 fmt::{Debug, Display, Formatter, Result as FmtResult},
28 future::Future,
29 io::{Error as IOError, Write},
30 str::from_utf8,
31 task::{Context, Poll},
32 },
33 subtle::ConstantTimeEq,
34 tower::{BoxError, Service},
35};
36
37use crate::{chronoutil::parse_date_str, hmac::hmac_sha256};
38
39/// Content-Type string for HTML forms
40const APPLICATION_X_WWW_FORM_URLENCODED: &str = "application/x-www-form-urlencoded";
41
42/// Algorithm for AWS SigV4
43const AWS4_HMAC_SHA256: &str = "AWS4-HMAC-SHA256";
44
45/// String included at the end of the AWS SigV4 credential scope
46const AWS4_REQUEST: &str = "aws4_request";
47
48/// Header parameter for the authorization
49const AUTHORIZATION: &str = "authorization";
50
51/// Content-Type parameter for specifying the character set
52const CHARSET: &str = "charset";
53
54/// Signature field for the access key
55const CREDENTIAL: &str = "Credential";
56
57/// Header field for the content type
58const CONTENT_TYPE: &str = "content-type";
59
60/// Header parameter for the date
61const DATE: &str = "date";
62
63/// Compact ISO8601 format used for the string to sign
64const ISO8601_COMPACT_FORMAT: &str = "%Y%m%dT%H%M%SZ";
65
66/// SHA-256 of an empty string.
67const SHA256_EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
68
69/// Signature field for the signature itself
70const SIGNATURE: &str = "Signature";
71
72/// Authorization header parameter specifying the signed headers
73const SIGNEDHEADERS: &str = "SignedHeaders";
74
75/// Query parameter for delivering the access key
76const X_AMZ_CREDENTIAL: &str = "X-Amz-Credential";
77
78/// Query parameter for delivering the date
79const X_AMZ_DATE: &str = "X-Amz-Date";
80
81/// Header for delivering the alternate date
82const X_AMZ_DATE_LOWER: &str = "x-amz-date";
83
84/// Query parameter for delivering the session token
85const X_AMZ_SECURITY_TOKEN: &str = "X-Amz-Security-Token";
86
87/// Header for delivering the session token
88const X_AMZ_SECURITY_TOKEN_LOWER: &str = "x-amz-security-token";
89
90/// Query parameter for delivering the signature
91const X_AMZ_SIGNATURE: &str = "X-Amz-Signature";
92
93/// Query parameter specifying the signed headers
94const X_AMZ_SIGNEDHEADERS: &str = "X-Amz-SignedHeaders";
95
96/// Length of a SigV4 signature string.
97const SIGV4_SIGNATURE_LEN: usize = 64;
98
99lazy_static! {
100 /// Multiple slash pattern for condensing URIs
101 static ref MULTISLASH: Regex = Regex::new("//+").unwrap();
102
103 /// Multiple space pattern for condensing header values
104 static ref MULTISPACE: Regex = Regex::new(" +").unwrap();
105
106 /// Pattern for the start of an AWS4 signature Authorization header.
107 static ref AWS4_HMAC_SHA256_RE: Regex = Regex::new(r"\s*AWS4-HMAC-SHA256(?:\s+|$)").unwrap();
108}
109
110/// Error returned when an attempt at validating an AWS SigV4 signature fails.
111#[derive(Debug)]
112pub enum SignatureError {
113 /// Validation failed due to an underlying I/O error.
114 IO(IOError),
115
116 /// The request body used an unsupported character set encoding. Currently only UTF-8 is supported.
117 InvalidBodyEncoding {
118 /// A message describing the error.
119 message: String,
120 },
121
122 /// The request signature specified an invalid credential -- either the access key was not specified, or the
123 /// credential scope (in the form `<code>_date_/_region_/_service_/aws4_request</code>`) did not match the
124 /// expected value for the server.
125 InvalidCredential {
126 /// A message describing the error.
127 message: String,
128 },
129
130 /// The secret key contains invalid bytes.
131 InvalidSecretKey,
132
133 /// The signature passed in the request did not match the calculated signature value.
134 InvalidSignature {
135 /// A message describing the error.
136 message: String,
137 },
138
139 /// The type of signing key is incorrect for this operation.
140 InvalidSigningKeyKind {
141 /// A message describing the error.
142 message: String,
143 },
144
145 /// The URI path includes invalid components. This can be a malformed hex encoding (e.g. `%0J`), a non-absolute
146 /// URI path (`foo/bar`), or a URI path that attempts to navigate above the root (`/x/../../../y`).
147 InvalidURIPath {
148 /// A message describing the error.
149 message: String,
150 },
151
152 /// An HTTP header was malformed -- the value could not be decoded as UTF-8, or the header was empty and this is
153 /// not allowed (e.g. the `content-type` header), or the header could not be parsed (e.g., the `date` header is
154 /// not a valid date).
155 MalformedHeader {
156 /// A message describing the error.
157 message: String,
158 },
159
160 /// A query parameter was malformed -- the value could not be decoded as UTF-8, or the parameter was empty and
161 /// this is not allowed (e.g. a signature parameter), or the parameter could not be parsed (e.g., the `X-Amz-Date`
162 /// parameter is not a valid date).
163 MalformedParameter {
164 /// A message describing the error.
165 message: String,
166 },
167
168 /// The AWS SigV4 signature was malformed in some way. This can include invalid timestamp formats, missing
169 /// authorization components, or unparseable components.
170 MalformedSignature {
171 /// A message describing the error.
172 message: String,
173 },
174
175 /// A required HTTP header (and its equivalent in the query string) is missing.
176 MissingHeader {
177 /// The name of the missing header.
178 header: String,
179 },
180
181 /// A required query parameter is missing. This is used internally in the library; external callers only see
182 /// `MissingHeader`.
183 MissingParameter {
184 /// The name of the missing query parameter.
185 parameter: String,
186 },
187
188 /// An HTTP header that can be specified only once was specified multiple times.
189 MultipleHeaderValues {
190 /// The name of the header.
191 header: String,
192 },
193
194 /// A query parameter that can be specified only once was specified multiple times.
195 MultipleParameterValues {
196 /// The name of the query parameter.
197 parameter: String,
198 },
199
200 /// The timestamp in the request is out of the allowed range.
201 TimestampOutOfRange {
202 /// The minimum allowed timestamp.
203 minimum: DateTime<Utc>,
204
205 /// The maximum allowed timestamp.
206 maximum: DateTime<Utc>,
207
208 /// The timestamp received in the request.
209 received: DateTime<Utc>,
210 },
211
212 /// The access key specified in the request is unknown.
213 UnknownAccessKey {
214 /// The unknown access key.
215 access_key: String,
216 },
217
218 /// The signature algorithm requested by the caller is unknown. This library only supports the `AWS4-HMAC-SHA256`
219 /// algorithm.
220 UnknownSignatureAlgorithm {
221 /// The unknown signature algorithm.
222 algorithm: String,
223 },
224}
225
226impl Display for SignatureError {
227 fn fmt(&self, f: &mut Formatter) -> FmtResult {
228 match self {
229 Self::IO(ref e) => Display::fmt(e, f),
230 Self::InvalidBodyEncoding {
231 message,
232 } => write!(f, "Invalid body encoding: {}", message),
233 Self::InvalidCredential {
234 message,
235 } => write!(f, "Invalid credential: {}", message),
236 Self::InvalidSecretKey => write!(f, "Invalid secret key"),
237 Self::InvalidSignature {
238 message,
239 } => write!(f, "Invalid request signature: {}", message),
240 Self::InvalidSigningKeyKind {
241 message,
242 } => write!(f, "Invalid signing key kind: {}", message),
243 Self::InvalidURIPath {
244 message,
245 } => write!(f, "Invalid URI path: {}", message),
246 Self::MalformedHeader {
247 message,
248 } => write!(f, "Malformed header: {}", message),
249 Self::MalformedParameter {
250 message,
251 } => write!(f, "Malformed query parameter: {}", message),
252 Self::MalformedSignature {
253 message,
254 } => write!(f, "Malformed signature: {}", message),
255 Self::MissingHeader {
256 header,
257 } => write!(f, "Missing header: {}", header),
258 Self::MissingParameter {
259 parameter,
260 } => write!(f, "Missing query parameter: {}", parameter),
261 Self::MultipleHeaderValues {
262 header,
263 } => write!(f, "Multiple values for header: {}", header),
264 Self::MultipleParameterValues {
265 parameter,
266 } => write!(f, "Multiple values for query parameter: {}", parameter),
267 Self::TimestampOutOfRange {
268 minimum,
269 maximum,
270 received,
271 } => {
272 write!(
273 f,
274 "Request timestamp out of range: minimum={}, maximum={}, received={}",
275 minimum, maximum, received
276 )
277 }
278 Self::UnknownAccessKey {
279 access_key,
280 } => write!(f, "Unknown access key: {}", access_key),
281 Self::UnknownSignatureAlgorithm {
282 algorithm,
283 } => write!(f, "Unknown signature algorithm: {}", algorithm),
284 }
285 }
286}
287
288impl Error for SignatureError {
289 fn source(&self) -> Option<&(dyn Error + 'static)> {
290 match self {
291 Self::IO(ref e) => Some(e),
292 _ => None,
293 }
294 }
295}
296
297impl From<IOError> for SignatureError {
298 fn from(e: IOError) -> SignatureError {
299 SignatureError::IO(e)
300 }
301}
302
303/// The types of signing key available.
304///
305/// See [`SigningKey`] for information on the types of keys and how they are derived.
306#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307pub enum SigningKeyKind {
308 /// KSecret: the raw secret key.
309 KSecret,
310
311 /// KDate: `HMAC_SHA256("AWS4" + KSecret, request_date)`, where `request_date` is the date of
312 /// the request in the UTC time zone formatted as "YYYYMMDD".
313 KDate,
314
315 /// KRegion: `HMAC_SHA256(KDate, region)`.
316 KRegion,
317
318 /// KService: `HMAC_SHA256(KRegion, service)`.
319 KService,
320
321 /// KSigning: `HMAC_SHA256(KService, "aws4_request")`.
322 KSigning,
323}
324
325impl Display for SigningKeyKind {
326 fn fmt(&self, f: &mut Formatter) -> FmtResult {
327 match self {
328 SigningKeyKind::KSecret => write!(f, "KSecret"),
329 SigningKeyKind::KDate => write!(f, "KDate"),
330 SigningKeyKind::KRegion => write!(f, "KRegion"),
331 SigningKeyKind::KService => write!(f, "KService"),
332 SigningKeyKind::KSigning => write!(f, "KSigning"),
333 }
334 }
335}
336
337/// An AWS SigV4 key for signing requests.
338///
339/// Signing keys are derived from a secret key and are used to sign requests made to AWS services.
340/// The raw secret key is hashed with other attributes in the request to derive the signing key.
341///
342/// In the table below, `HMAC_SHA256(key, data)` denotes the HMAC SHA-256 function with the given
343/// key and data.
344///
345/// The stages of derivation are:
346/// * [`KSecret`][SigningKeyKind::KSecret]: The raw secret key.
347/// * [`KDate`][SigningKeyKind::KDate]: The secret key hashed with the request date in UTC formatted
348/// as "YYYYMMDD". This is calculated as `HMAC_SHA256("AWS4" + KSecret, request_date)`.
349/// * [`KRegion`][SigningKeyKind::KRegion]: The KDate key hashed with the region name. This is
350/// calculated as `HMAC_SHA256(KDate, region)`.
351/// * [`KService`][SigningKeyKind::KService]: The KRegion key hashed with the service name. This is
352/// calculated as `HMAC_SHA256(KRegion, service)`.
353/// * [`KSigning`][SigningKeyKind::KSigning]: The KService key hashed with the string
354/// `"aws4_request"`. This is calculated as `HMAC_SHA256(KService, "aws4_request")`.
355///
356/// The `KSigning` key is the most secure key type. If the key is leaked, an attacker can only sign
357/// requests for the given date, region, and service. Key store services should never vend anything
358/// other than the `KSigning` key to an application service.
359///
360/// It is not possible to derive an earlier key from a later key—e.g., you cannot derive a
361/// `KRegion` key from a `KService` key. However, you can derive a later key from an earlier key.
362#[derive(Clone)]
363pub struct SigningKey {
364 /// The type of signing key.
365 pub kind: SigningKeyKind,
366
367 /// The key itself.
368 pub key: Vec<u8>,
369}
370
371impl Debug for SigningKey {
372 fn fmt(&self, f: &mut Formatter) -> FmtResult {
373 f.debug_struct("SigningKey")
374 .field("kind", &self.kind)
375 .field("key", &format!("<{} bytes>", self.key.len()))
376 .finish()
377 }
378}
379
380impl SigningKey {
381 /// Convert this key into the specified kind of key.
382 ///
383 /// It is safe to call this function with the same kind of key as the existing key; this will
384 /// return a copy of the existing key.
385 ///
386 /// # Errors
387 /// This function returns an error if the existing key is
388 pub fn try_derive<R, S>(
389 &self,
390 derived_key_kind: SigningKeyKind,
391 req_date: &NaiveDate,
392 region: R,
393 service: S,
394 ) -> Result<Self, SignatureError>
395 where
396 R: AsRef<str>,
397 S: AsRef<str>,
398 {
399 match derived_key_kind {
400 SigningKeyKind::KSigning => Ok(self.to_ksigning_key(req_date, ®ion, &service)),
401 SigningKeyKind::KService => self.try_to_kservice_key(req_date, ®ion, &service),
402 SigningKeyKind::KRegion => self.try_to_kregion_key(req_date, ®ion),
403 SigningKeyKind::KDate => self.try_to_kdate_key(req_date),
404 SigningKeyKind::KSecret => match self.kind {
405 SigningKeyKind::KSecret => Ok(self.clone()),
406 _ => Err(SignatureError::InvalidSigningKeyKind {
407 message: format!("Cannot derive {} key from {} key", derived_key_kind, self.kind),
408 }),
409 },
410 }
411 }
412
413 /// Return a KService key given a KService, KRegion, KDate, or KSecret key.
414 ///
415 /// # Errors
416 /// [`SignatureError::InvalidSigningKeyKind`] is returned if derivation to an earlier
417 /// kind is attempted, e.g. [`KRegion`][SigningKeyKind::KRegion] into [`KDate`][SigningKeyKind::KDate].
418 pub fn try_to_kservice_key<R, S>(&self, req_date: &NaiveDate, region: R, service: S) -> Result<Self, SignatureError>
419 where
420 R: AsRef<str>,
421 S: AsRef<str>,
422 {
423 match self.kind {
424 SigningKeyKind::KService => Ok(self.clone()),
425 SigningKeyKind::KRegion | SigningKeyKind::KDate | SigningKeyKind::KSecret => {
426 let k_region = self.to_kregion_key(req_date, ®ion);
427 Ok(Self {
428 kind: SigningKeyKind::KService,
429 key: hmac_sha256(&k_region.key, service.as_ref().as_bytes()).as_ref().to_vec(),
430 })
431 }
432 _ => Err(SignatureError::InvalidSigningKeyKind {
433 message: format!("Can't derive KService key from {}", self.kind),
434 }),
435 }
436 }
437
438 /// Return a [`KRegion`][SigningKeyKind::KRegion] key given a
439 /// [`KRegion`][SigningKeyKind::KRegion], [`KDate`][SigningKeyKind::KDate], or
440 /// [`KSecret`][SigningKeyKind::KSecret] key.
441 ///
442 /// # Errors
443 /// [`SignatureError::InvalidSigningKeyKind`] is returned if this is given a
444 /// [`KSigning`][SigningKeyKind::KSigning] key.
445 pub fn try_to_kregion_key<R>(&self, req_date: &NaiveDate, region: R) -> Result<Self, SignatureError>
446 where
447 R: AsRef<str>,
448 {
449 match self.kind {
450 SigningKeyKind::KRegion => Ok(self.clone()),
451 SigningKeyKind::KDate | SigningKeyKind::KSecret => {
452 let k_date = self.to_kdate_key(req_date);
453 Ok(Self {
454 kind: SigningKeyKind::KRegion,
455 key: hmac_sha256(&k_date.key, region.as_ref().as_bytes()).as_ref().to_vec(),
456 })
457 }
458 _ => Err(SignatureError::InvalidSigningKeyKind {
459 message: format!("Can't derive KRegion key from {}", self.kind),
460 }),
461 }
462 }
463
464 /// Return a [`KDate`][SigningKeyKind::KDate] key given a [`KDate`][SigningKeyKind::KDate] or
465 /// [`KSecret`][SigningKeyKind::KSecret] key.
466 ///
467 /// # Errors
468 /// [`SignatureError::InvalidSigningKeyKind`] is returned if this is given a
469 /// [`KRegion`][SigningKeyKind::KRegion],
470 /// [`KService`][SigningKeyKind::KService], or
471 /// [`KSigning`][SigningKeyKind::KSigning] key.
472 ///
473 /// This function returns an error if given a KRegion, KSigning, or KService key.
474 pub fn try_to_kdate_key(&self, req_date: &NaiveDate) -> Result<Self, SignatureError> {
475 match self.kind {
476 // Already the same type.
477 SigningKeyKind::KDate => Ok(self.clone()),
478
479 // key is KSecret == AWS4 + secret key.
480 // KDate = HMAC(KSecret + req_date)
481 SigningKeyKind::KSecret => {
482 let ymd = format!("{}", req_date.format("%Y%m%d"));
483 let k_secret_str = match from_utf8(&self.key) {
484 Ok(s) => s,
485 Err(_) => return Err(SignatureError::InvalidSecretKey),
486 };
487 Ok(SigningKey {
488 kind: SigningKeyKind::KDate,
489 key: hmac_sha256(format!("AWS4{}", k_secret_str).as_bytes(), ymd.as_bytes()).as_ref().to_vec(),
490 })
491 }
492
493 _ => Err(SignatureError::InvalidSigningKeyKind {
494 message: format!("Can't derive KDate key from {}", self.kind),
495 }),
496 }
497 }
498
499 /// Convert this key into the specified kind of key.
500 ///
501 /// # Panics
502 /// This function panics if the existing key cannot be derived into the dervied key kind. Use
503 /// [`try_derive`][SigningKey::try_derive] for a non-panicking version.
504 pub fn derive<R, S>(&self, derived_key_kind: SigningKeyKind, req_date: &NaiveDate, region: R, service: S) -> Self
505 where
506 R: AsRef<str>,
507 S: AsRef<str>,
508 {
509 match self.try_derive(derived_key_kind, req_date, ®ion, &service) {
510 Ok(s) => s,
511 Err(e) => panic!("{}", e),
512 }
513 }
514
515 /// Return a [`KSigning`][SigningKeyKind::KSigning] key given a
516 /// [`KSigning`][SigningKeyKind::KSigning],
517 /// [`KService`][SigningKeyKind::KService],
518 /// [`KRegion`][SigningKeyKind::KRegion],
519 /// [`KDate`][SigningKeyKind::KDate], or
520 /// [`KSecret`][SigningKeyKind::KSecret] key.
521 ///
522 /// This function is infallible; an equivalent `try_to_signing_key()` method does not exist as
523 /// as result.
524 pub fn to_ksigning_key<R, S>(&self, req_date: &NaiveDate, region: R, service: S) -> Self
525 where
526 R: AsRef<str>,
527 S: AsRef<str>,
528 {
529 match self.kind {
530 SigningKeyKind::KSigning => self.clone(),
531 _ => {
532 let k_service = self.to_kservice_key(req_date, ®ion, &service);
533 Self {
534 kind: SigningKeyKind::KSigning,
535 key: hmac_sha256(&k_service.key, AWS4_REQUEST.as_bytes()).as_ref().to_vec(),
536 }
537 }
538 }
539 }
540
541 /// Return a [`KService`][SigningKeyKind::KService] key given a
542 /// [`KService`][SigningKeyKind::KService],
543 /// [`KRegion`][SigningKeyKind::KRegion],
544 /// [`KDate`][SigningKeyKind::KDate], or
545 /// [`KSecret`][SigningKeyKind::KSecret] key.
546 ///
547 /// # Panics
548 /// This function will panic if given a [`KSigning`][SigningKeyKind::KSigning] key. Use
549 /// [`try_to_kservice_key`][SigningKey::try_to_kservice_key] for a non-panicking version.
550 pub fn to_kservice_key<R, S>(&self, req_date: &NaiveDate, region: R, service: S) -> Self
551 where
552 R: AsRef<str>,
553 S: AsRef<str>,
554 {
555 match self.try_to_kservice_key(req_date, ®ion, &service) {
556 Ok(s) => s,
557 Err(e) => panic!("{}", e),
558 }
559 }
560
561 /// Return a [`KRegion`][SigningKeyKind::KRegion] key given a
562 /// [`KRegion`][SigningKeyKind::KRegion],
563 /// [`KDate`][SigningKeyKind::KDate], or
564 /// [`KSecret`][SigningKeyKind::KSecret] key.
565 ///
566 /// # Panics
567 /// This function will panic if given a [`KSigning`][SigningKeyKind::KSigning] or
568 /// [`KService`][SigningKeyKind::KService] key. Use
569 /// [`try_to_kregion_key`][SigningKey::try_to_kregion_key] for a non-panicking version.
570 pub fn to_kregion_key<R>(&self, req_date: &NaiveDate, region: R) -> Self
571 where
572 R: AsRef<str>,
573 {
574 match self.try_to_kregion_key(req_date, ®ion) {
575 Ok(s) => s,
576 Err(e) => panic!("{}", e),
577 }
578 }
579
580 /// Return a [`KDate`][SigningKeyKind::KDate] key given a
581 /// [`KDate`][SigningKeyKind::KDate], or
582 /// [`KSecret`][SigningKeyKind::KSecret] key.
583 ///
584 /// # Panics
585 /// This function will panic if given a [`KSigning`][SigningKeyKind::KSigning],
586 /// [`KService`][SigningKeyKind::KService], or
587 /// [`KRegion`][SigningKeyKind::KRegion] key. Use
588 /// [`try_to_kdate_key`][SigningKey::try_to_kdate_key] for a non-panicking version.
589 pub fn to_kdate_key(&self, req_date: &NaiveDate) -> Self {
590 match self.try_to_kdate_key(req_date) {
591 Ok(s) => s,
592 Err(e) => panic!("{}", e),
593 }
594 }
595}
596
597/// A trait bound that describes how we obtain a signing key of a given type given a request. If you need to encapsulate
598/// additional data (e.g. a database connection) to look up a key, use this to implement a struct.
599pub trait GetSigningKey {}
600
601/// A request for retrieving a signing key from an outside source.
602pub struct GetSigningKeyRequest {
603 /// The type of signing key to retrieve.
604 pub signing_key_kind: SigningKeyKind,
605
606 /// The access key to use to retrieve the signing key.
607 pub access_key: String,
608
609 /// An optional session token to use to retrieve the signing key.
610 pub session_token: Option<String>,
611
612 /// The date of the request.
613 pub request_date: NaiveDate,
614
615 /// The region of the service being accessed.
616 pub region: String,
617
618 /// The service being accessed.
619 pub service: String,
620}
621
622impl<F, E> GetSigningKey
623 for dyn Service<GetSigningKeyRequest, Response = (PrincipalActor, SigningKey), Error = E, Future = F>
624where
625 F: Future<Output = Result<(PrincipalActor, SigningKey), SignatureError>> + Send + Sync,
626 E: Into<BoxError>,
627{
628}
629
630/// A wrapper for a function that retrieves a signing key.
631///
632/// To construct a `GetSigningKeyFn`, use the [`get_signing_key_fn`] function.
633///
634/// This implements [`Service<GetSigningKeyRequest>`][tower::Service].
635#[derive(Clone, Copy)]
636pub struct GetSigningKeyFn<F> {
637 f: F,
638}
639
640/// Wrap an async function taking a signing request and returns a result into a `GetSigningKey` trait implementation.
641///
642/// The function signature should look like:
643/// ```no_run
644/// # use { chrono::{DateTime, Utc}, scratchstack_aws_signature::{SignatureError, SigningKey, SigningKeyKind} };
645/// use scratchstack_aws_principal::PrincipalActor;
646///
647/// async fn get_key(
648/// kind: SigningKeyKind,
649/// access_key: String,
650/// session_token: Option<String>,
651/// request_date: DateTime<Utc>,
652/// region: String, service: String)
653/// -> Result<(PrincipalActor, SigningKey), SignatureError>
654/// # { todo!() }
655/// ```
656pub fn get_signing_key_fn<F>(f: F) -> GetSigningKeyFn<F> {
657 GetSigningKeyFn {
658 f,
659 }
660}
661
662impl<F> Debug for GetSigningKeyFn<F> {
663 fn fmt(&self, f: &mut Formatter) -> FmtResult {
664 f.debug_struct("GetSigningKeyFn").field("f", &format_args!("{}", type_name::<F>())).finish()
665 }
666}
667
668/// Implementation of a [`Service`] for [`GetSigningKeyFn`].
669///
670/// To send the request, use the [`call`][Service::call] method.
671impl<F, Fut, E> Service<GetSigningKeyRequest> for GetSigningKeyFn<F>
672where
673 F: FnMut(SigningKeyKind, String, Option<String>, NaiveDate, String, String) -> Fut,
674 Fut: Future<Output = Result<(PrincipalActor, SigningKey), E>> + Send + Sync,
675 E: Into<BoxError>,
676{
677 type Response = (PrincipalActor, SigningKey);
678 type Error = E;
679 type Future = Fut;
680
681 fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> {
682 Poll::Ready(Ok(()))
683 }
684
685 /// Invoke the underlying function to retrieve a signing key.
686 ///
687 /// # Example
688 /// ```rust
689 /// # use chrono::{NaiveDate, Utc};
690 /// # use scratchstack_aws_signature::{GetSigningKeyRequest, SignatureError, SigningKey, SigningKeyKind, get_signing_key_fn};
691 /// # use scratchstack_aws_principal::PrincipalActor;
692 /// use tower::Service;
693 ///
694 /// async fn get_key(
695 /// kind: SigningKeyKind,
696 /// access_key: String,
697 /// session_token: Option<String>,
698 /// request_date: NaiveDate,
699 /// region: String,
700 /// service: String)
701 /// -> Result<(PrincipalActor, SigningKey), SignatureError> {
702 /// // Replace with your implementation for obtaining a key.
703 /// let actor = PrincipalActor::user(
704 /// "aws", "123456789012", "/", "user", "AIDAQXZEAEXAMPLEUSER").unwrap();
705 /// let key = SigningKey {
706 /// kind: SigningKeyKind::KSecret,
707 /// key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".as_bytes().to_vec()
708 /// };
709 /// Ok((actor, key.try_derive(kind, &request_date, region, service)?))
710 /// }
711 ///
712 /// let mut get_key_service = get_signing_key_fn(get_key);
713 /// let req = GetSigningKeyRequest {
714 /// signing_key_kind: SigningKeyKind::KSigning,
715 /// access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
716 /// session_token: None,
717 /// request_date: NaiveDate::from_ymd_opt(2021, 1, 1).unwrap(),
718 /// region: "us-east-1".to_string(),
719 /// service: "example".to_string(),
720 /// };
721 /// # tokio_test::block_on(async {
722 /// let (actor, key) = get_key_service.call(req).await.unwrap();
723 /// # });
724 /// ```
725 fn call(&mut self, req: GetSigningKeyRequest) -> Self::Future {
726 (self.f)(req.signing_key_kind, req.access_key, req.session_token, req.request_date, req.region, req.service)
727 }
728}
729
730/// A data structure containing the elements of the request (some client-supplied, some service-supplied) involved in
731/// the SigV4 verification process.
732///
733/// This is typically populated from the HTTP request headers and body by using the
734/// [`Request::from_http_request_parts`] method.
735#[derive(Debug)]
736pub struct Request {
737 /// The request method (GET, PUT, POST) (client).
738 pub request_method: String,
739
740 /// The URI path being accessed (client).
741 pub uri: Uri,
742
743 /// The HTTP headers sent with the request (client).
744 pub headers: HeaderMap<HeaderValue>,
745
746 /// The request body (if any) (client).
747 pub body: Option<Vec<u8>>,
748}
749
750impl Request {
751 /// Create a Request from an HTTP request.
752 ///
753 /// # Example
754 /// ```rust
755 /// # use scratchstack_aws_signature::Request;
756 /// let http_req = http::Request::get("https://example.com")
757 /// .header("X-Amz-Date", "20210101T000000Z")
758 /// .body(())
759 /// .unwrap();
760 /// let req = Request::from_http_request_parts(&http_req.into_parts().0, None);
761 /// ```
762 pub fn from_http_request_parts(parts: &Parts, body: Option<Vec<u8>>) -> Self {
763 Self {
764 request_method: parts.method.as_str().to_string(),
765 uri: parts.uri.clone(),
766 headers: parts.headers.clone(),
767 body,
768 }
769 }
770
771 /// Returns a [`GetSigningKeyRequest`] from this request.
772 ///
773 /// This is used to request a signing key from an external source.
774 ///
775 /// # Example
776 /// ```rust
777 /// # use chrono::NaiveDate;
778 /// # use scratchstack_aws_signature::{GetSigningKeyRequest, Request, SignatureError, SigningKeyKind};
779 /// # let http_req = http::Request::get("https://example.com")
780 /// # .header("X-Amz-Date", "20210101T000000Z")
781 /// # .header("Authorization", "AWS4-HMAC-SHA256 \
782 /// # Credential=AKIAIOSFODNN7EXAMPLE/20210101/us-east-1/example/aws4_request, \
783 /// # SignedHeaders=host;x-amz-date, \
784 /// # Signature=3ea4679d2ecf5a8293e1fb10298c82988f024a2e937e9b37876b34bb119da0bc")
785 /// # .body(())
786 /// # .unwrap();
787 /// # let req = Request::from_http_request_parts(&http_req.into_parts().0, None);
788 /// let gsk_req = req.to_get_signing_key_request(
789 /// SigningKeyKind::KSigning, "us-east-1", "example").unwrap();
790 /// assert_eq!(gsk_req.signing_key_kind, SigningKeyKind::KSigning);
791 /// assert_eq!(gsk_req.access_key, "AKIAIOSFODNN7EXAMPLE");
792 /// assert_eq!(gsk_req.request_date, NaiveDate::from_ymd_opt(2021, 1, 1).unwrap());
793 /// assert_eq!(gsk_req.region, "us-east-1");
794 /// assert_eq!(gsk_req.service, "example");
795 /// ```
796 pub fn to_get_signing_key_request<A1, A2>(
797 &self,
798 signing_key_kind: SigningKeyKind,
799 region: A1,
800 service: A2,
801 ) -> Result<GetSigningKeyRequest, SignatureError>
802 where
803 A1: AsRef<str>,
804 A2: AsRef<str>,
805 {
806 Ok(GetSigningKeyRequest {
807 signing_key_kind,
808 access_key: self.get_access_key(®ion, &service)?,
809 session_token: self.get_session_token()?,
810 request_date: self.get_request_date()?,
811 region: region.as_ref().to_string(),
812 service: service.as_ref().to_string(),
813 })
814 }
815
816 /// Retrieve a header value, requiring exactly one value be present.
817 ///
818 /// # Errors
819 /// If the header is missing, a `SignatureError::MissingHeader` error is returned.
820 ///
821 /// If the header contains multiple values, a `SignatureError::MultipleHeaderValues` error is returned.
822 ///
823 /// If the header value is not valid UTF-8, a `SignatureError::MalformedHeader` error is returned.
824 pub(crate) fn get_header_one<S: Into<String>>(&self, header: S) -> Result<String, SignatureError> {
825 let header = header.into();
826 let mut iter = self.headers.get_all(&header).iter();
827 match iter.next() {
828 None => Err(SignatureError::MissingHeader {
829 header: header.to_string(),
830 }),
831 Some(value) => match iter.next() {
832 None => match from_utf8(value.as_bytes()) {
833 Ok(ref s) => Ok(s.to_string()),
834 Err(_) => Err(SignatureError::MalformedHeader {
835 message: format!("{} cannot does not contain valid UTF-8", header),
836 }),
837 },
838 Some(_) => Err(SignatureError::MultipleHeaderValues {
839 header: header.to_string(),
840 }),
841 },
842 }
843 }
844
845 /// Return query parameters from the request, normalized, as a `HashMap` of parameter names to the list of
846 /// values for that parameter.
847 pub(crate) fn get_query_parameters(&self) -> Result<HashMap<String, Vec<String>>, SignatureError> {
848 match self.uri.query() {
849 Some(s) => normalize_query_parameters(s),
850 None => Ok(HashMap::new()),
851 }
852 }
853
854 /// Retrieve a query parameter, requiring exactly one value be present.
855 pub(crate) fn get_query_param_one(&self, parameter: &str) -> Result<String, SignatureError> {
856 match self.get_query_parameters()?.get(parameter) {
857 None => Err(SignatureError::MissingParameter {
858 parameter: parameter.to_string(),
859 }),
860 Some(values) => match values.len() {
861 0 => Err(SignatureError::MissingParameter {
862 parameter: parameter.to_string(),
863 }),
864 1 => Ok(values[0].to_string()),
865 _ => Err(SignatureError::MultipleParameterValues {
866 parameter: parameter.to_string(),
867 }),
868 },
869 }
870 }
871
872 /// Get the content type and character set used in the body
873 pub(crate) fn get_content_type_and_charset(&self) -> Result<(String, String), SignatureError> {
874 let content_type_opts = self.get_header_one(CONTENT_TYPE)?;
875
876 let mut parts = content_type_opts.split(';');
877 let content_type = match parts.next() {
878 Some(s) => s.trim(),
879 None => {
880 return Err(SignatureError::MalformedHeader {
881 message: "content-type header is empty".to_string(),
882 })
883 }
884 };
885
886 for option in parts {
887 let opt_trim = option.trim();
888 let opt_parts: Vec<&str> = opt_trim.splitn(2, '=').collect();
889
890 if opt_parts.len() == 2 && opt_parts[0] == CHARSET {
891 return Ok((content_type.to_string(), opt_parts[1].trim().to_lowercase()));
892 }
893 }
894
895 Ok((content_type.to_string(), "utf-8".to_string()))
896 }
897
898 /// The canonicalized URI path for a request.
899 pub(crate) fn get_canonical_uri_path(&self) -> Result<String, SignatureError> {
900 canonicalize_uri_path(self.uri.path())
901 }
902
903 /// The canonical query string from the query parameters.
904 ///
905 /// This takes the query_string from the request, merges it with the body if the request has a body of type
906 /// `application/x-www-form-urlencoded`, and orders the parameters.
907 pub(crate) fn get_canonical_query_string(&self) -> Result<String, SignatureError> {
908 let query_parameters = self.get_query_parameters()?;
909 let mut results = Vec::new();
910
911 for (key, values) in query_parameters.iter() {
912 // Don't include the signature itself.
913 if key != X_AMZ_SIGNATURE {
914 for value in values.iter() {
915 results.push(format!("{}={}", key, value));
916 }
917 }
918 }
919
920 if let Ok((content_type, charset)) = self.get_content_type_and_charset() {
921 if content_type == APPLICATION_X_WWW_FORM_URLENCODED {
922 if charset != "utf-8" && charset != "utf8" {
923 return Err(SignatureError::InvalidBodyEncoding {
924 message: format!("application/x-www-form-urlencoded body uses unsupported charset {}", charset),
925 });
926 }
927
928 // Parse the body as a URL string
929 let body_utf8 = match &self.body {
930 None => "",
931 Some(body) => match from_utf8(body) {
932 Ok(s) => s,
933 Err(_) => {
934 return Err(SignatureError::InvalidBodyEncoding {
935 message: "application/x-www-form-urlencoded body contains invalid UTF-8 characters"
936 .to_string(),
937 });
938 }
939 },
940 };
941
942 let body_normalized = normalize_query_parameters(body_utf8)?;
943 for (key, values) in body_normalized.iter() {
944 for value in values.iter() {
945 results.push(format!("{}={}", key, value));
946 }
947 }
948 }
949 }
950
951 results.sort_unstable();
952 Ok(results.join("&"))
953 }
954
955 /// The parameters from the Authorization header (only -- not the query parameter). If the Authorization header is not present
956 /// or is not an AWS SigV4 header, an Err(SignatureError) is returned.
957 pub(crate) fn get_authorization_header_parameters(&self) -> Result<HashMap<String, String>, SignatureError> {
958 let auth_headers = self.headers.get_all(AUTHORIZATION);
959 let mut parameters_opt: Option<&str> = None;
960
961 let mut auth_iter = auth_headers.iter();
962 loop {
963 match auth_iter.next() {
964 None => break,
965 Some(auth_header) => {
966 if let Ok(auth_header) = auth_header.to_str() {
967 if let Some(captures) = AWS4_HMAC_SHA256_RE.captures(auth_header) {
968 if parameters_opt.is_some() {
969 return Err(SignatureError::MultipleHeaderValues {
970 header: AUTHORIZATION.to_string(),
971 });
972 }
973
974 parameters_opt = Some(auth_header.split_at(captures.get(0).unwrap().end()).1);
975 trace!("parameters_opt set to {:?}; captures={:?}", parameters_opt, captures);
976 } else {
977 trace!("Not SigV4: {:?}", auth_header);
978 }
979 }
980 }
981 }
982 }
983
984 match parameters_opt {
985 None => Err(SignatureError::MissingHeader {
986 header: AUTHORIZATION.to_string(),
987 }),
988 Some(parameters) => {
989 if parameters.is_empty() {
990 Err(SignatureError::MalformedSignature {
991 message: "invalid Authorization header: missing parameters".to_string(),
992 })
993 } else {
994 let result = split_authorization_header_parameters(parameters)?;
995 if !result.contains_key(CREDENTIAL) {
996 Err(SignatureError::MalformedSignature {
997 message: "invalid Authorization header: missing Credential".to_string(),
998 })
999 } else if !result.contains_key(SIGNATURE) {
1000 Err(SignatureError::MalformedSignature {
1001 message: "invalid Authorization header: missing Signature".to_string(),
1002 })
1003 } else if !result.contains_key(SIGNEDHEADERS) {
1004 Err(SignatureError::MalformedSignature {
1005 message: "invalid Authorization header: missing SignedHeaders".to_string(),
1006 })
1007 } else {
1008 Ok(result)
1009 }
1010 }
1011 }
1012 }
1013 }
1014
1015 /// Returns a sorted dictionary containing the signed header names and their values.
1016 pub(crate) fn get_signed_headers(&self) -> Result<BTreeMap<String, Vec<Vec<u8>>>, SignatureError> {
1017 // See if the signed headers are listed in the query string.
1018 let qp_result = self.get_query_param_one(X_AMZ_SIGNEDHEADERS);
1019 let ah_result;
1020 let ah_signedheaders;
1021
1022 let signed_headers = match qp_result {
1023 Ok(ref sh) => sh,
1024 Err(e) => match e {
1025 SignatureError::MissingParameter {
1026 ..
1027 } => {
1028 ah_result = self.get_authorization_header_parameters();
1029 match ah_result {
1030 Err(e) => return Err(e),
1031 Ok(ref ahp) => {
1032 ah_signedheaders = ahp.get(SIGNEDHEADERS);
1033 match ah_signedheaders {
1034 None => {
1035 return Err(SignatureError::MalformedSignature {
1036 message: "invalid Authorization header: missing SignedHeaders".to_string(),
1037 })
1038 }
1039 Some(headers) => headers,
1040 }
1041 }
1042 }
1043 }
1044 _ => return Err(e),
1045 },
1046 };
1047
1048 // Header names are separated by semicolons.
1049 let parts: Vec<String> = signed_headers.split(';').map(|s| s.to_string()).collect();
1050
1051 // Make sure the signed headers list is canonicalized. For security reasons, we consider it an error if it isn't.
1052 let mut canonicalized = parts.clone();
1053 canonicalized.sort_unstable_by(|a, b| a.to_lowercase().partial_cmp(&b.to_lowercase()).unwrap());
1054
1055 if parts != canonicalized {
1056 return Err(SignatureError::MalformedSignature {
1057 message: "SignedHeaders is not canonicalized".to_string(),
1058 });
1059 }
1060
1061 let mut result = BTreeMap::<String, Vec<Vec<u8>>>::new();
1062 for header in canonicalized.iter() {
1063 let mut header_values = Vec::new();
1064 let v_iter = self.headers.get_all(header).iter();
1065
1066 for value in v_iter {
1067 match from_utf8(value.as_bytes()) {
1068 Ok(value) => header_values.push(value.as_bytes().to_vec()),
1069 Err(_) => {
1070 return Err(SignatureError::MalformedHeader {
1071 message: format!("Header {} contains invalid UTF-8 characters", header),
1072 })
1073 }
1074 }
1075 }
1076 if header_values.is_empty() {
1077 return Err(SignatureError::MissingHeader {
1078 header: header.to_string(),
1079 });
1080 }
1081
1082 result.insert(header.to_string(), header_values);
1083 }
1084
1085 Ok(result)
1086 }
1087
1088 /// The date of the request.
1089 pub(crate) fn get_request_date(&self) -> Result<NaiveDate, SignatureError> {
1090 let timestamp = self.get_request_timestamp()?;
1091 Ok(timestamp.naive_utc().date())
1092 }
1093
1094 /// The timestamp of the request.
1095 ///
1096 /// This returns the first value found from:
1097 ///
1098 /// * The `X-Amz-Date` query parameter.
1099 /// * The `X-Amz-Date` HTTP header.
1100 /// * The `Date` HTTP header.
1101 ///
1102 /// The timestamp _should_ be in ISO 8601 `YYYYMMDDTHHMMSSZ` format
1103 /// without milliseconds (_must_ per [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html)).
1104 /// However, the AWS SigV4 test suite includes a variety of date formats,
1105 /// including RFC 2822, RFC 3339, and ISO 8601. This routine allows all
1106 /// of these formats.
1107 ///
1108 /// This is unlikely to be useful in regular code; it is exposed for testing purposes.
1109 ///
1110 /// # Errors
1111 /// If the `X-Amz-Date`` query parameter is present but is not a valid timestamp,
1112 /// [`SignatureError::MalformedParameter`] is returned.
1113 ///
1114 /// If the `X-Amz-Date` query parameter is missing and the `X-Amz-Date` header is present but is not a valid
1115 /// timestamp, [`SignatureError::MalformedHeader`] is returned.
1116 ///
1117 /// If the `X-Amz-Date` query parameter and `X-Amz-Date` header are missing and the `Date` header is present
1118 /// but is not a valid timestamp, [`SignatureError::MalformedHeader`] is returned.
1119 ///
1120 /// If none of the above are present, [`SignatureError::MissingHeader`] is returned.
1121 ///
1122 /// # Example
1123 /// ```rust
1124 /// use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
1125 /// use http::Request;
1126 /// use scratchstack_aws_signature::Request as SigRequest;
1127 ///
1128 /// let req = Request::get("https://example.com")
1129 /// .header("X-Amz-Date", "20210101T000000Z")
1130 /// .body(())
1131 /// .unwrap();
1132 /// let req = SigRequest::from_http_request_parts(&req.into_parts().0, None);
1133 /// let expected = DateTime::<Utc>::from_naive_utc_and_offset(
1134 /// NaiveDateTime::new(
1135 /// NaiveDate::from_ymd_opt(2021, 1, 1).unwrap(),
1136 /// NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
1137 /// Utc
1138 /// );
1139 /// assert_eq!(req.get_request_timestamp().unwrap(), expected);
1140 /// ```
1141 pub fn get_request_timestamp(&self) -> Result<DateTime<Utc>, SignatureError> {
1142 // It turns out that unrolling this logic is the most straightforward way to return sensible error messages.
1143
1144 match self.get_query_param_one(X_AMZ_DATE) {
1145 Ok(date_str) => parse_date_str(
1146 &date_str,
1147 SignatureError::MalformedParameter {
1148 message: "X-Amz-Date is not a valid timestamp".to_string(),
1149 },
1150 ),
1151 Err(e) => match e {
1152 SignatureError::MissingParameter {
1153 ..
1154 } => match self.get_header_one(X_AMZ_DATE_LOWER) {
1155 Ok(date_str) => parse_date_str(
1156 &date_str,
1157 SignatureError::MalformedHeader {
1158 message: "X-Amz-Date is not a valid timestamp".to_string(),
1159 },
1160 ),
1161 Err(e) => match e {
1162 SignatureError::MissingHeader {
1163 ..
1164 } => match self.get_header_one(DATE) {
1165 Ok(date_str) => parse_date_str(
1166 &date_str,
1167 SignatureError::MalformedHeader {
1168 message: "Date is not a valid timestamp".to_string(),
1169 },
1170 ),
1171 Err(e) => Err(e),
1172 },
1173 _ => Err(e),
1174 },
1175 },
1176 _ => Err(e),
1177 },
1178 }
1179 }
1180
1181 /// The scope of the credentials to use, as calculated by the service's region and name, but using the timestamp
1182 /// of the request.
1183 ///
1184 /// The result is a string in the form `YYYYMMDD/region/service/aws4_request`.
1185 pub(crate) fn get_credential_scope<A1, A2>(&self, region: A1, service: A2) -> Result<String, SignatureError>
1186 where
1187 A1: AsRef<str>,
1188 A2: AsRef<str>,
1189 {
1190 let ts = self.get_request_timestamp()?;
1191 let date = ts.date_naive().format("%Y%m%d");
1192 Ok(format!("{}/{}/{}/{}", date, region.as_ref(), service.as_ref(), AWS4_REQUEST))
1193 }
1194
1195 /// The access key used to sign the request.
1196 ///
1197 /// If the credential scope does not match our expected credential scope, a SignatureError is returned.
1198 pub(crate) fn get_access_key<A1, A2>(&self, region: A1, service: A2) -> Result<String, SignatureError>
1199 where
1200 A1: AsRef<str>,
1201 A2: AsRef<str>,
1202 {
1203 let qp_result = self.get_query_param_one(X_AMZ_CREDENTIAL);
1204 let auth_headers;
1205
1206 let credential = match qp_result {
1207 Ok(ref c) => c,
1208 Err(e) => match e {
1209 SignatureError::MissingParameter {
1210 ..
1211 } => {
1212 auth_headers = self.get_authorization_header_parameters()?;
1213 match auth_headers.get(CREDENTIAL) {
1214 Some(c) => c,
1215 None => {
1216 trace!("auth_headers={:?}", auth_headers);
1217 return Err(SignatureError::MalformedSignature {
1218 message: "invalid Authorization header: missing Credential".to_string(),
1219 });
1220 }
1221 }
1222 }
1223 _ => return Err(e),
1224 },
1225 };
1226
1227 let parts: Vec<&str> = credential.splitn(2, '/').collect();
1228 if parts.len() != 2 {
1229 return Err(SignatureError::InvalidCredential {
1230 message: "Malformed credential".to_string(),
1231 });
1232 }
1233
1234 let access_key = parts[0];
1235 let request_scope = parts[1];
1236 let server_scope = self.get_credential_scope(region, service)?;
1237 if request_scope == server_scope {
1238 Ok(access_key.to_string())
1239 } else {
1240 Err(SignatureError::InvalidCredential {
1241 message: format!("Invalid credential scope: Expected {} instead of {}", server_scope, request_scope),
1242 })
1243 }
1244 }
1245
1246 /// The session token sent with the access key.
1247 ///
1248 /// Session tokens are used only for temporary credentials. If a long-term credential was used, the result
1249 /// is `Ok(None)`.
1250 pub(crate) fn get_session_token(&self) -> Result<Option<String>, SignatureError> {
1251 let qp_result = self.get_query_param_one(X_AMZ_SECURITY_TOKEN);
1252
1253 match qp_result {
1254 Ok(token) => Ok(Some(token)),
1255 Err(e) => match e {
1256 SignatureError::MissingParameter {
1257 ..
1258 } => match self.get_header_one(X_AMZ_SECURITY_TOKEN_LOWER) {
1259 Ok(token) => Ok(Some(token)),
1260 Err(e) => match e {
1261 SignatureError::MissingHeader {
1262 ..
1263 } => Ok(None),
1264 _ => Err(e),
1265 },
1266 },
1267 _ => Err(e),
1268 },
1269 }
1270 }
1271
1272 /// The signature passed into the request.
1273 pub(crate) fn get_request_signature(&self) -> Result<String, SignatureError> {
1274 match self.get_query_param_one(X_AMZ_SIGNATURE) {
1275 Ok(sig) => Ok(sig),
1276 Err(e) => match e {
1277 SignatureError::MissingParameter {
1278 ..
1279 } => {
1280 let ah: HashMap<String, String> = self.get_authorization_header_parameters()?;
1281 match ah.get(SIGNATURE) {
1282 Some(c) => Ok(c.to_string()),
1283 None => Err(SignatureError::MalformedSignature {
1284 message: "invalid Authorization header: missing Signature".to_string(),
1285 }),
1286 }
1287 }
1288 _ => Err(e),
1289 },
1290 }
1291 }
1292
1293 /// The AWS SigV4 canonical request given parameters from the HTTP request, as outlined in the
1294 /// [AWS documentation](http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html).
1295 ///
1296 /// The canonical request is:
1297 /// ```text
1298 /// request_method + '\n' +
1299 /// canonical_uri_path + '\n' +
1300 /// canonical_query_string + '\n' +
1301 /// signed_headers + '\n' +
1302 /// sha256(body).hexdigest()
1303 /// ```
1304 pub(crate) fn get_canonical_request(&self) -> Result<Vec<u8>, SignatureError> {
1305 let mut result = Vec::<u8>::new();
1306 let mut header_keys = Vec::<u8>::new();
1307 let canonical_uri_path = self.get_canonical_uri_path()?;
1308 let canonical_query_string = self.get_canonical_query_string()?;
1309 let body_hex_digest = self.get_body_digest()?;
1310
1311 result.write_all(self.request_method.as_bytes())?;
1312 result.push(b'\n');
1313 result.write_all(canonical_uri_path.as_bytes())?;
1314 result.push(b'\n');
1315 result.write_all(canonical_query_string.as_bytes())?;
1316 result.push(b'\n');
1317
1318 let mut is_first_key = true;
1319
1320 for (key, values) in self.get_signed_headers()? {
1321 let key_bytes = key.as_bytes();
1322
1323 result.write_all(key_bytes)?;
1324 result.push(b':');
1325
1326 let mut is_first_value = true;
1327 for ref value in values {
1328 if is_first_value {
1329 is_first_value = false;
1330 } else {
1331 result.push(b',');
1332 }
1333
1334 let value_collapsed_space = MULTISPACE.replace_all(from_utf8(value).unwrap(), " ");
1335 result.write_all(value_collapsed_space.as_bytes())?;
1336 }
1337 result.push(b'\n');
1338
1339 if is_first_key {
1340 is_first_key = false;
1341 } else {
1342 header_keys.push(b';');
1343 }
1344
1345 header_keys.write_all(key_bytes)?;
1346 }
1347
1348 result.push(b'\n');
1349 result.append(&mut header_keys);
1350 result.push(b'\n');
1351
1352 match self.get_content_type_and_charset() {
1353 Ok((content_type, _)) if content_type == APPLICATION_X_WWW_FORM_URLENCODED => {
1354 result.write(SHA256_EMPTY.as_bytes())?
1355 }
1356 _ => result.write(body_hex_digest.as_bytes())?,
1357 };
1358
1359 Ok(result)
1360 }
1361
1362 /// The SHA-256 hex digest of the body.
1363 pub(crate) fn get_body_digest(&self) -> Result<String, SignatureError> {
1364 match &self.body {
1365 None => Ok(SHA256_EMPTY.to_string()),
1366 Some(body) => Ok(hex::encode(digest(&SHA256, body).as_ref())),
1367 }
1368 }
1369
1370 /// The string to sign for the request.
1371 pub(crate) fn get_string_to_sign<A1, A2>(&self, region: A1, service: A2) -> Result<Vec<u8>, SignatureError>
1372 where
1373 A1: AsRef<str>,
1374 A2: AsRef<str>,
1375 {
1376 let mut result = Vec::new();
1377 let timestamp = self.get_request_timestamp()?;
1378 let credential_scope = self.get_credential_scope(region, service)?;
1379 let canonical_request = self.get_canonical_request()?;
1380 trace!("Credential scope: {:?}", credential_scope);
1381 trace!("Canonical request: {:?}", from_utf8(canonical_request.as_ref()));
1382
1383 result.write_all(AWS4_HMAC_SHA256.as_bytes())?;
1384 result.push(b'\n');
1385 write!(&mut result, "{}", timestamp.format(ISO8601_COMPACT_FORMAT))?;
1386 result.push(b'\n');
1387 result.write_all(credential_scope.as_bytes())?;
1388 result.push(b'\n');
1389 result.write_all(hex::encode(digest(&SHA256, &canonical_request).as_ref()).as_bytes())?;
1390
1391 Ok(result)
1392 }
1393}
1394
1395/// Return the expected signature for a request.
1396pub fn sigv4_get_expected_signature<A1, A2>(
1397 req: &Request,
1398 signing_key: &SigningKey,
1399 region: A1,
1400 service: A2,
1401) -> Result<String, SignatureError>
1402where
1403 A1: AsRef<str>,
1404 A2: AsRef<str>,
1405{
1406 let k_signing = signing_key.to_ksigning_key(&(req.get_request_date()?), ®ion, &service);
1407 let string_to_sign = req.get_string_to_sign(®ion, &service)?;
1408 trace!("String to sign: {:?}", from_utf8(string_to_sign.as_ref()));
1409
1410 Ok(hex::encode(hmac_sha256(&k_signing.key, &string_to_sign).as_ref()))
1411}
1412
1413/// Verify a SigV4 request at a particular point-in-time. This verifies that the request timestamp is not beyond the
1414/// allowed timestamp mismatch against the specified point-in-time, and that the request signature matches our expected
1415/// signature.
1416///
1417/// This is mainly for unit testing. For general purpose use, use `sigv4_verify`.
1418///
1419/// # Errors
1420/// If the request timestamp is outside the allowed timestamp mismatch, a `SignatureError::TimestampOutOfRange` error
1421/// is returned.
1422///
1423/// If the request signature does not match the expected signature, a `SignatureError::InvalidSignature` error is
1424/// returned.
1425///
1426/// # Panics
1427/// If the expected signature string is not 64 bytes long, this function will panic. This should never happen.
1428pub fn sigv4_verify_at<A1, A2>(
1429 req: &Request,
1430 signing_key: &SigningKey,
1431 server_timestamp: &DateTime<Utc>,
1432 allowed_mismatch: Option<Duration>,
1433 region: A1,
1434 service: A2,
1435) -> Result<(), SignatureError>
1436where
1437 A1: AsRef<str>,
1438 A2: AsRef<str>,
1439{
1440 if let Some(mm) = allowed_mismatch {
1441 let req_ts = req.get_request_timestamp()?;
1442 let min_ts = server_timestamp.checked_sub_signed(mm).unwrap_or(*server_timestamp);
1443 let max_ts = server_timestamp.checked_add_signed(mm).unwrap_or(*server_timestamp);
1444
1445 if req_ts < min_ts || req_ts > max_ts {
1446 return Err(SignatureError::TimestampOutOfRange {
1447 minimum: min_ts,
1448 maximum: max_ts,
1449 received: req_ts,
1450 });
1451 }
1452 }
1453
1454 let request_sig_str = req.get_request_signature()?;
1455 let expected_sig_str = sigv4_get_expected_signature(req, signing_key, ®ion, &service)?;
1456
1457 if request_sig_str.len() != SIGV4_SIGNATURE_LEN {
1458 return Err(SignatureError::InvalidSignature {
1459 message: format!("Expected {}-byte signature instead of {}", SIGV4_SIGNATURE_LEN, request_sig_str.len()),
1460 });
1461 }
1462
1463 if expected_sig_str.len() != SIGV4_SIGNATURE_LEN {
1464 panic!("Expected signature is not {SIGV4_SIGNATURE_LEN} bytes long");
1465 }
1466
1467 let mut request_sig: [u8; SIGV4_SIGNATURE_LEN] = [0; SIGV4_SIGNATURE_LEN];
1468 let mut expected_sig: [u8; SIGV4_SIGNATURE_LEN] = [0; SIGV4_SIGNATURE_LEN];
1469
1470 request_sig.copy_from_slice(request_sig_str.as_bytes());
1471 expected_sig.copy_from_slice(expected_sig_str.as_bytes());
1472
1473 // Cryptographically sensitive: we must compare every byte and not short-circuit this, or the
1474 // we can be vulnerable to timing attacks. `ct_eq` comes from the `Subtle::ConstantTimeEq` trait.
1475 if expected_sig.ct_eq(&request_sig).unwrap_u8() == 0 {
1476 Err(SignatureError::InvalidSignature {
1477 message: format!("Expected {} instead of {}", expected_sig_str, request_sig_str),
1478 })
1479 } else {
1480 Ok(())
1481 }
1482}
1483
1484/// Verify a SigV4 request. This verifies that the request timestamp is not beyond the
1485/// allowed timestamp mismatch against the current time, and that the request signature matches our expected
1486/// signature.
1487///
1488/// # Errors
1489/// If the request timestamp is outside the allowed timestamp mismatch, a `SignatureError::TimestampOutOfRange` error
1490/// is returned.
1491///
1492/// If the request signature does not match the expected signature, a `SignatureError::InvalidSignature` error is
1493/// returned.
1494///
1495/// # Panics
1496/// If the expected signature string is not 64 bytes long, this function will panic. This should never happen.
1497pub fn sigv4_verify<A1, A2>(
1498 req: &Request,
1499 signing_key: &SigningKey,
1500 allowed_mismatch: Option<Duration>,
1501 region: A1,
1502 service: A2,
1503) -> Result<(), SignatureError>
1504where
1505 A1: AsRef<str>,
1506 A2: AsRef<str>,
1507{
1508 sigv4_verify_at(req, signing_key, &Utc::now(), allowed_mismatch, region, service)
1509}
1510
1511/// Indicates whether the specified byte is RFC3986 unreserved -- i.e., can be represented without being
1512/// percent-encoded, e.g. `'?'` -> `'%3F'`.
1513///
1514/// # Example
1515/// ```rust
1516/// # use scratchstack_aws_signature::is_rfc3986_unreserved;
1517/// assert!(is_rfc3986_unreserved(b'a'));
1518/// assert!(!is_rfc3986_unreserved(b' '));
1519/// ```
1520pub const fn is_rfc3986_unreserved(c: u8) -> bool {
1521 c.is_ascii_alphanumeric() || c == b'-' || c == b'.' || c == b'_' || c == b'~'
1522}
1523
1524/// Normalize the path component according to RFC 3986. This performs the following operations:
1525/// * Alpha, digit, and the symbols `-`, `.`, `_`, and `~` (unreserved characters) are left alone.
1526/// * Characters outside this range are percent-encoded.
1527/// * Percent-encoded values are upper-cased (`%2a` becomes `%2A`)
1528/// * Percent-encoded values in the unreserved space (`%2D`, `%2E`, `%30`-`%39`, `%5F`, `%41`-`%5A`, `%61`-`%7A`,
1529/// `%7E`) are converted to normal characters.
1530/// * Plus-encoded spaces (`+`) are converted to `%20`.
1531///
1532/// # Errors
1533/// If a percent encoding is incomplete or is invalid, `SignatureError::InvalidURIPath` is returned.
1534///
1535/// # Example
1536/// ```rust
1537/// # use scratchstack_aws_signature::normalize_uri_path_component;
1538/// assert_eq!(normalize_uri_path_component("a b").unwrap(), "a%20b");
1539/// assert_eq!(normalize_uri_path_component("%61b").unwrap(), "ab");
1540/// ```
1541pub fn normalize_uri_path_component(path_component: &str) -> Result<String, SignatureError> {
1542 let path_component = path_component.as_bytes();
1543 let mut i = 0;
1544 let result = &mut Vec::<u8>::new();
1545
1546 while i < path_component.len() {
1547 let c = path_component[i];
1548
1549 if is_rfc3986_unreserved(c) {
1550 result.push(c);
1551 i += 1;
1552 } else if c == b'%' {
1553 if i + 2 >= path_component.len() {
1554 // % encoding would go beyond end of string; return an error.
1555 return Err(SignatureError::InvalidURIPath {
1556 message: "Incomplete hex encoding".to_string(),
1557 });
1558 }
1559
1560 let hex_digits = &path_component[i + 1..i + 3];
1561 match hex::decode(hex_digits) {
1562 Ok(value) => {
1563 assert_eq!(value.len(), 1);
1564 let c = value[0];
1565
1566 if is_rfc3986_unreserved(c) {
1567 result.push(c);
1568 } else {
1569 // Rewrite the hex-escape so it's always upper-cased.
1570 write!(result, "%{:02X}", c)?;
1571 }
1572 i += 3;
1573 }
1574 Err(_) => {
1575 return Err(SignatureError::InvalidURIPath {
1576 message: format!("Invalid hex encoding: {:?}", hex_digits),
1577 })
1578 }
1579 }
1580 } else if c == b'+' {
1581 // Plus-encoded space. Convert this to %20.
1582 result.write_all(b"%20")?;
1583 i += 1;
1584 } else {
1585 // Character should have been encoded.
1586 write!(result, "%{:02X}", c)?;
1587 i += 1;
1588 }
1589 }
1590
1591 Ok(from_utf8(result.as_slice()).unwrap().to_string())
1592}
1593
1594/// Normalizes the specified URI path, removing redundant slashes and relative path components.
1595///
1596/// # Errors
1597/// If the path is not absolute, a `SignatureError::InvalidURIPath` error is returned.
1598///
1599/// If any component of the path cannot be normalized per [`normalize_uri_path_component`], a
1600/// `SignatureError::InvalidURIPath` error is returned.
1601///
1602/// # Example
1603/// ```rust
1604/// # use scratchstack_aws_signature::canonicalize_uri_path;
1605/// assert_eq!(canonicalize_uri_path("/a//b/../c").unwrap(), "/a/c");
1606/// assert_eq!(canonicalize_uri_path("/a/b/./c/../").unwrap(), "/a/b/");
1607/// ```
1608pub fn canonicalize_uri_path(uri_path: &str) -> Result<String, SignatureError> {
1609 // Special case: empty path is converted to '/'; also short-circuit the usual '/' path here.
1610 if uri_path.is_empty() || uri_path == "/" {
1611 return Ok("/".to_string());
1612 }
1613
1614 // All other paths must be abolute.
1615 if !uri_path.starts_with('/') {
1616 return Err(SignatureError::InvalidURIPath {
1617 message: format!("Path is not absolute: {}", uri_path),
1618 });
1619 }
1620
1621 // Replace double slashes; this makes it easier to handle slashes at the end.
1622 let uri_path = MULTISLASH.replace_all(uri_path, "/");
1623
1624 // Examine each path component for relative directories.
1625 let mut components: Vec<String> = uri_path.split('/').map(|s| s.to_string()).collect();
1626 let mut i = 1; // Ignore the leading "/"
1627 while i < components.len() {
1628 let component = normalize_uri_path_component(&components[i])?;
1629
1630 if component == "." {
1631 // Relative path: current directory; remove this.
1632 components.remove(i);
1633
1634 // Don't increment i; with the deletion, we're now pointing to the next element in the path.
1635 } else if component == ".." {
1636 // Relative path: parent directory. Remove this and the previous component.
1637
1638 if i <= 1 {
1639 // This isn't allowed at the beginning!
1640 return Err(SignatureError::InvalidURIPath {
1641 message: format!("Relative path entry '..' navigates above root: {}", uri_path),
1642 });
1643 }
1644
1645 components.remove(i - 1);
1646 components.remove(i - 1);
1647
1648 // Since we've deleted two components, we need to back up one to examine what's now the next component.
1649 i -= 1;
1650 } else {
1651 // Leave it alone; proceed to the next component.
1652 components[i] = component;
1653 i += 1;
1654 }
1655 }
1656
1657 assert!(!components.is_empty());
1658 match components.len() {
1659 1 => Ok("/".to_string()),
1660 _ => Ok(components.join("/")),
1661 }
1662}
1663
1664/// Normalize the query parameters by normalizing the keys and values of each parameter and return a `HashMap` mapping
1665/// each key to a _vector_ of values (since it is valid for a query parameters to appear multiple times).
1666///
1667/// The order of the values matches the order that they appeared in the query string -- this is important for SigV4
1668/// validation.
1669///
1670/// Keys and values are normalized according to the rules of [`normalize_uri_path_component`].
1671///
1672/// # Errors
1673/// If any key or value cannot be normalized as a URI path component, a `SignatureError::InvalidURIPath` error is
1674/// returned.
1675///
1676/// # Example
1677/// ```rust
1678/// # use scratchstack_aws_signature::normalize_query_parameters;
1679/// let result = normalize_query_parameters("a=1&b=2&a=3&c=4").unwrap();
1680///
1681/// assert_eq!(result.get("a").unwrap(), &vec!["1", "3"]);
1682/// assert_eq!(result.get("b").unwrap(), &vec!["2"]);
1683/// assert_eq!(result.get("c").unwrap(), &vec!["4"]);
1684/// ```
1685pub fn normalize_query_parameters(query_string: &str) -> Result<HashMap<String, Vec<String>>, SignatureError> {
1686 if query_string.is_empty() {
1687 return Ok(HashMap::new());
1688 }
1689
1690 // Split the query string into parameters on '&' boundaries.
1691 let components = query_string.split('&');
1692 let mut result = HashMap::<String, Vec<String>>::new();
1693
1694 for component in components {
1695 if component.is_empty() {
1696 // Empty component; skip it.
1697 continue;
1698 }
1699
1700 // Split the parameter into key and value portions on the '='
1701 let parts: Vec<&str> = component.splitn(2, '=').collect();
1702 let key = parts[0];
1703 let value = if parts.len() > 1 {
1704 parts[1]
1705 } else {
1706 ""
1707 };
1708
1709 // Normalize the key and value.
1710 let norm_key = normalize_uri_path_component(key)?;
1711 let norm_value = normalize_uri_path_component(value)?;
1712
1713 // If we already have a value for this key, append to it; otherwise, create a new vector containing the value.
1714 if let Some(result_value) = result.get_mut(&norm_key) {
1715 result_value.push(norm_value);
1716 } else {
1717 result.insert(norm_key, vec![norm_value]);
1718 }
1719 }
1720
1721 Ok(result)
1722}
1723
1724/// Split `Authorization`` header parameters from key=value parts into a HashMap.
1725///
1726/// # Errors
1727/// If the header is missing an '=' in what should be a `key=value` pair, `SignatureError::MalformedSignature` error
1728/// is returned.
1729///
1730/// If the header contains duplicate keys, a `SignatureError::MalformedSignature` error is returned.
1731pub(crate) fn split_authorization_header_parameters(
1732 parameters: &str,
1733) -> Result<HashMap<String, String>, SignatureError> {
1734 trace!("split_authorization_header_parameters: parameters={:?}", parameters);
1735 let mut result = HashMap::<String, String>::new();
1736 for parameter in parameters.split(',') {
1737 let parts: Vec<&str> = parameter.splitn(2, '=').collect();
1738 if parts.len() != 2 {
1739 return Err(SignatureError::MalformedSignature {
1740 message: "invalid Authorization header: missing '='".to_string(),
1741 });
1742 }
1743
1744 let key = parts[0].trim_start().to_string();
1745 let value = parts[1].trim_end().to_string();
1746
1747 if result.contains_key(&key) {
1748 return Err(SignatureError::MalformedSignature {
1749 message: format!("invalid Authorization header: duplicate field {}", key),
1750 });
1751 }
1752
1753 result.insert(key, value);
1754 }
1755
1756 Ok(result)
1757}