1use 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;
15
16const ALGORITHM: &[u8] = b"AWS4-ECDSA-P256-SHA256";
17static BIG_N_MINUS_2: Lazy<U256> = Lazy::new(|| {
18 const ORDER: U256 =
22 U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
23 ORDER.checked_sub(&U256::from(2u32)).unwrap()
24});
25
26pub fn calculate_signature(signing_key: impl AsRef<[u8]>, string_to_sign: &[u8]) -> String {
28 let signing_key = SigningKey::from_bytes(signing_key.as_ref()).unwrap();
29 let signature: Signature = signing_key.sign(string_to_sign);
30 let signature = signature.to_der();
35 hex::encode(signature.as_ref())
36}
37
38pub fn generate_signing_key(access_key: &str, secret_access_key: &str) -> impl AsRef<[u8]> {
40 let mut input_key = Zeroizing::new(Vec::with_capacity(secret_access_key.len() + 5));
42 write!(input_key, "AWS4A{secret_access_key}").unwrap();
43
44 let mut kdf_context = Zeroizing::new(Vec::with_capacity(access_key.len() + 1));
46 let mut counter = Zeroizing::new(1u8);
47 let key = loop {
48 write!(kdf_context, "{access_key}").unwrap();
49 kdf_context.push(*counter);
50
51 let mut fis = ALGORITHM.to_vec();
52 fis.push(0);
53 fis.append(&mut kdf_context);
54 fis.put_i32(256);
55
56 let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, &input_key);
57
58 let mut buf = BytesMut::new();
59 buf.put_i32(1);
60 buf.put_slice(&fis);
61 let tag = ring::hmac::sign(&key, &buf);
62 let tag = &tag.as_ref()[0..32];
63
64 let k0 = U256::from_be_bytes(tag.try_into().expect("convert to [u8; 32]"));
65
66 if k0 <= *BIG_N_MINUS_2 {
69 let pk = k0
70 .checked_add(&U256::ONE)
71 .expect("k0 is always less than U256::MAX");
72 let d = Zeroizing::new(pk.to_be_bytes());
73 break SigningKey::from_bytes(d.as_ref()).unwrap();
74 }
75
76 *counter = counter
77 .checked_add(1)
78 .expect("counter will never get to 255");
79 };
80
81 key.to_bytes()
82}
83
84#[derive(Debug)]
86#[non_exhaustive]
87pub struct SigningParams<'a, S> {
88 pub(crate) identity: &'a Identity,
90
91 pub(crate) region_set: &'a str,
93 pub(crate) name: &'a str,
97 pub(crate) time: SystemTime,
99
100 pub(crate) settings: S,
102}
103
104pub(crate) const ECDSA_256: &str = "AWS4-ECDSA-P256-SHA256";
105
106impl<'a, S> SigningParams<'a, S> {
107 pub fn region_set(&self) -> &str {
109 self.region_set
110 }
111
112 pub fn name(&self) -> &str {
114 self.name
115 }
116
117 pub fn algorithm(&self) -> &'static str {
119 ECDSA_256
120 }
121}
122
123impl<'a, S: Default> SigningParams<'a, S> {
124 pub fn builder() -> signing_params::Builder<'a, S> {
126 Default::default()
127 }
128}
129
130pub mod signing_params {
132 use super::SigningParams;
133 use aws_smithy_runtime_api::client::identity::Identity;
134 use std::error::Error;
135 use std::fmt;
136 use std::time::SystemTime;
137
138 #[derive(Debug)]
140 pub struct BuildError {
141 reason: &'static str,
142 }
143 impl BuildError {
144 fn new(reason: &'static str) -> Self {
145 Self { reason }
146 }
147 }
148
149 impl fmt::Display for BuildError {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(f, "{}", self.reason)
152 }
153 }
154
155 impl Error for BuildError {}
156
157 #[derive(Debug, Default)]
159 pub 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 }
166
167 impl<'a, S> Builder<'a, S> {
168 builder_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 );
190
191 pub fn build(self) -> Result<SigningParams<'a, S>, BuildError> {
194 Ok(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}