use chrono::{DateTime, TimeZone, Utc}; use http::{ header::{HeaderMap, ToStrError}, method::Method, uri::PathAndQuery, }; use std::{collections::HashMap, error::Error, fmt, str::FromStr}; use crate::{ build_headers_list, build_signing_string, ALGORITHM_FIELD, CREATED, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD, }; pub struct Unvalidated { pub(crate) key_id: String, pub(crate) signature: String, pub(crate) algorithm: Option, pub(crate) created: Option>, pub(crate) expires: Option>, pub(crate) parsed_at: DateTime, pub(crate) signing_string: String, } pub struct ParsedHeader { signature: String, key_id: String, headers: Vec, algorithm: Option, created: Option>, expires: Option>, parsed_at: DateTime, } #[derive(Clone, Copy, Debug)] pub enum DeprecatedAlgorithm { HmacSha1, HmacSha256, HmacSha384, HmacSha512, RsaSha1, RsaSha256, RsaSha384, RsaSha512, EcdsaSha1, EcdsaSha256, EcdsaSha384, EcdsaSha512, } #[derive(Clone, Debug)] pub enum Algorithm { Hs2019, Deprecated(DeprecatedAlgorithm), Unknown(String), } #[derive(Clone, Debug)] pub enum ValidateError { Expired, Decode, } #[derive(Clone, Debug)] pub struct ParseSignatureError(&'static str); impl Unvalidated { pub fn key_id(&self) -> &str { &self.key_id } pub fn algorithm(&self) -> Option<&Algorithm> { self.algorithm.as_ref() } } impl ParsedHeader { pub fn to_unvalidated( self, method: Method, path_and_query: &PathAndQuery, headers: &HeaderMap, ) -> Result { let (_, mut btm) = build_headers_list(headers)?; let signing_string = build_signing_string( method, path_and_query, self.created, self.expires, &self.headers, &mut btm, ); Ok(Unvalidated { key_id: self.key_id, signature: self.signature, parsed_at: self.parsed_at, algorithm: self.algorithm, created: self.created, expires: self.expires, signing_string, }) } } impl FromStr for ParsedHeader { type Err = ParseSignatureError; fn from_str(s: &str) -> Result { let s = s.trim_start_matches("Signature").trim(); let mut hm: HashMap = s .split(',') .filter_map(|part| { let mut i = part.splitn(2, "="); if let Some(key) = i.next() { if let Some(value) = i.next() { return Some((key.to_owned(), value.to_owned())); } } None }) .collect(); Ok(ParsedHeader { signature: hm .remove(SIGNATURE_FIELD) .ok_or(ParseSignatureError(SIGNATURE_FIELD))?, key_id: hm .remove(KEY_ID_FIELD) .ok_or(ParseSignatureError(KEY_ID_FIELD))?, headers: hm .remove(HEADERS_FIELD) .map(|h| h.split_whitespace().map(|s| s.to_owned()).collect()) .unwrap_or_else(|| vec![CREATED.to_owned()]), algorithm: hm.remove(ALGORITHM_FIELD).map(Algorithm::from), created: parse_time(&mut hm, CREATED_FIELD)?, expires: parse_time(&mut hm, EXPIRES_FIELD)?, parsed_at: Utc::now(), }) } } fn parse_time( hm: &mut HashMap, key: &'static str, ) -> Result>, ParseSignatureError> { let r = hm.remove(key).map(|s| { Utc.datetime_from_str(&s, "%s") .map_err(|_| ParseSignatureError(key)) }); match r { Some(Ok(t)) => Ok(Some(t)), Some(Err(e)) => Err(e), None => Ok(None), } } impl From for Algorithm { fn from(d: DeprecatedAlgorithm) -> Algorithm { Algorithm::Deprecated(d) } } impl From for Algorithm { fn from(s: String) -> Self { Algorithm::from(s.as_str()) } } impl From<&str> for Algorithm { fn from(s: &str) -> Self { match s { "hmac-sha1" => DeprecatedAlgorithm::HmacSha1.into(), "hmac-sha256" => DeprecatedAlgorithm::HmacSha256.into(), "hmac-sha384" => DeprecatedAlgorithm::HmacSha384.into(), "hmac-sha512" => DeprecatedAlgorithm::HmacSha512.into(), "rsa-sha1" => DeprecatedAlgorithm::RsaSha1.into(), "rsa-sha256" => DeprecatedAlgorithm::RsaSha256.into(), "rsa-sha384" => DeprecatedAlgorithm::RsaSha384.into(), "rsa-sha512" => DeprecatedAlgorithm::RsaSha512.into(), "ecdsa-sha1" => DeprecatedAlgorithm::EcdsaSha1.into(), "ecdsa-sha256" => DeprecatedAlgorithm::EcdsaSha256.into(), "ecdsa-sha384" => DeprecatedAlgorithm::EcdsaSha384.into(), "ecdsa-sha512" => DeprecatedAlgorithm::EcdsaSha512.into(), "hs2019" => Algorithm::Hs2019, other => Algorithm::Unknown(other.into()), } } } impl fmt::Display for ValidateError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ValidateError::Expired => write!(f, "Http Signature is expired"), ValidateError::Decode => write!(f, "Http Signature could not be decoded"), } } } impl fmt::Display for ParseSignatureError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Error when parsing {} from Http Signature", self.0) } } impl Error for ValidateError { fn description(&self) -> &'static str { match *self { ValidateError::Expired => "Http Signature is expired", ValidateError::Decode => "Http Signature could not be decoded", } } } impl Error for ParseSignatureError { fn description(&self) -> &'static str { "There was an error parsing the Http Signature" } }