1use aws_smithy_runtime_api::client::identity::Identity;
7use bytes::{BufMut, BytesMut};
8use crypto_bigint::{CheckedAdd, CheckedSub, Encoding, U256};
9use hmac::{digest::FixedOutput, Hmac, KeyInit, Mac};
10use p256::ecdsa::signature::Signer;
11use p256::ecdsa::{DerSignature, SigningKey};
12use sha2::Sha256;
13use std::io::Write;
14use std::sync::LazyLock;
15use std::time::SystemTime;
16use zeroize::Zeroizing;
17
18const ALGORITHM: &[u8] = b"AWS4-ECDSA-P256-SHA256";
19static BIG_N_MINUS_2: LazyLock<U256> = LazyLock::new(|| {
20 const ORDER: U256 =
24 U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
25 ORDER.checked_sub(&U256::from(2u32)).unwrap()
26});
27
28pub fn calculate_signature(signing_key: impl AsRef<[u8]>, string_to_sign: &[u8]) -> String {
30 let signing_key = SigningKey::from_slice(signing_key.as_ref()).unwrap();
31 let signature: DerSignature = signing_key.sign(string_to_sign);
32 hex::encode(signature.as_bytes())
33}
34
35pub fn generate_signing_key(access_key: &str, secret_access_key: &str) -> impl AsRef<[u8]> {
37 let mut input_key = Zeroizing::new(Vec::with_capacity(secret_access_key.len() + 5));
39 write!(input_key, "AWS4A{secret_access_key}").unwrap();
40
41 let mut kdf_context = Zeroizing::new(Vec::with_capacity(access_key.len() + 1));
43 let mut counter = Zeroizing::new(1u8);
44 let key = loop {
45 write!(kdf_context, "{access_key}").unwrap();
46 kdf_context.push(*counter);
47
48 let mut fis = ALGORITHM.to_vec();
49 fis.push(0);
50 fis.append(&mut kdf_context);
51 fis.put_i32(256);
52
53 let mut mac =
54 Hmac::<Sha256>::new_from_slice(&input_key).expect("HMAC can take key of any size");
55
56 let mut buf = BytesMut::new();
57 buf.put_i32(1);
58 buf.put_slice(&fis);
59 mac.update(&buf);
60 let k0 = U256::from_be_bytes(mac.finalize_fixed().into());
61
62 if k0 <= *BIG_N_MINUS_2 {
65 let pk = k0
66 .checked_add(&U256::ONE)
67 .expect("k0 is always less than U256::MAX");
68 let d = Zeroizing::new(pk.to_be_bytes());
69 break SigningKey::from_slice(d.as_ref()).unwrap();
70 }
71
72 *counter = counter
73 .checked_add(1)
74 .expect("counter will never get to 255");
75 };
76
77 key.to_bytes()
78}
79
80#[derive(Debug)]
82#[non_exhaustive]
83pub struct SigningParams<'a, S> {
84 pub(crate) identity: &'a Identity,
86
87 pub(crate) region_set: &'a str,
89 pub(crate) name: &'a str,
93 pub(crate) time: SystemTime,
95
96 pub(crate) settings: S,
98}
99
100pub(crate) const ECDSA_256: &str = "AWS4-ECDSA-P256-SHA256";
101
102impl<S> SigningParams<'_, S> {
103 pub fn region_set(&self) -> &str {
105 self.region_set
106 }
107
108 pub fn name(&self) -> &str {
110 self.name
111 }
112
113 pub fn algorithm(&self) -> &'static str {
115 ECDSA_256
116 }
117}
118
119impl<'a, S: Default> SigningParams<'a, S> {
120 pub fn builder() -> signing_params::Builder<'a, S> {
122 Default::default()
123 }
124}
125
126pub mod signing_params {
128 use super::SigningParams;
129 use aws_smithy_runtime_api::client::identity::Identity;
130 use std::error::Error;
131 use std::fmt;
132 use std::time::SystemTime;
133
134 #[derive(Debug)]
136 pub struct BuildError {
137 reason: &'static str,
138 }
139 impl BuildError {
140 fn new(reason: &'static str) -> Self {
141 Self { reason }
142 }
143 }
144
145 impl fmt::Display for BuildError {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}", self.reason)
148 }
149 }
150
151 impl Error for BuildError {}
152
153 #[derive(Debug, Default)]
155 pub struct Builder<'a, S> {
156 identity: Option<&'a Identity>,
157 region_set: Option<&'a str>,
158 name: Option<&'a str>,
159 time: Option<SystemTime>,
160 settings: Option<S>,
161 }
162
163 impl<'a, S> Builder<'a, S> {
164 builder_methods!(
165 set_identity,
166 identity,
167 &'a Identity,
168 "Sets the identity (required)",
169 set_region_set,
170 region_set,
171 &'a str,
172 "Sets the region set (required)",
173 set_name,
174 name,
175 &'a str,
176 "Sets the name (required)",
177 set_time,
178 time,
179 SystemTime,
180 "Sets the time to be used in the signature (required)",
181 set_settings,
182 settings,
183 S,
184 "Sets additional signing settings (required)"
185 );
186
187 pub fn build(self) -> Result<SigningParams<'a, S>, BuildError> {
190 Ok(SigningParams {
191 identity: self
192 .identity
193 .ok_or_else(|| BuildError::new("identity is required"))?,
194 region_set: self
195 .region_set
196 .ok_or_else(|| BuildError::new("region_set is required"))?,
197 name: self
198 .name
199 .ok_or_else(|| BuildError::new("name is required"))?,
200 time: self
201 .time
202 .ok_or_else(|| BuildError::new("time is required"))?,
203 settings: self
204 .settings
205 .ok_or_else(|| BuildError::new("settings are required"))?,
206 })
207 }
208 }
209}