http-signature-normalization 0.7.0

An HTTP Signatures library that leaves the signing to you
Documentation
//! 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<String>,
    created: Option<SystemTime>,
    expires: Option<SystemTime>,
    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<String>,
    pub(crate) created: Option<SystemTime>,
    pub(crate) expires: Option<SystemTime>,
}

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::<Vec<_>>()
            .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<F, E>(self, key_id: String, f: F) -> Result<Signed, E>
    where
        F: FnOnce(&str) -> Result<String, E>,
    {
        (f)(&self.signing_string).map(|signature| Signed {
            signature,
            sig_headers: self.sig_headers,
            created: self.created,
            expires: self.expires,
            key_id,
        })
    }
}