//! Types and logic for creating signature and authorization headers use crate::{ unix_timestamp, ALGORITHM_FIELD, ALGORITHM_VALUE, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD, }; use std::time::SystemTime; #[derive(Debug)] /// The signed stage of creating a signature /// /// From here, the Signature or Authorization headers can be generated as string pub struct Signed { signature: String, sig_headers: Vec, created: Option, expires: Option, key_id: String, } #[derive(Debug)] /// The Unsigned stage of creating a signature /// /// From here, the `sign` method can be used to sign the signing_string, producing a [`Signed`] /// type. pub struct Unsigned { pub(crate) signing_string: String, pub(crate) sig_headers: Vec, pub(crate) created: Option, pub(crate) expires: Option, } impl Signed { /// Turn the Signed type into a String that can be used as the Signature Header /// /// Done manually, it would look like `format!("Signature: {}", signed.signature_header())` pub fn signature_header(self) -> String { self.into_header() } /// Turn the Signed type into a String that can be used as the Authorization Header /// /// Done manually, it would look like `format!("Authorization: {}", signed.authorization_header())` pub fn authorization_header(self) -> String { format!("Signature {}", self.into_header()) } fn into_header(self) -> String { let mapped = self.created.and_then(|c| self.expires.map(|e| (c, e))); let header_parts = if let Some((created, expires)) = mapped { vec![ (KEY_ID_FIELD, self.key_id), (ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()), (CREATED_FIELD, unix_timestamp(created).to_string()), (EXPIRES_FIELD, unix_timestamp(expires).to_string()), (HEADERS_FIELD, self.sig_headers.join(" ")), (SIGNATURE_FIELD, self.signature), ] } else { vec![ (KEY_ID_FIELD, self.key_id), (ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()), (HEADERS_FIELD, self.sig_headers.join(" ")), (SIGNATURE_FIELD, self.signature), ] }; header_parts .iter() .map(|(k, v)| format!("{}=\"{}\"", k, v)) .collect::>() .join(",") } } impl Unsigned { /// Sign the signing string, producing a String that can be used in an HTTP Header /// /// When using RSA or HMAC to sign the string, be sure to base64-encode the result to produce a /// String. pub fn sign(self, key_id: String, f: F) -> Result where F: FnOnce(&str) -> Result, { (f)(&self.signing_string).map(|signature| Signed { signature, sig_headers: self.sig_headers, created: self.created, expires: self.expires, key_id, }) } }