aws_sigv4/sign/
v4a.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use 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    // The N value from section 3.2.1.3 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf
21    // 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
22    // *(Basically a prime number blessed by the NSA for use in p256)*
23    const ORDER: U256 =
24        U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
25    ORDER.checked_sub(&U256::from(2u32)).unwrap()
26});
27
28/// Calculates a Sigv4a signature
29pub 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
35/// Generates a signing key for Sigv4a signing.
36pub fn generate_signing_key(access_key: &str, secret_access_key: &str) -> impl AsRef<[u8]> {
37    // Capacity is the secret access key length plus the length of "AWS4A"
38    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    // Capacity is the access key length plus the counter byte
42    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        // It would be more secure for this to be a constant time comparison, but because this
63        // is for client usage, that's not as big a deal.
64        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/// Parameters to use when signing.
81#[derive(Debug)]
82#[non_exhaustive]
83pub struct SigningParams<'a, S> {
84    /// The identity to use when signing a request
85    pub(crate) identity: &'a Identity,
86
87    /// Region set to sign for.
88    pub(crate) region_set: &'a str,
89    /// Service Name to sign for.
90    ///
91    /// NOTE: Endpoint resolution rules may specify a name that differs from the typical service name.
92    pub(crate) name: &'a str,
93    /// Timestamp to use in the signature (should be `SystemTime::now()` unless testing).
94    pub(crate) time: SystemTime,
95
96    /// Additional signing settings. These differ between HTTP and Event Stream.
97    pub(crate) settings: S,
98}
99
100pub(crate) const ECDSA_256: &str = "AWS4-ECDSA-P256-SHA256";
101
102impl<S> SigningParams<'_, S> {
103    /// Returns the region that will be used to sign SigV4a requests
104    pub fn region_set(&self) -> &str {
105        self.region_set
106    }
107
108    /// Returns the service name that will be used to sign requests
109    pub fn name(&self) -> &str {
110        self.name
111    }
112
113    /// Return the name of the algorithm used to sign requests
114    pub fn algorithm(&self) -> &'static str {
115        ECDSA_256
116    }
117}
118
119impl<'a, S: Default> SigningParams<'a, S> {
120    /// Returns a builder that can create new `SigningParams`.
121    pub fn builder() -> signing_params::Builder<'a, S> {
122        Default::default()
123    }
124}
125
126/// Builder and error for creating [`SigningParams`]
127pub 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    /// [`SigningParams`] builder error
135    #[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    /// Builder that can create new [`SigningParams`]
154    #[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        /// Builds an instance of [`SigningParams`]. Will yield a [`BuildError`] if
188        /// a required argument was not given.
189        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}