1use crate::date_time::format_date;
7use aws_smithy_runtime_api::client::identity::Identity;
8use hmac::{digest::FixedOutput, Hmac, Mac};
9use sha2::{Digest, Sha256};
10use std::time::SystemTime;
11
12#[allow(dead_code)] pub(crate) fn sha256_hex_string(bytes: impl AsRef<[u8]>) -> String {
15 let mut hasher = Sha256::new();
16 hasher.update(bytes);
17 hex::encode(hasher.finalize_fixed())
18}
19
20pub fn calculate_signature(signing_key: impl AsRef<[u8]>, string_to_sign: &[u8]) -> String {
22 let mut mac = Hmac::<Sha256>::new_from_slice(signing_key.as_ref())
23 .expect("HMAC can take key of any size");
24 mac.update(string_to_sign);
25 hex::encode(mac.finalize_fixed())
26}
27
28pub fn generate_signing_key(
30 secret: &str,
31 time: SystemTime,
32 region: &str,
33 service: &str,
34) -> impl AsRef<[u8]> {
35 let secret = format!("AWS4{}", secret);
42 let mut mac =
43 Hmac::<Sha256>::new_from_slice(secret.as_ref()).expect("HMAC can take key of any size");
44 mac.update(format_date(time).as_bytes());
45 let tag = mac.finalize_fixed();
46
47 let mut mac = Hmac::<Sha256>::new_from_slice(&tag).expect("HMAC can take key of any size");
49 mac.update(region.as_bytes());
50 let tag = mac.finalize_fixed();
51
52 let mut mac = Hmac::<Sha256>::new_from_slice(&tag).expect("HMAC can take key of any size");
54 mac.update(service.as_bytes());
55 let tag = mac.finalize_fixed();
56
57 let mut mac = Hmac::<Sha256>::new_from_slice(&tag).expect("HMAC can take key of any size");
59 mac.update("aws4_request".as_bytes());
60 mac.finalize_fixed()
61}
62
63#[derive(Debug)]
65#[non_exhaustive]
66pub struct SigningParams<'a, S> {
67 pub(crate) identity: &'a Identity,
69
70 pub(crate) region: &'a str,
72 pub(crate) name: &'a str,
76 pub(crate) time: SystemTime,
78
79 pub(crate) settings: S,
81}
82
83const HMAC_256: &str = "AWS4-HMAC-SHA256";
84
85impl<'a, S> SigningParams<'a, S> {
86 pub fn region(&self) -> &str {
88 self.region
89 }
90
91 pub fn name(&self) -> &str {
93 self.name
94 }
95
96 pub fn algorithm(&self) -> &'static str {
98 HMAC_256
99 }
100}
101
102impl<'a, S: Default> SigningParams<'a, S> {
103 pub fn builder() -> signing_params::Builder<'a, S> {
105 Default::default()
106 }
107}
108
109pub mod signing_params {
111 use super::SigningParams;
112 use aws_smithy_runtime_api::client::identity::Identity;
113 use std::error::Error;
114 use std::fmt;
115 use std::time::SystemTime;
116
117 #[derive(Debug)]
119 pub struct BuildError {
120 reason: &'static str,
121 }
122 impl BuildError {
123 fn new(reason: &'static str) -> Self {
124 Self { reason }
125 }
126 }
127
128 impl fmt::Display for BuildError {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "{}", self.reason)
131 }
132 }
133
134 impl Error for BuildError {}
135
136 #[derive(Debug, Default)]
138 pub struct Builder<'a, S> {
139 identity: Option<&'a Identity>,
140 region: Option<&'a str>,
141 name: Option<&'a str>,
142 time: Option<SystemTime>,
143 settings: Option<S>,
144 }
145
146 impl<'a, S> Builder<'a, S> {
147 builder_methods!(
148 set_identity,
149 identity,
150 &'a Identity,
151 "Sets the identity (required)",
152 set_region,
153 region,
154 &'a str,
155 "Sets the region (required)",
156 set_name,
157 name,
158 &'a str,
159 "Sets the name (required)",
160 set_time,
161 time,
162 SystemTime,
163 "Sets the time to be used in the signature (required)",
164 set_settings,
165 settings,
166 S,
167 "Sets additional signing settings (required)"
168 );
169
170 pub fn build(self) -> Result<SigningParams<'a, S>, BuildError> {
173 Ok(SigningParams {
174 identity: self
175 .identity
176 .ok_or_else(|| BuildError::new("identity is required"))?,
177 region: self
178 .region
179 .ok_or_else(|| BuildError::new("region is required"))?,
180 name: self
181 .name
182 .ok_or_else(|| BuildError::new("name is required"))?,
183 time: self
184 .time
185 .ok_or_else(|| BuildError::new("time is required"))?,
186 settings: self
187 .settings
188 .ok_or_else(|| BuildError::new("settings are required"))?,
189 })
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::{calculate_signature, generate_signing_key, sha256_hex_string};
197 use crate::date_time::test_parsers::parse_date_time;
198 use crate::http_request::test;
199
200 #[test]
201 fn test_signature_calculation() {
202 let secret = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
203 let creq = test::v4::test_canonical_request("iam");
204 let time = parse_date_time("20150830T123600Z").unwrap();
205
206 let derived_key = generate_signing_key(secret, time, "us-east-1", "iam");
207 let signature = calculate_signature(derived_key, creq.as_bytes());
208
209 let expected = "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7";
210 assert_eq!(expected, &signature);
211 }
212
213 #[test]
214 fn sign_payload_empty_string() {
215 let expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
216 let actual = sha256_hex_string([]);
217 assert_eq!(expected, actual);
218 }
219}