http-signature-normalization/src/verify.rs
2019-09-13 17:56:06 -05:00

307 lines
8.4 KiB
Rust

use chrono::{DateTime, Duration, TimeZone, Utc};
use std::{
collections::{BTreeMap, HashMap},
error::Error,
fmt,
str::FromStr,
};
use crate::{
build_signing_string, ALGORITHM_FIELD, CREATED, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD,
KEY_ID_FIELD, SIGNATURE_FIELD,
};
#[derive(Debug)]
pub struct Unverified {
key_id: String,
signature: String,
algorithm: Option<Algorithm>,
signing_string: String,
}
#[derive(Debug)]
pub struct Unvalidated {
pub(crate) key_id: String,
pub(crate) signature: String,
pub(crate) algorithm: Option<Algorithm>,
pub(crate) created: Option<DateTime<Utc>>,
pub(crate) expires: Option<DateTime<Utc>>,
pub(crate) parsed_at: DateTime<Utc>,
pub(crate) signing_string: String,
}
#[derive(Debug)]
pub struct ParsedHeader {
signature: String,
key_id: String,
headers: Vec<String>,
algorithm: Option<Algorithm>,
created: Option<DateTime<Utc>>,
expires: Option<DateTime<Utc>>,
parsed_at: DateTime<Utc>,
}
#[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 {
Missing,
Expired,
}
#[derive(Clone, Debug)]
pub struct ParseSignatureError(&'static str);
impl Unverified {
pub fn key_id(&self) -> &str {
&self.key_id
}
pub fn algorithm(&self) -> Option<&Algorithm> {
self.algorithm.as_ref()
}
pub fn verify<F, T>(&self, f: F) -> T
where
F: FnOnce(&str, &str) -> T,
{
(f)(&self.signature, &self.signing_string)
}
}
impl Unvalidated {
pub fn validate(self, expires_after: Duration) -> Result<Unverified, ValidateError> {
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);
}
}
Ok(Unverified {
key_id: self.key_id,
algorithm: self.algorithm,
signing_string: self.signing_string,
signature: self.signature,
})
}
}
impl ParsedHeader {
pub fn into_unvalidated(
self,
method: &str,
path_and_query: &str,
headers: &mut BTreeMap<String, String>,
) -> Unvalidated {
let signing_string = build_signing_string(
method,
path_and_query,
self.created,
self.expires,
&self.headers,
headers,
);
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<Self, Self::Err> {
let s = s.trim_start_matches("Signature").trim();
let mut hm: HashMap<String, String> = 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<String, String>,
key: &'static str,
) -> Result<Option<DateTime<Utc>>, 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<DeprecatedAlgorithm> for Algorithm {
fn from(d: DeprecatedAlgorithm) -> Algorithm {
Algorithm::Deprecated(d)
}
}
impl From<String> 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 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);
}
}