1use {
2 crate::{
3 crypto::{hmac_sha256, SHA256_OUTPUT_LEN},
4 KeyTooLongError,
5 },
6 chrono::NaiveDate,
7 derive_builder::Builder,
8 scratchstack_aws_principal::{Principal, SessionData},
9 std::{
10 fmt::{Debug, Display, Formatter, Result as FmtResult},
11 future::Future,
12 str::FromStr,
13 },
14 tower::{service_fn, util::ServiceFn, BoxError},
15};
16
17const AWS4_REQUEST: &str = "aws4_request";
19
20#[derive(Clone, Copy, PartialEq, Eq)]
22pub struct KSecretKey<const M: usize = 44> {
23 prefixed_key: [u8; M],
25
26 len: usize,
28}
29
30#[derive(Clone, Copy, PartialEq, Eq)]
32pub struct KDateKey {
33 key: [u8; SHA256_OUTPUT_LEN],
35}
36
37#[derive(Clone, Copy, PartialEq, Eq)]
39pub struct KRegionKey {
40 key: [u8; SHA256_OUTPUT_LEN],
42}
43
44#[derive(Clone, Copy, PartialEq, Eq)]
46pub struct KServiceKey {
47 key: [u8; SHA256_OUTPUT_LEN],
49}
50
51#[derive(Clone, Copy, PartialEq, Eq)]
53pub struct KSigningKey {
54 key: [u8; SHA256_OUTPUT_LEN],
56}
57
58impl AsRef<[u8]> for KSecretKey {
59 fn as_ref(&self) -> &[u8] {
60 &self.prefixed_key[4..self.len]
62 }
63}
64
65impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KDateKey {
66 fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
67 &self.key
68 }
69}
70
71impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KRegionKey {
72 fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
73 &self.key
74 }
75}
76
77impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KServiceKey {
78 fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
79 &self.key
80 }
81}
82
83impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KSigningKey {
84 fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
85 &self.key
86 }
87}
88
89impl Debug for KSecretKey {
90 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
91 f.write_str("KSecretKey")
92 }
93}
94
95impl Debug for KDateKey {
96 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
97 f.write_str("KDateKey")
98 }
99}
100
101impl Debug for KRegionKey {
102 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
103 f.write_str("KRegionKey")
104 }
105}
106
107impl Debug for KServiceKey {
108 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
109 f.write_str("KServiceKey")
110 }
111}
112
113impl Debug for KSigningKey {
114 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
115 f.write_str("KSigningKey")
116 }
117}
118
119impl Display for KSecretKey {
120 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
121 f.write_str("KSecretKey")
122 }
123}
124
125impl Display for KDateKey {
126 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
127 f.write_str("KDateKey")
128 }
129}
130
131impl Display for KRegionKey {
132 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
133 f.write_str("KRegionKey")
134 }
135}
136
137impl Display for KServiceKey {
138 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
139 f.write_str("KServiceKey")
140 }
141}
142
143impl Display for KSigningKey {
144 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
145 f.write_str("KSigningKey")
146 }
147}
148
149impl<const M: usize> FromStr for KSecretKey<M> {
150 type Err = KeyTooLongError;
151
152 fn from_str(raw: &str) -> Result<Self, KeyTooLongError> {
154 let len = raw.len();
155 if len > M - 4 {
156 return Err(KeyTooLongError);
157 }
158
159 let mut prefixed_key = [0; M];
160
161 prefixed_key[..4].copy_from_slice(b"AWS4");
162 prefixed_key[4..].copy_from_slice(raw.as_bytes());
163 Ok(Self {
164 prefixed_key,
165 len: len + 4,
166 })
167 }
168}
169
170impl KSecretKey {
171 pub fn to_kdate(&self, date: NaiveDate) -> KDateKey {
173 let date = date.format("%Y%m%d").to_string();
174 let date = date.as_bytes();
175 let key = hmac_sha256(self.prefixed_key.as_slice(), date);
176 let mut key_bytes = [0; SHA256_OUTPUT_LEN];
177 key_bytes.copy_from_slice(key.as_ref());
178 KDateKey {
179 key: key_bytes,
180 }
181 }
182
183 pub fn to_kregion(&self, date: NaiveDate, region: &str) -> KRegionKey {
185 self.to_kdate(date).to_kregion(region)
186 }
187
188 pub fn to_kservice(&self, date: NaiveDate, region: &str, service: &str) -> KServiceKey {
190 self.to_kdate(date).to_kservice(region, service)
191 }
192
193 pub fn to_ksigning(&self, date: NaiveDate, region: &str, service: &str) -> KSigningKey {
195 self.to_kdate(date).to_ksigning(region, service)
196 }
197}
198
199impl KDateKey {
200 pub fn to_kregion(&self, region: &str) -> KRegionKey {
202 let region = region.as_bytes();
203 let key = hmac_sha256(self.key.as_slice(), region);
204 let mut key_bytes = [0; SHA256_OUTPUT_LEN];
205 key_bytes.copy_from_slice(key.as_ref());
206 KRegionKey {
207 key: key_bytes,
208 }
209 }
210
211 pub fn to_kservice(&self, region: &str, service: &str) -> KServiceKey {
213 self.to_kregion(region).to_kservice(service)
214 }
215
216 pub fn to_ksigning(&self, region: &str, service: &str) -> KSigningKey {
218 self.to_kregion(region).to_ksigning(service)
219 }
220}
221
222impl KRegionKey {
223 pub fn to_kservice(&self, service: &str) -> KServiceKey {
225 let service = service.as_bytes();
226 let key = hmac_sha256(self.key.as_slice(), service);
227 let mut key_bytes = [0; SHA256_OUTPUT_LEN];
228 key_bytes.copy_from_slice(key.as_ref());
229 KServiceKey {
230 key: key_bytes,
231 }
232 }
233
234 pub fn to_ksigning(&self, service: &str) -> KSigningKey {
236 self.to_kservice(service).to_ksigning()
237 }
238}
239
240impl KServiceKey {
241 pub fn to_ksigning(&self) -> KSigningKey {
243 let key = hmac_sha256(self.key.as_slice(), AWS4_REQUEST.as_bytes());
244 let mut key_bytes = [0; SHA256_OUTPUT_LEN];
245 key_bytes.copy_from_slice(key.as_ref());
246 KSigningKey {
247 key: key_bytes,
248 }
249 }
250}
251
252#[derive(Builder, Clone, Debug)]
257#[non_exhaustive]
258pub struct GetSigningKeyRequest {
259 #[builder(setter(into))]
261 access_key: String,
262
263 #[builder(setter(into), default)]
265 session_token: Option<String>,
266
267 request_date: NaiveDate,
269
270 #[builder(setter(into))]
272 region: String,
273
274 #[builder(setter(into))]
276 service: String,
277}
278
279impl GetSigningKeyRequest {
280 #[inline]
282 pub fn builder() -> GetSigningKeyRequestBuilder {
283 GetSigningKeyRequestBuilder::default()
284 }
285
286 #[inline]
288 pub fn access_key(&self) -> &str {
289 &self.access_key
290 }
291
292 #[inline]
294 pub fn session_token(&self) -> Option<&str> {
295 self.session_token.as_deref()
296 }
297
298 #[inline]
300 pub fn request_date(&self) -> NaiveDate {
301 self.request_date
302 }
303
304 #[inline]
306 pub fn region(&self) -> &str {
307 &self.region
308 }
309
310 #[inline]
312 pub fn service(&self) -> &str {
313 &self.service
314 }
315}
316
317#[derive(Builder, Clone, Debug)]
322pub struct GetSigningKeyResponse {
323 #[builder(setter(into), default)]
325 pub(crate) principal: Principal,
326
327 #[builder(setter(into), default)]
329 pub(crate) session_data: SessionData,
330
331 signing_key: KSigningKey,
333}
334
335impl GetSigningKeyResponse {
336 #[inline]
338 pub fn builder() -> GetSigningKeyResponseBuilder {
339 GetSigningKeyResponseBuilder::default()
340 }
341
342 #[inline]
344 pub fn principal(&self) -> &Principal {
345 &self.principal
346 }
347
348 #[inline]
350 pub fn session_data(&self) -> &SessionData {
351 &self.session_data
352 }
353
354 #[inline]
356 pub fn signing_key(&self) -> &KSigningKey {
357 &self.signing_key
358 }
359}
360
361impl Default for GetSigningKeyResponse {
362 fn default() -> Self {
363 Self {
364 signing_key: KSigningKey {
365 key: [0; SHA256_OUTPUT_LEN],
366 },
367 session_data: SessionData::default(),
368 principal: Principal::new(vec![]),
369 }
370 }
371}
372
373pub fn service_for_signing_key_fn<F, Fut>(f: F) -> ServiceFn<F>
385where
386 F: FnOnce(GetSigningKeyRequest) -> Fut + Send + 'static,
387 Fut: Future<Output = Result<GetSigningKeyResponse, BoxError>> + Send + 'static,
388{
389 service_fn(f)
390}
391
392#[cfg(test)]
393mod tests {
394 use {
395 crate::{GetSigningKeyRequest, GetSigningKeyResponse, KSecretKey},
396 chrono::NaiveDate,
397 scratchstack_aws_principal::{AssumedRole, Principal},
398 std::str::FromStr,
399 };
400
401 #[test_log::test]
402 fn test_signing_key_derived() {
403 let date = NaiveDate::from_ymd_opt(2015, 8, 30).unwrap();
404
405 let ksecret1a = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap();
406 let ksecret1b = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap();
407 let ksecret2 = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCZEXAMPLEKEY").unwrap();
408
409 assert_eq!(ksecret1a, ksecret1b);
410 assert_eq!(ksecret1a, ksecret1a.clone());
411 assert_ne!(ksecret1a, ksecret2);
412 assert_eq!(format!("{:?}", ksecret1a).as_str(), "KSecretKey");
413 assert_eq!(format!("{}", ksecret1a).as_str(), "KSecretKey");
414 assert_eq!(ksecret1a.as_ref(), b"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY");
415
416 let kdate1a = ksecret1a.to_kdate(date);
417 let kdate1b = ksecret1b.to_kdate(date);
418 let kdate2 = ksecret2.to_kdate(date);
419 assert_eq!(
420 kdate1a.as_ref(),
421 &[
422 0x01u8, 0x38u8, 0xc7u8, 0xa6u8, 0xcbu8, 0xd6u8, 0x0au8, 0xa7u8, 0x27u8, 0xb2u8, 0xf6u8, 0x53u8, 0xa5u8,
423 0x22u8, 0x56u8, 0x74u8, 0x39u8, 0xdfu8, 0xb9u8, 0xf3u8, 0xe7u8, 0x2bu8, 0x21u8, 0xf9u8, 0xb2u8, 0x59u8,
424 0x41u8, 0xa4u8, 0x2fu8, 0x04u8, 0xa7u8, 0xcdu8
425 ]
426 );
427 assert_eq!(kdate1a, kdate1b);
428 assert_eq!(kdate1a, kdate1a.clone());
429 assert_ne!(kdate1a, kdate2);
430 assert_eq!(format!("{:?}", kdate1a).as_str(), "KDateKey");
431 assert_eq!(format!("{}", kdate1a).as_str(), "KDateKey");
432
433 let kregion1a = kdate1a.to_kregion("us-east-1");
434 let kregion1b = kdate1b.to_kregion("us-east-1");
435 let kregion2 = kdate2.to_kregion("us-east-1");
436 assert_eq!(
437 kregion1a.as_ref(),
438 &[
439 0xf3u8, 0x3du8, 0x58u8, 0x08u8, 0x50u8, 0x4bu8, 0xf3u8, 0x48u8, 0x12u8, 0xe5u8, 0xfau8, 0xdeu8, 0x63u8,
440 0x30u8, 0x8bu8, 0x42u8, 0x4bu8, 0x24u8, 0x4cu8, 0x59u8, 0x18u8, 0x9bu8, 0xe2u8, 0xa5u8, 0x91u8, 0xddu8,
441 0x22u8, 0x82u8, 0xc7u8, 0xcbu8, 0x56u8, 0x3fu8
442 ]
443 );
444 assert_eq!(kregion1a, kregion1b);
445 assert_eq!(kregion1a, kregion1a.clone());
446 assert_ne!(kregion1a, kregion2);
447 assert_eq!(format!("{:?}", kregion1a).as_str(), "KRegionKey");
448 assert_eq!(format!("{}", kregion1a).as_str(), "KRegionKey");
449
450 let kservice1a = kregion1a.to_kservice("example");
451 let kservice1b = kregion1b.to_kservice("example");
452 let kservice2 = kregion2.to_kservice("example");
453 assert_eq!(
454 kservice1a.as_ref(),
455 &[
456 0xc6u8, 0x0cu8, 0xc4u8, 0xb1u8, 0xd0u8, 0x34u8, 0xc7u8, 0x57u8, 0x34u8, 0x8fu8, 0x2cu8, 0x67u8, 0x30u8,
457 0x04u8, 0xc1u8, 0x89u8, 0x08u8, 0xbbu8, 0xa9u8, 0xa4u8, 0x6fu8, 0xa1u8, 0xdbu8, 0x87u8, 0xa9u8, 0x83u8,
458 0x50u8, 0xf2u8, 0x7eu8, 0x7bu8, 0x2du8, 0xf6u8
459 ]
460 );
461 assert_eq!(kservice1a, kservice1b);
462 assert_eq!(kservice1a, kservice1a.clone());
463 assert_ne!(kservice1a, kservice2);
464 assert_eq!(format!("{:?}", kservice1a).as_str(), "KServiceKey");
465 assert_eq!(format!("{}", kservice1a).as_str(), "KServiceKey");
466
467 let ksigning1a = kservice1a.to_ksigning();
468 let ksigning1b = kservice1b.to_ksigning();
469 let ksigning2 = kservice2.to_ksigning();
470 assert_eq!(
471 ksigning1a.as_ref(),
472 &[
473 0x43u8, 0x1cu8, 0xc9u8, 0xefu8, 0x58u8, 0x76u8, 0x28u8, 0x7du8, 0xbbu8, 0x92u8, 0x5du8, 0x4bu8, 0xa4u8,
474 0x62u8, 0x9fu8, 0x45u8, 0x90u8, 0x02u8, 0xadu8, 0x1du8, 0x26u8, 0xb7u8, 0xc7u8, 0x51u8, 0x60u8, 0x1bu8,
475 0xb2u8, 0x04u8, 0xe1u8, 0x17u8, 0x18u8, 0xb8u8
476 ]
477 );
478 assert_eq!(ksigning1a, ksigning1b);
479 assert_eq!(ksigning1a, ksigning1a.clone());
480 assert_ne!(ksigning1a, ksigning2);
481 assert_eq!(format!("{:?}", ksigning1a).as_str(), "KSigningKey");
482 assert_eq!(format!("{}", ksigning1a).as_str(), "KSigningKey");
483
484 assert_eq!(ksecret1a.to_kregion(date, "us-east-1"), kregion1a);
485 assert_eq!(ksecret1a.to_kservice(date, "us-east-1", "example"), kservice1a);
486 assert_eq!(ksecret1a.to_ksigning(date, "us-east-1", "example"), ksigning1a);
487
488 assert_eq!(kdate1a.to_kservice("us-east-1", "example"), kservice1a);
489 assert_eq!(kdate1a.to_ksigning("us-east-1", "example"), ksigning1a);
490
491 assert_eq!(kregion1a.to_kservice("example"), kservice1a);
492 }
493
494 #[test_log::test]
495 fn test_gsk_derived() {
496 let date = NaiveDate::from_ymd_opt(2015, 8, 30).unwrap();
497
498 let gsk_req1a = GetSigningKeyRequest {
499 access_key: "AKIDEXAMPLE".to_string(),
500 session_token: Some("token".to_string()),
501 request_date: date,
502 region: "us-east-1".to_string(),
503 service: "example".to_string(),
504 };
505
506 let _ = format!("{:?}", gsk_req1a);
508
509 let gsk_req1b = gsk_req1a.clone();
511 assert_eq!(gsk_req1a.access_key, gsk_req1b.access_key);
512 assert_eq!(gsk_req1a.session_token, gsk_req1b.session_token);
513 assert_eq!(gsk_req1a.request_date, gsk_req1b.request_date);
514 assert_eq!(gsk_req1a.region, gsk_req1b.region);
515 assert_eq!(gsk_req1a.service, gsk_req1b.service);
516
517 let signing_key = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap().to_ksigning(
518 date,
519 "us-east-1",
520 "example",
521 );
522 let principal = Principal::from(AssumedRole::new("aws", "123456789012", "role", "session").unwrap());
523
524 let gsk_resp1a =
525 GetSigningKeyResponse::builder().signing_key(signing_key).principal(principal).build().unwrap();
526
527 let _ = format!("{:?}", gsk_resp1a);
529
530 let gsk_resp1b = gsk_resp1a.clone();
532 assert_eq!(gsk_resp1a.signing_key, gsk_resp1b.signing_key);
533 assert_eq!(gsk_resp1a.principal, gsk_resp1b.principal);
534 }
535
536 #[test_log::test]
537 fn test_gsk_reponse_derived() {
538 let response: GetSigningKeyResponse = Default::default();
539 assert_eq!(response.signing_key.as_ref(), &[0u8; 32]);
540 }
541
542 #[test_log::test]
543 fn test_response_builder() {
544 let date = NaiveDate::from_ymd_opt(2015, 8, 30).unwrap();
545 let signing_key = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap().to_ksigning(
546 date,
547 "us-east-1",
548 "example",
549 );
550 let response = GetSigningKeyResponse::builder().signing_key(signing_key).build().unwrap();
551 assert!(response.principal().is_empty());
552 assert!(response.session_data().is_empty());
553 }
554}