scratchstack_aws_signature/
signing_key.rs

1use {
2    crate::{
3        crypto::{hmac_sha256, SHA256_OUTPUT_LEN},
4        KeyLengthError,
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
17/// String included at the end of the AWS SigV4 credential scope
18const AWS4_REQUEST: &str = "aws4_request";
19
20/// The default length of an AWS secret key, including the "AWS4" prefix.
21pub(crate) static KSECRETKEY_LENGTH: usize = 44;
22
23/// A raw AWS secret key (`kSecret`).
24#[derive(Clone, Copy, PartialEq, Eq)]
25pub struct KSecretKey<const M: usize = KSECRETKEY_LENGTH> {
26    /// The secret key, prefixed with "AWS4".
27    prefixed_key: [u8; M],
28
29    /// The length of the key.
30    len: usize,
31}
32
33/// The `kDate` key: `HMAC_SHA256("AWS4" + KSecretKey, "YYYYMMDD")`
34#[derive(Clone, Copy, PartialEq, Eq)]
35pub struct KDateKey {
36    /// The raw key.
37    key: [u8; SHA256_OUTPUT_LEN],
38}
39
40/// The `kRegion` key: an AWS `kDate` key, HMAC-SHA256 hashed with the region.
41#[derive(Clone, Copy, PartialEq, Eq)]
42pub struct KRegionKey {
43    /// The raw key.
44    key: [u8; SHA256_OUTPUT_LEN],
45}
46
47/// The `kService` key: an AWS `kRegion` key, HMAC-SHA256 hashed with the service.
48#[derive(Clone, Copy, PartialEq, Eq)]
49pub struct KServiceKey {
50    /// The raw key.
51    key: [u8; SHA256_OUTPUT_LEN],
52}
53
54/// The `kSigning` key: an AWS `kService` key, HMAC-SHA256 hashed with the "aws4_request" string.
55#[derive(Clone, Copy, PartialEq, Eq)]
56pub struct KSigningKey {
57    /// The resulting raw signing key.
58    key: [u8; SHA256_OUTPUT_LEN],
59}
60
61impl AsRef<[u8]> for KSecretKey {
62    fn as_ref(&self) -> &[u8] {
63        // Remove the "AWS4" prefix.
64        &self.prefixed_key[4..self.len]
65    }
66}
67
68impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KDateKey {
69    fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
70        &self.key
71    }
72}
73
74impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KRegionKey {
75    fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
76        &self.key
77    }
78}
79
80impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KServiceKey {
81    fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
82        &self.key
83    }
84}
85
86impl AsRef<[u8; SHA256_OUTPUT_LEN]> for KSigningKey {
87    fn as_ref(&self) -> &[u8; SHA256_OUTPUT_LEN] {
88        &self.key
89    }
90}
91
92impl Debug for KSecretKey {
93    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
94        f.write_str("KSecretKey")
95    }
96}
97
98impl Debug for KDateKey {
99    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
100        f.write_str("KDateKey")
101    }
102}
103
104impl Debug for KRegionKey {
105    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
106        f.write_str("KRegionKey")
107    }
108}
109
110impl Debug for KServiceKey {
111    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
112        f.write_str("KServiceKey")
113    }
114}
115
116impl Debug for KSigningKey {
117    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
118        f.write_str("KSigningKey")
119    }
120}
121
122impl Display for KSecretKey {
123    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
124        f.write_str("KSecretKey")
125    }
126}
127
128impl Display for KDateKey {
129    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
130        f.write_str("KDateKey")
131    }
132}
133
134impl Display for KRegionKey {
135    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
136        f.write_str("KRegionKey")
137    }
138}
139
140impl Display for KServiceKey {
141    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
142        f.write_str("KServiceKey")
143    }
144}
145
146impl Display for KSigningKey {
147    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
148        f.write_str("KSigningKey")
149    }
150}
151
152impl<const M: usize> FromStr for KSecretKey<M> {
153    type Err = KeyLengthError;
154
155    /// Create a new `KSecretKey` from a raw AWS secret key.
156    fn from_str(raw: &str) -> Result<Self, KeyLengthError> {
157        let len = raw.len();
158        if len > M - 4 {
159            return Err(KeyLengthError::TooLong);
160        }
161        if len + 4 < M {
162            return Err(KeyLengthError::TooShort);
163        }
164
165        let mut prefixed_key = [0; M];
166
167        prefixed_key[..4].copy_from_slice(b"AWS4");
168        prefixed_key[4..].copy_from_slice(raw.as_bytes());
169        Ok(Self {
170            prefixed_key,
171            len: len + 4,
172        })
173    }
174}
175
176impl KSecretKey {
177    /// Create a new `KDateKey` from this `KSecretKey` and a date.
178    pub fn to_kdate(&self, date: NaiveDate) -> KDateKey {
179        let date = date.format("%Y%m%d").to_string();
180        let date = date.as_bytes();
181        let key = hmac_sha256(self.prefixed_key.as_slice(), date);
182        let mut key_bytes = [0; SHA256_OUTPUT_LEN];
183        key_bytes.copy_from_slice(key.as_ref());
184        KDateKey {
185            key: key_bytes,
186        }
187    }
188
189    /// Creeate a new `KRegionKey` from this `KSecretKey`, a date, and a region.
190    pub fn to_kregion(&self, date: NaiveDate, region: &str) -> KRegionKey {
191        self.to_kdate(date).to_kregion(region)
192    }
193
194    /// Creeate a new `KServiceKey` from this `KSecretKey`, a date, a region, and a service.
195    pub fn to_kservice(&self, date: NaiveDate, region: &str, service: &str) -> KServiceKey {
196        self.to_kdate(date).to_kservice(region, service)
197    }
198
199    /// Creeate a new `KSigningKey` from this `KSecretKey`, a date, a region, and a service.
200    pub fn to_ksigning(&self, date: NaiveDate, region: &str, service: &str) -> KSigningKey {
201        self.to_kdate(date).to_ksigning(region, service)
202    }
203}
204
205impl KDateKey {
206    /// Create a new `KRegionKey` from this `KDateKey` and a region.
207    pub fn to_kregion(&self, region: &str) -> KRegionKey {
208        let region = region.as_bytes();
209        let key = hmac_sha256(self.key.as_slice(), region);
210        let mut key_bytes = [0; SHA256_OUTPUT_LEN];
211        key_bytes.copy_from_slice(key.as_ref());
212        KRegionKey {
213            key: key_bytes,
214        }
215    }
216
217    /// Create a new `KServiceKey` from this `KDateKey`, a region, and a service.
218    pub fn to_kservice(&self, region: &str, service: &str) -> KServiceKey {
219        self.to_kregion(region).to_kservice(service)
220    }
221
222    /// Create a new `KSigningKey` from this `KDateKey`, a region, and a service.
223    pub fn to_ksigning(&self, region: &str, service: &str) -> KSigningKey {
224        self.to_kregion(region).to_ksigning(service)
225    }
226}
227
228impl KRegionKey {
229    /// Create a new `KServiceKey` from this `KRegionKey` and a service.
230    pub fn to_kservice(&self, service: &str) -> KServiceKey {
231        let service = service.as_bytes();
232        let key = hmac_sha256(self.key.as_slice(), service);
233        let mut key_bytes = [0; SHA256_OUTPUT_LEN];
234        key_bytes.copy_from_slice(key.as_ref());
235        KServiceKey {
236            key: key_bytes,
237        }
238    }
239
240    /// Create a new `KSigningKey` from this `KRegionKey` and a service.
241    pub fn to_ksigning(&self, service: &str) -> KSigningKey {
242        self.to_kservice(service).to_ksigning()
243    }
244}
245
246impl KServiceKey {
247    /// Create a new `KSigningKey` from this `KServiceKey`.
248    pub fn to_ksigning(&self) -> KSigningKey {
249        let key = hmac_sha256(self.key.as_slice(), AWS4_REQUEST.as_bytes());
250        let mut key_bytes = [0; SHA256_OUTPUT_LEN];
251        key_bytes.copy_from_slice(key.as_ref());
252        KSigningKey {
253            key: key_bytes,
254        }
255    }
256}
257
258/// A request for a signing key of a given kind for the specified request.
259///
260/// GetSigningKeyRequest structs are immutable. Use [GetSigningKeyRequestBuilder] to programmatically construct a
261/// request.
262#[derive(Builder, Clone, Debug)]
263#[non_exhaustive]
264pub struct GetSigningKeyRequest {
265    /// The access key used in the request.
266    #[builder(setter(into))]
267    access_key: String,
268
269    /// The session token provided in the request, if any.
270    #[builder(setter(into), default)]
271    session_token: Option<String>,
272
273    /// The date of the request.
274    request_date: NaiveDate,
275
276    /// The region of the request.
277    #[builder(setter(into))]
278    region: String,
279
280    /// The service of the request.
281    #[builder(setter(into))]
282    service: String,
283}
284
285impl GetSigningKeyRequest {
286    /// Create a [GetSigningKeyRequestBuilder] to construct a [GetSigningKeyRequest].
287    #[inline]
288    pub fn builder() -> GetSigningKeyRequestBuilder {
289        GetSigningKeyRequestBuilder::default()
290    }
291
292    /// Retrieve the access key used in the request.
293    #[inline]
294    pub fn access_key(&self) -> &str {
295        &self.access_key
296    }
297
298    /// Retrieve the session token provided in the request, if any.
299    #[inline]
300    pub fn session_token(&self) -> Option<&str> {
301        self.session_token.as_deref()
302    }
303
304    /// Retrieve the date of the request.
305    #[inline]
306    pub fn request_date(&self) -> NaiveDate {
307        self.request_date
308    }
309
310    /// Retrieve the region of the request.
311    #[inline]
312    pub fn region(&self) -> &str {
313        &self.region
314    }
315
316    /// Retrieve the service of the request.
317    #[inline]
318    pub fn service(&self) -> &str {
319        &self.service
320    }
321}
322
323/// A response from the signing key provider.
324///
325/// GetSigningKeyResponse structs are immutable. Use [GetSigningKeyResponseBuilder] to programmatically construct a
326/// response.
327#[derive(Builder, Clone, Debug)]
328pub struct GetSigningKeyResponse {
329    /// The principal actors of the request.
330    #[builder(setter(into), default)]
331    pub(crate) principal: Principal,
332
333    /// The session data associated with the principal.
334    #[builder(setter(into), default)]
335    pub(crate) session_data: SessionData,
336
337    /// The signing key.
338    signing_key: KSigningKey,
339}
340
341impl GetSigningKeyResponse {
342    /// Create a [GetSigningKeyResponseBuilder] to construct a [GetSigningKeyResponse].
343    #[inline]
344    pub fn builder() -> GetSigningKeyResponseBuilder {
345        GetSigningKeyResponseBuilder::default()
346    }
347
348    /// Retrieve the principal actors of the request.
349    #[inline]
350    pub fn principal(&self) -> &Principal {
351        &self.principal
352    }
353
354    /// Retrieve the session data associated with the principal.
355    #[inline]
356    pub fn session_data(&self) -> &SessionData {
357        &self.session_data
358    }
359
360    /// Retrieve the signing key.
361    #[inline]
362    pub fn signing_key(&self) -> &KSigningKey {
363        &self.signing_key
364    }
365}
366
367impl Default for GetSigningKeyResponse {
368    fn default() -> Self {
369        Self {
370            signing_key: KSigningKey {
371                key: [0; SHA256_OUTPUT_LEN],
372            },
373            session_data: SessionData::default(),
374            principal: Principal::new(vec![]),
375        }
376    }
377}
378
379// A trait alias that describes how we obtain a signing key of a given type given a request. If you need to encapsulate
380// additional data (e.g. a database connection) to look up a key, use this to implement a struct.
381//
382// This requires the trait_alias feature to be stabilized and is commented out until then.
383// https://github.com/rust-lang/rust/issues/41517
384//
385// I find trait bounds annoying since they have to be repeated everywhere.
386//
387// pub trait GetSigningKey<F> = Service<GetSigningKeyRequest, Response = (Principal, KSigningKey), Error = BoxError> + Send + 'static;
388
389/// Create a Service that wraps a function that can produce a signing key.
390pub fn service_for_signing_key_fn<F, Fut>(f: F) -> ServiceFn<F>
391where
392    F: FnOnce(GetSigningKeyRequest) -> Fut + Send + 'static,
393    Fut: Future<Output = Result<GetSigningKeyResponse, BoxError>> + Send + 'static,
394{
395    service_fn(f)
396}
397
398#[cfg(test)]
399mod tests {
400    use {
401        crate::{GetSigningKeyRequest, GetSigningKeyResponse, KSecretKey, KSECRETKEY_LENGTH},
402        chrono::NaiveDate,
403        scratchstack_aws_principal::{AssumedRole, Principal},
404        std::str::FromStr,
405    };
406
407    #[test_log::test]
408    fn test_signing_key_derived() {
409        let date = NaiveDate::from_ymd_opt(2015, 8, 30).unwrap();
410
411        let ksecret1a = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap();
412        let ksecret1b = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap();
413        let ksecret2 = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCZEXAMPLEKEY").unwrap();
414
415        assert_eq!(ksecret1a, ksecret1b);
416        assert_eq!(ksecret1a, ksecret1a.clone());
417        assert_ne!(ksecret1a, ksecret2);
418        assert_eq!(format!("{:?}", ksecret1a).as_str(), "KSecretKey");
419        assert_eq!(format!("{}", ksecret1a).as_str(), "KSecretKey");
420        assert_eq!(ksecret1a.as_ref(), b"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY");
421
422        let kdate1a = ksecret1a.to_kdate(date);
423        let kdate1b = ksecret1b.to_kdate(date);
424        let kdate2 = ksecret2.to_kdate(date);
425        assert_eq!(
426            kdate1a.as_ref(),
427            &[
428                0x01u8, 0x38u8, 0xc7u8, 0xa6u8, 0xcbu8, 0xd6u8, 0x0au8, 0xa7u8, 0x27u8, 0xb2u8, 0xf6u8, 0x53u8, 0xa5u8,
429                0x22u8, 0x56u8, 0x74u8, 0x39u8, 0xdfu8, 0xb9u8, 0xf3u8, 0xe7u8, 0x2bu8, 0x21u8, 0xf9u8, 0xb2u8, 0x59u8,
430                0x41u8, 0xa4u8, 0x2fu8, 0x04u8, 0xa7u8, 0xcdu8
431            ]
432        );
433        assert_eq!(kdate1a, kdate1b);
434        assert_eq!(kdate1a, kdate1a.clone());
435        assert_ne!(kdate1a, kdate2);
436        assert_eq!(format!("{:?}", kdate1a).as_str(), "KDateKey");
437        assert_eq!(format!("{}", kdate1a).as_str(), "KDateKey");
438
439        let kregion1a = kdate1a.to_kregion("us-east-1");
440        let kregion1b = kdate1b.to_kregion("us-east-1");
441        let kregion2 = kdate2.to_kregion("us-east-1");
442        assert_eq!(
443            kregion1a.as_ref(),
444            &[
445                0xf3u8, 0x3du8, 0x58u8, 0x08u8, 0x50u8, 0x4bu8, 0xf3u8, 0x48u8, 0x12u8, 0xe5u8, 0xfau8, 0xdeu8, 0x63u8,
446                0x30u8, 0x8bu8, 0x42u8, 0x4bu8, 0x24u8, 0x4cu8, 0x59u8, 0x18u8, 0x9bu8, 0xe2u8, 0xa5u8, 0x91u8, 0xddu8,
447                0x22u8, 0x82u8, 0xc7u8, 0xcbu8, 0x56u8, 0x3fu8
448            ]
449        );
450        assert_eq!(kregion1a, kregion1b);
451        assert_eq!(kregion1a, kregion1a.clone());
452        assert_ne!(kregion1a, kregion2);
453        assert_eq!(format!("{:?}", kregion1a).as_str(), "KRegionKey");
454        assert_eq!(format!("{}", kregion1a).as_str(), "KRegionKey");
455
456        let kservice1a = kregion1a.to_kservice("example");
457        let kservice1b = kregion1b.to_kservice("example");
458        let kservice2 = kregion2.to_kservice("example");
459        assert_eq!(
460            kservice1a.as_ref(),
461            &[
462                0xc6u8, 0x0cu8, 0xc4u8, 0xb1u8, 0xd0u8, 0x34u8, 0xc7u8, 0x57u8, 0x34u8, 0x8fu8, 0x2cu8, 0x67u8, 0x30u8,
463                0x04u8, 0xc1u8, 0x89u8, 0x08u8, 0xbbu8, 0xa9u8, 0xa4u8, 0x6fu8, 0xa1u8, 0xdbu8, 0x87u8, 0xa9u8, 0x83u8,
464                0x50u8, 0xf2u8, 0x7eu8, 0x7bu8, 0x2du8, 0xf6u8
465            ]
466        );
467        assert_eq!(kservice1a, kservice1b);
468        assert_eq!(kservice1a, kservice1a.clone());
469        assert_ne!(kservice1a, kservice2);
470        assert_eq!(format!("{:?}", kservice1a).as_str(), "KServiceKey");
471        assert_eq!(format!("{}", kservice1a).as_str(), "KServiceKey");
472
473        let ksigning1a = kservice1a.to_ksigning();
474        let ksigning1b = kservice1b.to_ksigning();
475        let ksigning2 = kservice2.to_ksigning();
476        assert_eq!(
477            ksigning1a.as_ref(),
478            &[
479                0x43u8, 0x1cu8, 0xc9u8, 0xefu8, 0x58u8, 0x76u8, 0x28u8, 0x7du8, 0xbbu8, 0x92u8, 0x5du8, 0x4bu8, 0xa4u8,
480                0x62u8, 0x9fu8, 0x45u8, 0x90u8, 0x02u8, 0xadu8, 0x1du8, 0x26u8, 0xb7u8, 0xc7u8, 0x51u8, 0x60u8, 0x1bu8,
481                0xb2u8, 0x04u8, 0xe1u8, 0x17u8, 0x18u8, 0xb8u8
482            ]
483        );
484        assert_eq!(ksigning1a, ksigning1b);
485        assert_eq!(ksigning1a, ksigning1a.clone());
486        assert_ne!(ksigning1a, ksigning2);
487        assert_eq!(format!("{:?}", ksigning1a).as_str(), "KSigningKey");
488        assert_eq!(format!("{}", ksigning1a).as_str(), "KSigningKey");
489
490        assert_eq!(ksecret1a.to_kregion(date, "us-east-1"), kregion1a);
491        assert_eq!(ksecret1a.to_kservice(date, "us-east-1", "example"), kservice1a);
492        assert_eq!(ksecret1a.to_ksigning(date, "us-east-1", "example"), ksigning1a);
493
494        assert_eq!(kdate1a.to_kservice("us-east-1", "example"), kservice1a);
495        assert_eq!(kdate1a.to_ksigning("us-east-1", "example"), ksigning1a);
496
497        assert_eq!(kregion1a.to_kservice("example"), kservice1a);
498    }
499
500    #[test_log::test]
501    fn test_gsk_derived() {
502        let date = NaiveDate::from_ymd_opt(2015, 8, 30).unwrap();
503
504        let gsk_req1a = GetSigningKeyRequest {
505            access_key: "AKIDEXAMPLE".to_string(),
506            session_token: Some("token".to_string()),
507            request_date: date,
508            region: "us-east-1".to_string(),
509            service: "example".to_string(),
510        };
511
512        // Make sure we can debug print the request.
513        let _ = format!("{:?}", gsk_req1a);
514
515        // Make sure clones are field-by-field equal.
516        let gsk_req1b = gsk_req1a.clone();
517        assert_eq!(gsk_req1a.access_key, gsk_req1b.access_key);
518        assert_eq!(gsk_req1a.session_token, gsk_req1b.session_token);
519        assert_eq!(gsk_req1a.request_date, gsk_req1b.request_date);
520        assert_eq!(gsk_req1a.region, gsk_req1b.region);
521        assert_eq!(gsk_req1a.service, gsk_req1b.service);
522
523        let signing_key = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap().to_ksigning(
524            date,
525            "us-east-1",
526            "example",
527        );
528        let principal = Principal::from(AssumedRole::new("aws", "123456789012", "role", "session").unwrap());
529
530        let gsk_resp1a =
531            GetSigningKeyResponse::builder().signing_key(signing_key).principal(principal).build().unwrap();
532
533        // Make sure we can debug print the response.
534        let _ = format!("{:?}", gsk_resp1a);
535
536        // Make sure clones are field-by-field equal.
537        let gsk_resp1b = gsk_resp1a.clone();
538        assert_eq!(gsk_resp1a.signing_key, gsk_resp1b.signing_key);
539        assert_eq!(gsk_resp1a.principal, gsk_resp1b.principal);
540    }
541
542    #[test_log::test]
543    fn test_gsk_reponse_derived() {
544        let response: GetSigningKeyResponse = Default::default();
545        assert_eq!(response.signing_key.as_ref(), &[0u8; 32]);
546    }
547
548    #[test_log::test]
549    fn test_response_builder() {
550        let date = NaiveDate::from_ymd_opt(2015, 8, 30).unwrap();
551        let signing_key = KSecretKey::from_str("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").unwrap().to_ksigning(
552            date,
553            "us-east-1",
554            "example",
555        );
556        let response = GetSigningKeyResponse::builder().signing_key(signing_key).build().unwrap();
557        assert!(response.principal().is_empty());
558        assert!(response.session_data().is_empty());
559    }
560
561    #[test]
562    fn test_key_from_str_length() {
563        assert_eq!(KSecretKey::from_str("123"), Err(crate::KeyLengthError::TooShort));
564        assert_eq!(
565            KSecretKey::from_str("123456789012345678901234567890123456789012345"),
566            Err(crate::KeyLengthError::TooLong)
567        );
568        assert!(KSecretKey::<KSECRETKEY_LENGTH>::from_str("1234567890123456789012345678901234567890").is_ok());
569    }
570}