//! Types and methods to verify a signature or authorization header use chrono::{DateTime, Duration, TimeZone, Utc}; use std::{ collections::{BTreeMap, HashMap, HashSet}, error::Error, fmt, str::FromStr, }; use crate::{ build_signing_string, RequiredError, ALGORITHM_FIELD, CREATED, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD, }; #[derive(Debug)] /// The Unverified step of the verification process /// /// This type is the result of performing some basic validation on the parsed header, and can be /// used to verify the header pub struct Unverified { key_id: String, signature: String, algorithm: Option, signing_string: String, } #[derive(Debug)] /// The Unvalidated stage /// /// This is created after generating the signing string from a parsed header, and transitions into /// the Unverified type by applying some basic validations 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) date: Option, pub(crate) signing_string: String, } #[derive(Debug)] /// The successful result of parsing a Signature or Authorization header pub struct ParsedHeader { signature: String, key_id: String, headers: Vec, algorithm: Option, created: Option>, expires: Option>, parsed_at: DateTime, } #[derive(Clone, Copy, Debug)] /// Algorithms that may be present in an HTTP Signature's `algorithm` field, but are considered /// deprecated due to security issues /// /// Most of these are Deprecated solely because the presence of the algorithm's name in the request /// could be used to gain insight into ways to forge requests. This doesn't mean that using these /// algorithms to sign and verify requests is bad, it just means that stating which algorithm is in /// use is dangerous. In the case of the SHA1 variants, they were deprecated for being weak /// hashes. /// /// This library only produces HTTP Signatures with the "HS2019" algorithm type, and leaves /// deciding which algorithm to actually use to implementors pub enum DeprecatedAlgorithm { /// HMAC SHA-1 HmacSha1, /// HMAC SHA-256 HmacSha256, /// HMAC SHA-384 HmacSha384, /// HMAC SHA-512 HmacSha512, /// RSA SHA-1 RsaSha1, /// RSA SHA-256 RsaSha256, /// RSA SHA-384 RsaSha384, /// RSA SHA-512 RsaSha512, /// ECDSA SHA-1 EcdsaSha1, /// ECDSA SHA-256 EcdsaSha256, /// ECDSA SHA-384 EcdsaSha384, /// ECDSA SHA-512 EcdsaSha512, } #[derive(Clone, Debug)] /// Kinds of algorithms /// /// This library knows about HS2019 as a supported algorithm, and any other algorithms are either /// unknown at the time of writing, or deprecated pub enum Algorithm { /// The only officially supported algorithm from the current HTTP Signatures specification Hs2019, /// Algorithms that have been used historically, but are deprecated Deprecated(DeprecatedAlgorithm), /// Algorithms that may be used by custom implementations and are unknown to the spec Unknown(String), } #[derive(Clone, Debug)] /// Kinds of errors for validating a request pub enum ValidateError { /// The Authorization or Signature header is not present Missing, /// The request's `created` or `expires` field indicates it is too old to be valid Expired, } #[derive(Clone, Debug)] /// The error produced when parsing the HTTPT Signature fails, including the name of the field that /// was invalid. pub struct ParseSignatureError(&'static str); impl ParseSignatureError { /// Get the name of the missing field pub fn missing_field(&self) -> &'static str { self.0 } } impl Unverified { /// Get the Key ID from an Unverified type /// /// This is useful for looking up the proper verification key to verify the request pub fn key_id(&self) -> &str { &self.key_id } /// Get the Algorithm used in the request, if one is present /// /// If the algorithm is present and is not what an implementor expected, they should not /// attempt to verify the signature pub fn algorithm(&self) -> Option<&Algorithm> { self.algorithm.as_ref() } /// Get the signing string used to create the signature pub fn signing_string(&self) -> &str { &self.signing_string } /// Get the signature itself pub fn signature(&self) -> &str { &self.signature } /// Verify the signature with the signature and the signing string /// /// ```rust,ignore /// unverified.verify(|signature, signing_string| { /// let bytes = match base64::decode(signature) { /// Ok(bytes) => bytes, /// Err(_) => return false, /// }; /// /// public_key /// .verify(bytes, signing_string) /// .unwrap_or(false) /// }) /// ``` pub fn verify(&self, f: F) -> T where F: FnOnce(&str, &str) -> T, { (f)(&self.signature, &self.signing_string) } } impl Unvalidated { /// Validate parts of the header, ensuring that the provided dates don't indicate that it is /// expired. pub fn validate(self, expires_after: Duration) -> Result { if let Some(expires) = self.expires { if expires < self.parsed_at { return Err(ValidateError::Expired); } } if let Some(created) = self.created { if created + expires_after < self.parsed_at { return Err(ValidateError::Expired); } } if let Some(date) = self.date { if let Ok(datetime) = DateTime::parse_from_rfc2822(&date) { let datetime: DateTime = datetime.into(); if datetime + expires_after < self.parsed_at { return Err(ValidateError::Expired); } } } Ok(Unverified { key_id: self.key_id, algorithm: self.algorithm, signing_string: self.signing_string, signature: self.signature, }) } } impl ParsedHeader { /// Generate a Signing String from the header pub fn into_unvalidated( self, method: &str, path_and_query: &str, headers: &mut BTreeMap, required_headers: HashSet, ) -> Result { let date = headers.get("date").cloned(); let signing_string = build_signing_string( method, path_and_query, self.created, self.expires, &self.headers, headers, required_headers, )?; Ok(Unvalidated { key_id: self.key_id, signature: self.signature, parsed_at: self.parsed_at, algorithm: self.algorithm, created: self.created, expires: self.expires, date, 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.trim_matches('"').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(|s| Algorithm::from(s)), 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 DeprecatedAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match self { DeprecatedAlgorithm::HmacSha1 => "hmac-sha1", DeprecatedAlgorithm::HmacSha256 => "hmac-sha256", DeprecatedAlgorithm::HmacSha384 => "hmac-sha384", DeprecatedAlgorithm::HmacSha512 => "hmac-sha512", DeprecatedAlgorithm::RsaSha1 => "rsa-sha1", DeprecatedAlgorithm::RsaSha256 => "rsa-sha256", DeprecatedAlgorithm::RsaSha384 => "rsa-sha384", DeprecatedAlgorithm::RsaSha512 => "rsa-sha512", DeprecatedAlgorithm::EcdsaSha1 => "ecdsa-sha1", DeprecatedAlgorithm::EcdsaSha256 => "ecdsa-sha256", DeprecatedAlgorithm::EcdsaSha384 => "ecdsa-sha384", DeprecatedAlgorithm::EcdsaSha512 => "ecdsa-sha512", }; write!(f, "{}", s) } } impl fmt::Display for Algorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Algorithm::Hs2019 => write!(f, "{}", "hs2019"), Algorithm::Deprecated(d) => d.fmt(f), Algorithm::Unknown(other) => write!(f, "{}", other), } } } 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 ParseSignatureError { fn description(&self) -> &'static str { "There was an error parsing the Http Signature" } } impl fmt::Display for ValidateError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ValidateError::Missing => write!(f, "Http Signature is missing"), ValidateError::Expired => write!(f, "Http Signature is expired"), } } } impl Error for ValidateError { fn description(&self) -> &'static str { match *self { ValidateError::Missing => "Http Signature is missing", ValidateError::Expired => "Http Signature is expired", } } } #[cfg(test)] mod tests { use chrono::Utc; use super::ParsedHeader; #[test] fn parses_header_succesfully_1() { let time1 = Utc::now().timestamp(); let time2 = Utc::now().timestamp(); let h = format!( r#"Signature keyId="my-key-id",algorithm="hs2019",created="{}",expires="{}",headers="(request-target) (created) (expires) date content-type",signature="blah blah blah""#, time1, time2 ); parse_signature(&h) } #[test] fn parses_header_succesfully_2() { let time1 = Utc::now().timestamp(); let time2 = Utc::now().timestamp(); let h = format!( r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#, time1, time2 ); parse_signature(&h) } #[test] fn parses_header_succesfully_3() { let time1 = Utc::now().timestamp(); let h = format!( r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#, time1 ); parse_signature(&h) } #[test] fn parses_header_succesfully_4() { let h = r#"Signature keyId="my-key-id",algorithm="rsa-sha256",headers="(request-target) date content-type",signature="blah blah blah""#; parse_signature(h) } fn parse_signature(s: &str) { let ph: ParsedHeader = s.parse().unwrap(); println!("{:?}", ph); } }