1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
56use aws_smithy_runtime_api::client::identity::Identity;
7use bytes::{BufMut, BytesMut};
8use crypto_bigint::{CheckedAdd, CheckedSub, Encoding, U256};
9use once_cell::sync::Lazy;
10use p256::ecdsa::signature::Signer;
11use p256::ecdsa::{Signature, SigningKey};
12use std::io::Write;
13use std::time::SystemTime;
14use zeroize::Zeroizing;
1516const ALGORITHM: &[u8] = b"AWS4-ECDSA-P256-SHA256";
17static BIG_N_MINUS_2: Lazy<U256> = Lazy::new(|| {
18// The N value from section 3.2.1.3 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf
19 // Used as the N value for the algorithm described in section A.2.2 of https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
20 // *(Basically a prime number blessed by the NSA for use in p256)*
21const ORDER: U256 =
22 U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
23 ORDER.checked_sub(&U256::from(2u32)).unwrap()
24});
2526/// Calculates a Sigv4a signature
27pub fn calculate_signature(signing_key: impl AsRef<[u8]>, string_to_sign: &[u8]) -> String {
28let signing_key = SigningKey::from_bytes(signing_key.as_ref()).unwrap();
29let signature: Signature = signing_key.sign(string_to_sign);
30// This conversion sucks but we have to do it afaict. Because we also use
31 // the HMAC crate, we have to use a compatible (and therefore older) version
32 // of the p256 crate. That older version requires us to convert between
33 // signature types instead of using DER-encoded signatures directly.
34let signature = signature.to_der();
35 hex::encode(signature.as_ref())
36}
3738/// Generates a signing key for Sigv4a signing.
39pub fn generate_signing_key(access_key: &str, secret_access_key: &str) -> impl AsRef<[u8]> {
40// Capacity is the secret access key length plus the length of "AWS4A"
41let mut input_key = Zeroizing::new(Vec::with_capacity(secret_access_key.len() + 5));
42write!(input_key, "AWS4A{secret_access_key}").unwrap();
4344// Capacity is the access key length plus the counter byte
45let mut kdf_context = Zeroizing::new(Vec::with_capacity(access_key.len() + 1));
46let mut counter = Zeroizing::new(1u8);
47let key = loop {
48write!(kdf_context, "{access_key}").unwrap();
49 kdf_context.push(*counter);
5051let mut fis = ALGORITHM.to_vec();
52 fis.push(0);
53 fis.append(&mut kdf_context);
54 fis.put_i32(256);
5556let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, &input_key);
5758let mut buf = BytesMut::new();
59 buf.put_i32(1);
60 buf.put_slice(&fis);
61let tag = ring::hmac::sign(&key, &buf);
62let tag = &tag.as_ref()[0..32];
6364let k0 = U256::from_be_bytes(tag.try_into().expect("convert to [u8; 32]"));
6566// It would be more secure for this to be a constant time comparison, but because this
67 // is for client usage, that's not as big a deal.
68if k0 <= *BIG_N_MINUS_2 {
69let pk = k0
70 .checked_add(&U256::ONE)
71 .expect("k0 is always less than U256::MAX");
72let d = Zeroizing::new(pk.to_be_bytes());
73break SigningKey::from_bytes(d.as_ref()).unwrap();
74 }
7576*counter = counter
77 .checked_add(1)
78 .expect("counter will never get to 255");
79 };
8081 key.to_bytes()
82}
8384/// Parameters to use when signing.
85#[derive(Debug)]
86#[non_exhaustive]
87pub struct SigningParams<'a, S> {
88/// The identity to use when signing a request
89pub(crate) identity: &'a Identity,
9091/// Region set to sign for.
92pub(crate) region_set: &'a str,
93/// Service Name to sign for.
94 ///
95 /// NOTE: Endpoint resolution rules may specify a name that differs from the typical service name.
96pub(crate) name: &'a str,
97/// Timestamp to use in the signature (should be `SystemTime::now()` unless testing).
98pub(crate) time: SystemTime,
99100/// Additional signing settings. These differ between HTTP and Event Stream.
101pub(crate) settings: S,
102}
103104pub(crate) const ECDSA_256: &str = "AWS4-ECDSA-P256-SHA256";
105106impl<'a, S> SigningParams<'a, S> {
107/// Returns the region that will be used to sign SigV4a requests
108pub fn region_set(&self) -> &str {
109self.region_set
110 }
111112/// Returns the service name that will be used to sign requests
113pub fn name(&self) -> &str {
114self.name
115 }
116117/// Return the name of the algorithm used to sign requests
118pub fn algorithm(&self) -> &'static str {
119 ECDSA_256
120 }
121}
122123impl<'a, S: Default> SigningParams<'a, S> {
124/// Returns a builder that can create new `SigningParams`.
125pub fn builder() -> signing_params::Builder<'a, S> {
126 Default::default()
127 }
128}
129130/// Builder and error for creating [`SigningParams`]
131pub mod signing_params {
132use super::SigningParams;
133use aws_smithy_runtime_api::client::identity::Identity;
134use std::error::Error;
135use std::fmt;
136use std::time::SystemTime;
137138/// [`SigningParams`] builder error
139#[derive(Debug)]
140pub struct BuildError {
141 reason: &'static str,
142 }
143impl BuildError {
144fn new(reason: &'static str) -> Self {
145Self { reason }
146 }
147 }
148149impl fmt::Display for BuildError {
150fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151write!(f, "{}", self.reason)
152 }
153 }
154155impl Error for BuildError {}
156157/// Builder that can create new [`SigningParams`]
158#[derive(Debug, Default)]
159pub struct Builder<'a, S> {
160 identity: Option<&'a Identity>,
161 region_set: Option<&'a str>,
162 name: Option<&'a str>,
163 time: Option<SystemTime>,
164 settings: Option<S>,
165 }
166167impl<'a, S> Builder<'a, S> {
168builder_methods!(
169 set_identity,
170 identity,
171&'a Identity,
172"Sets the identity (required)",
173 set_region_set,
174 region_set,
175&'a str,
176"Sets the region set (required)",
177 set_name,
178 name,
179&'a str,
180"Sets the name (required)",
181 set_time,
182 time,
183 SystemTime,
184"Sets the time to be used in the signature (required)",
185 set_settings,
186 settings,
187 S,
188"Sets additional signing settings (required)"
189);
190191/// Builds an instance of [`SigningParams`]. Will yield a [`BuildError`] if
192 /// a required argument was not given.
193pub fn build(self) -> Result<SigningParams<'a, S>, BuildError> {
194Ok(SigningParams {
195 identity: self
196.identity
197 .ok_or_else(|| BuildError::new("identity is required"))?,
198 region_set: self
199.region_set
200 .ok_or_else(|| BuildError::new("region_set is required"))?,
201 name: self
202.name
203 .ok_or_else(|| BuildError::new("name is required"))?,
204 time: self
205.time
206 .ok_or_else(|| BuildError::new("time is required"))?,
207 settings: self
208.settings
209 .ok_or_else(|| BuildError::new("settings are required"))?,
210 })
211 }
212 }
213}