http_signature_normalization/
create.rs

1//! Types and logic for creating signature and authorization headers
2use crate::{
3    unix_timestamp, ALGORITHM_FIELD, ALGORITHM_VALUE, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD,
4    KEY_ID_FIELD, SIGNATURE_FIELD,
5};
6use std::time::SystemTime;
7
8#[derive(Debug)]
9/// The signed stage of creating a signature
10///
11/// From here, the Signature or Authorization headers can be generated as string
12pub struct Signed {
13    signature: String,
14    sig_headers: Vec<String>,
15    created: Option<SystemTime>,
16    expires: Option<SystemTime>,
17    key_id: String,
18}
19
20#[derive(Debug)]
21/// The Unsigned stage of creating a signature
22///
23/// From here, the `sign` method can be used to sign the signing_string, producing a [`Signed`]
24/// type.
25pub struct Unsigned {
26    pub(crate) signing_string: String,
27    pub(crate) sig_headers: Vec<String>,
28    pub(crate) created: Option<SystemTime>,
29    pub(crate) expires: Option<SystemTime>,
30}
31
32impl Signed {
33    /// Turn the Signed type into a String that can be used as the Signature Header
34    ///
35    /// Done manually, it would look like `format!("Signature: {}", signed.signature_header())`
36    pub fn signature_header(self) -> String {
37        self.into_header()
38    }
39
40    /// Turn the Signed type into a String that can be used as the Authorization Header
41    ///
42    /// Done manually, it would look like `format!("Authorization: {}", signed.authorization_header())`
43    pub fn authorization_header(self) -> String {
44        format!("Signature {}", self.into_header())
45    }
46
47    fn into_header(self) -> String {
48        let mapped = self.created.and_then(|c| self.expires.map(|e| (c, e)));
49        let header_parts = if let Some((created, expires)) = mapped {
50            vec![
51                (KEY_ID_FIELD, self.key_id),
52                (ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()),
53                (CREATED_FIELD, unix_timestamp(created).to_string()),
54                (EXPIRES_FIELD, unix_timestamp(expires).to_string()),
55                (HEADERS_FIELD, self.sig_headers.join(" ")),
56                (SIGNATURE_FIELD, self.signature),
57            ]
58        } else {
59            vec![
60                (KEY_ID_FIELD, self.key_id),
61                (ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()),
62                (HEADERS_FIELD, self.sig_headers.join(" ")),
63                (SIGNATURE_FIELD, self.signature),
64            ]
65        };
66
67        header_parts
68            .iter()
69            .map(|(k, v)| format!("{}=\"{}\"", k, v))
70            .collect::<Vec<_>>()
71            .join(",")
72    }
73}
74
75impl Unsigned {
76    /// Sign the signing string, producing a String that can be used in an HTTP Header
77    ///
78    /// When using RSA or HMAC to sign the string, be sure to base64-encode the result to produce a
79    /// String.
80    pub fn sign<F, E>(self, key_id: String, f: F) -> Result<Signed, E>
81    where
82        F: FnOnce(&str) -> Result<String, E>,
83    {
84        (f)(&self.signing_string).map(|signature| Signed {
85            signature,
86            sig_headers: self.sig_headers,
87            created: self.created,
88            expires: self.expires,
89            key_id,
90        })
91    }
92}