scratchstack_aws_signature/lib.rs
1//! The `aws_sig_verify` crate provides AWS SigV4 _verification_ routines. This *is not* the library you want if you
2//! just want to call AWS services or other services that use AWS SigV4 signatures.
3//! [Rusoto](https://github.com/rusoto/rusoto) already has a library,
4//! [rusoto_signature](https://docs.rs/rusoto_signature/), that provides this functionality.
5//!
6//! If you are attempting to perform AWS SigV4 verification using AWS-vended credentials, this library also
7//! ___will not work for you___. You need the caller's secret key (or a derivative), and AWS does not allow this for
8//! obvious reasons. Instead, you should be using [API Gateway with IAM
9//! authentication](https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html).
10//!
11//! On the other hand, if you have your own ecosystem of AWS-like credentials and are developing mock-AWS services or
12//! just really like AWS SigV4 but can't run within AWS, this library _might_ be for you.
13//!
14//! # Workflow
15//! This assumes you have a complete HTTP request (headers _and_ body) already. As a result, you may not be able to
16//! implement this as a middleware layer for a web server—those typically only provide the headers. Having the body is
17//! required for almost all modes of AWS SigV4.
18//!
19//! The typical workflow is:
20//! 1. Convert an HTTP `Request` object into a scratchstack `Request` object.
21//! 2. Create a `GetSigningKeyRequest` from this `Request`.
22//! 3. Call your service to obtain the principal and signing key for this request.
23//! 4. Verify the request using `sigv4_verify` or `sigv4_verify_at`.
24//!
25//! ## Example
26//! ```rust
27//! use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
28//! use http::Request;
29//! use scratchstack_aws_principal::PrincipalActor;
30//! use scratchstack_aws_signature::{
31//! Request as SigRequest, SigningKey, SigningKeyKind, get_signing_key_fn, sigv4_verify_at,
32//! };
33//! use std::error::Error;
34//! use tower::Service;
35//!
36//! const ACCESS_KEY: &str = "AKIAIOSFODNN7EXAMPLE";
37//! const SECRET_KEY: &str = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
38//! const ACCOUNT_ID: &str = "123456789012";
39//! const PARTITION: &str = "aws";
40//! const PATH: &str = "/engineering/";
41//! const REGION: &str = "us-east-1";
42//! const SERVICE: &str = "example";
43//! const USER_NAME: &str = "user";
44//! const USER_ID: &str = "AIDAQXZEAEXAMPLEUSER";
45//!
46//! // The date for which the signature calculation was made.
47//! #[allow(deprecated)]
48//! const TEST_TIMESTAMP: DateTime<Utc> = DateTime::<Utc>::from_naive_utc_and_offset(
49//! NaiveDateTime::new(
50//! NaiveDate::from_ymd(2021, 1, 1),
51//! NaiveTime::from_hms(0, 0, 0)),
52//! Utc
53//! );
54//!
55//! // This is a mock function that returns a static secret key converted into the requested type
56//! // of signing key. For actual use, you would call out to a database or other service to obtain
57//! // a signing key.
58//! async fn get_signing_key(
59//! kind: SigningKeyKind,
60//! access_key: String,
61//! _session_token: Option<String>,
62//! req_date: NaiveDate,
63//! region: String,
64//! service: String)
65//! -> Result<(PrincipalActor, SigningKey), Box<(dyn Error + Send + Sync)>> {
66//! assert!(access_key == ACCESS_KEY);
67//! let signing_key = SigningKey {
68//! kind: SigningKeyKind::KSecret,
69//! key: SECRET_KEY.as_bytes().to_vec()
70//! };
71//! let signing_key = signing_key.try_derive(kind, &req_date, ®ion, &service)?;
72//! let principal = PrincipalActor::user(PARTITION, ACCOUNT_ID, PATH, USER_NAME, USER_ID)?;
73//! Ok((principal, signing_key))
74//! }
75//!
76//! # tokio_test::block_on(async {
77//! // Normally this would come from your web framework.
78//! let req = Request::get("https://example.com")
79//! .header("Host", "example.com")
80//! .header("X-Amz-Date", "20210101T000000Z")
81//! .header("Authorization", "AWS4-HMAC-SHA256 \
82//! Credential=AKIAIOSFODNN7EXAMPLE/20210101/us-east-1/example/aws4_request, \
83//! SignedHeaders=host;x-amz-date, \
84//! Signature=3ea4679d2ecf5a8293e1fb10298c82988f024a2e937e9b37876b34bb119da0bc")
85//! .body(())
86//! .unwrap();
87//!
88//! // Extract the header parts; the body is not used in this example.
89//! let parts = req.into_parts().0;
90//!
91//! // Convert this into a scratchstack Request.
92//! let sig_req = SigRequest::from_http_request_parts(&parts, None);
93//!
94//! // Create a request to obtain a signing key. Here, we're asking for a SigningKeyKing::KSigning
95//! // key type which is the most secure; if the key is leaked, an attacker can only sign requests
96//! // for the given date, region, and service.
97//! let get_signing_key_req = sig_req.to_get_signing_key_request(
98//! SigningKeyKind::KSigning, REGION, SERVICE).unwrap();
99//! let (_principal, signing_key) = get_signing_key_fn(get_signing_key)
100//! .call(get_signing_key_req).await.unwrap();
101//!
102//! // Normally you would use `sigv4_verify` instead of `sigv4_verify_at`.
103//! // We're pinning this to a specific date for testing purposes.
104//! sigv4_verify_at(&sig_req, &signing_key, &TEST_TIMESTAMP, None, REGION, SERVICE).unwrap();
105//! # });
106//! ```
107#![warn(missing_docs)]
108#![deny(rustdoc::broken_intra_doc_links)]
109#![warn(rustdoc::missing_crate_level_docs)]
110
111mod chronoutil;
112mod hmac;
113mod signature;
114pub use crate::signature::{
115 canonicalize_uri_path, get_signing_key_fn, is_rfc3986_unreserved, normalize_query_parameters,
116 normalize_uri_path_component, sigv4_get_expected_signature, sigv4_verify, sigv4_verify_at, GetSigningKey,
117 GetSigningKeyFn, GetSigningKeyRequest, Request, SignatureError, SigningKey, SigningKeyKind,
118};
119
120#[cfg(test)]
121mod unittest;
122
123#[cfg(test)]
124mod aws4;