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, &region, &service)),
401            SigningKeyKind::KService => self.try_to_kservice_key(req_date, &region, &service),
402            SigningKeyKind::KRegion => self.try_to_kregion_key(req_date, &region),
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, &region);
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, &region, &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, &region, &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, &region, &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, &region) {
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(&region, &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()?), &region, &service);
1407    let string_to_sign = req.get_string_to_sign(&region, &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, &region, &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}