~vpzom/hancock

567e47cd1dc6bd67df4556c016076d712710557f — Colin Reeder 1 year, 5 months ago
Initial commit
3 files changed, 84 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.toml
A src/lib.rs
A  => .gitignore +2 -0
@@ 1,2 @@
/target
Cargo.lock

A  => Cargo.toml +12 -0
@@ 1,12 @@
[package]
name = "hancock"
version = "0.1.0"
authors = ["Colin Reeder <colin@vpzom.click>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
http = "0.2.1"
thiserror = "1.0.19"
base64 = "0.12.1"

A  => src/lib.rs +70 -0
@@ 1,70 @@
#[derive(Debug, thiserror::Error)]
pub enum Error<T: std::fmt::Debug> {
    #[error("IO error occurred")]
    IO(#[from] std::io::Error),
    #[error("Formatting error occurred")]
    Fmt(#[from] std::fmt::Error),
    #[error("Failed in signer call")]
    User(T),
}

pub fn create_signature<E: std::fmt::Debug>(
    key_id: &str,
    request_method: &http::method::Method,
    request_path_and_query: &str,
    lifetime_secs: u64,
    headers: &mut http::header::HeaderMap,
    signer: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, E>,
) -> Result<http::header::HeaderValue, Error<E>> {
    use std::io::Write;

    let created = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("Timestamp is wildly unrealistic (before epoch)")
        .as_secs();
    let expires = created + lifetime_secs;

    let mut body = Vec::new();

    write!(
        body,
        "(request-target): {} {}\n(created): {}\n(expires): {}",
        request_method.as_str().to_lowercase(),
        request_path_and_query,
        created,
        expires,
    )?;

    for name in headers.keys() {
        write!(body, "\n{}: ", name)?;

        let mut first = true;
        for value in headers.get_all(name) {
            if first {
                first = false;
            } else {
                write!(body, ", ")?;
            }

            body.extend(value.as_bytes());
        }
    }

    let signature = signer(body).map_err(Error::User)?;

    {
        use std::fmt::Write;
        let mut params = String::new();
        write!(params, "algorithm=hs2019,created={},expires={},keyId=\"{}\",headers=\"(request-target) (created) (expires)", created, expires, key_id)?;
        for name in headers.keys() {
            write!(params, " {}", name)?;
        }
        write!(params, "\",signature=\"")?;

        base64::encode_config_buf(signature, base64::STANDARD, &mut params);

        write!(params, "\"")?;

        Ok(http::header::HeaderValue::from_bytes(params.as_bytes()).unwrap())
    }
}