scratchstack_aws_signature/lib.rs
1//! AWS API request signatures verification routines.
2//!
3//! The `scratchstack_aws_signature` crate provides
4//! AWS [SigV4](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
5//! _validation_ routines. This *is not* the library you want if you just want to call AWS services
6//! or other services that use AWS SigV4 signatures. [Rusoto](https://github.com/rusoto/rusoto)
7//! already has a library, [rusoto_signature](https://docs.rs/rusoto_signature/), that provides
8//! this functionality.
9//!
10//! If you are attempting to perform AWS SigV4 verification using AWS-vended credentials, this
11//! library also ___will not work for you___. You need the caller's secret key (or a derivative),
12//! and AWS does not allow this for obvious reasons. Instead, you should be using [API Gateway with
13//! IAM authentication](https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html).
14//!
15//! On the other hand, if you have your own ecosystem of AWS-like credentials and are developing
16//! mock-AWS services or other services that need to use AWS SigV4, this _might_ be the right
17//! crate for you.
18//!
19//! Users migrating from version 0.10 to 0.11 should consult the [migration guide][migration].
20//!
21//! # Feature flags
22//! This crate has one feature flag:
23//! * `unstable`: Allows access to unstable APIs (structs, traits, functions) such as
24//! [`canonical::normalize_uri_path_component`]. These APIs are not needed for normal use of
25//! this crate; they are provided for others exploring AWS SigV4 internals.
26//!
27//! # Workflow
28//! This assumes you have a complete HTTP request (headers _and_ body) already. As a result, you may not be able to
29//! implement this as a middleware layer for a web server—those typically only provide the headers. Having the body is
30//! required for almost all modes of AWS SigV4.
31//!
32//! The typical workflow is:
33//! 1. Convert an HTTP `Request` object into a scratchstack `Request` object.
34//! 2. Create a `GetSigningKeyRequest` from this `Request`.
35//! 3. Call your service to obtain the principal and signing key for this request.
36//! 4. Verify the request using `sigv4_verify` or `sigv4_verify_at`.
37//!
38//! ## Example
39//! ```rust
40//! use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
41//! use http::Request;
42//! use scratchstack_aws_principal::{Principal, User};
43//! use scratchstack_aws_signature::{
44//! service_for_signing_key_fn, sigv4_validate_request, GetSigningKeyRequest,
45//! GetSigningKeyResponse, KSecretKey, SignatureOptions, NO_ADDITIONAL_SIGNED_HEADERS,
46//! };
47//! use std::str::FromStr;
48//! use tower::{BoxError, Service};
49//!
50//! const ACCESS_KEY: &str = "AKIAIOSFODNN7EXAMPLE";
51//! const SECRET_KEY: &str = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
52//! const ACCOUNT_ID: &str = "123456789012";
53//! const PARTITION: &str = "aws";
54//! const PATH: &str = "/engineering/";
55//! const REGION: &str = "us-east-1";
56//! const SERVICE: &str = "example";
57//! const USER_NAME: &str = "user";
58//! const USER_ID: &str = "AIDAQXZEAEXAMPLEUSER";
59//!
60//! // The date for which the signature calculation was made.
61//! #[allow(deprecated)]
62//! const TEST_TIMESTAMP: DateTime<Utc> = DateTime::<Utc>::from_naive_utc_and_offset(
63//! NaiveDateTime::new(
64//! NaiveDate::from_ymd(2021, 1, 1),
65//! NaiveTime::from_hms(0, 0, 0)),
66//! Utc
67//! );
68//!
69//! // This is a mock function that returns a static secret key converted into the requested type
70//! // of signing key. For actual use, you would call out to a database or other service to obtain
71//! // a signing key.
72//! async fn get_signing_key(
73//! request: GetSigningKeyRequest)
74//! -> Result<GetSigningKeyResponse, BoxError> {
75//! assert_eq!(request.access_key(), ACCESS_KEY);
76//! assert_eq!(request.region(), REGION);
77//! assert_eq!(request.service(), SERVICE);
78//! let user = User::new(PARTITION, ACCOUNT_ID, PATH, USER_NAME)?;
79//! let secret_key = KSecretKey::from_str(SECRET_KEY).unwrap();
80//! let signing_key = secret_key.to_ksigning(request.request_date(), REGION, SERVICE);
81//! Ok(GetSigningKeyResponse::builder()
82//! .principal(user)
83//! .signing_key(signing_key)
84//! .build()?)
85//! }
86//!
87//! // Wrap `get_signing_key` in a `tower::Service`.
88//! let mut get_signing_key_service = service_for_signing_key_fn(get_signing_key);
89//!
90//! // Normally this would come from your web framework.
91//! let req = Request::get("https://example.com")
92//! .header("Host", "example.com")
93//! .header("X-Amz-Date", "20210101T000000Z")
94//! .header("Authorization", "AWS4-HMAC-SHA256 \
95//! Credential=AKIAIOSFODNN7EXAMPLE/20210101/us-east-1/example/aws4_request, \
96//! SignedHeaders=host;x-amz-date, \
97//! Signature=3ea4679d2ecf5a8293e1fb10298c82988f024a2e937e9b37876b34bb119da0bc")
98//! .body(())
99//! .unwrap();
100//!
101//! // The headers that _must_ be signed (beyond the default SigV4 headers) for this service.
102//! // In this case, we're not requiring any additional headers.
103//! let signed_headers = NO_ADDITIONAL_SIGNED_HEADERS;
104//!
105//! // Signature options for the request. Defaults are typically used, except for S3.
106//! let signature_options = SignatureOptions::default();
107//!
108//! # tokio_test::block_on(async {
109//! // Validate the request.
110//! let (parts, body, auth) = sigv4_validate_request(
111//! req, ®ION, &SERVICE, &mut get_signing_key_service, TEST_TIMESTAMP, &signed_headers,
112//! signature_options).await.unwrap();
113//!
114//! // The principal we expect to be associated with the request.
115//! let expected_principal: Principal = User::new(PARTITION, ACCOUNT_ID, PATH, USER_NAME)
116//! .unwrap()
117//! .into();
118//! assert_eq!(auth.principal(), &expected_principal);
119//! # });
120//! ```
121#![warn(missing_docs)]
122#![deny(rustdoc::broken_intra_doc_links)]
123#![warn(rustdoc::missing_crate_level_docs)]
124#![cfg_attr(doc, feature(doc_cfg))]
125
126mod chronoutil;
127mod crypto;
128mod error;
129mod signature;
130mod signing_key;
131
132pub mod auth;
133pub mod canonical;
134
135pub use {
136 error::*,
137 signature::*,
138 signing_key::*,
139 scratchstack_errors as errors,
140 scratchstack_aws_principal as principal,
141};
142
143#[doc(inline)]
144pub use canonical::{
145 ConstSignedHeaderRequirements, SignedHeaderRequirements, SliceSignedHeaderRequirements,
146 VecSignedHeaderRequirements, NO_ADDITIONAL_SIGNED_HEADERS,
147};
148
149#[cfg(doc)]
150pub mod migration;
151
152#[cfg(test)]
153mod aws4;