1use aws_smithy_runtime_api::client::identity::Identity;
7use bytes::{BufMut, BytesMut};
8use crypto_bigint::{CheckedAdd, CheckedSub, Encoding, U256};
9use p256::ecdsa::signature::Signer;
10use p256::ecdsa::{Signature, SigningKey};
11use std::io::Write;
12use std::sync::LazyLock;
13use std::time::SystemTime;
14use zeroize::Zeroizing;
15
16const ALGORITHM: &[u8] = b"AWS4-ECDSA-P256-SHA256";
17static BIG_N_MINUS_2: LazyLock<U256> = LazyLock::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<S> SigningParams<'_, 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}