From bc34e8e054308529ab15b281df5d8e849bc8061f Mon Sep 17 00:00:00 2001 From: "Aode (Lion)" Date: Mon, 17 Jan 2022 16:49:01 -0600 Subject: [PATCH] Remove chrono, thiserror --- Cargo.toml | 5 +- http-signature-normalization-actix/Cargo.toml | 10 +- http-signature-normalization-actix/src/lib.rs | 2 +- http-signature-normalization-http/Cargo.toml | 4 +- .../Cargo.toml | 5 +- .../src/lib.rs | 3 +- src/create.rs | 19 ++-- src/lib.rs | 93 +++++++++++++++---- src/verify.rs | 55 ++++++----- 9 files changed, 122 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da16a56..09a82e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.5.4" +version = "0.6.0" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -20,5 +20,4 @@ members = [ ] [dependencies] -chrono = "0.4" -thiserror = "1.0" +httpdate = "1" diff --git a/http-signature-normalization-actix/Cargo.toml b/http-signature-normalization-actix/Cargo.toml index 84eef51..0271439 100644 --- a/http-signature-normalization-actix/Cargo.toml +++ b/http-signature-normalization-actix/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-actix" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.5.0-beta.14" +version = "0.6.0-beta.0" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -27,14 +27,14 @@ name = "client" required-features = ["client", "sha-2"] [dependencies] -actix-http = { version = "3.0.0-beta.14", default-features = false } +actix-http = { version = "=3.0.0-beta.18", default-features = false } actix-rt = "2.5.0" -actix-web = { version = "4.0.0-beta.13", default-features = false, optional = true } +actix-router = "=0.5.0-beta.4" +actix-web = { version = "=4.0.0-beta.19", default-features = false, optional = true } awc = { version = "3.0.0-beta.12", default-features = false, optional = true } base64 = { version = "0.13", optional = true } -chrono = "0.4.6" futures-util = { version = "0.3", default-features = false } -http-signature-normalization = { version = "0.5.1", path = ".." } +http-signature-normalization = { version = "0.6.0", path = ".." } sha2 = { version = "0.10", optional = true } sha3 = { version = "0.10", optional = true } thiserror = "1.0" diff --git a/http-signature-normalization-actix/src/lib.rs b/http-signature-normalization-actix/src/lib.rs index b97bc23..1ee71db 100644 --- a/http-signature-normalization-actix/src/lib.rs +++ b/http-signature-normalization-actix/src/lib.rs @@ -159,7 +159,7 @@ //! } //! ``` -use chrono::Duration; +use std::time::Duration; #[cfg(any(feature = "client", feature = "server"))] use actix_http::{ diff --git a/http-signature-normalization-http/Cargo.toml b/http-signature-normalization-http/Cargo.toml index feec389..0de81fe 100644 --- a/http-signature-normalization-http/Cargo.toml +++ b/http-signature-normalization-http/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-http" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.3.0" +version = "0.4.0" authors = ["asonix "] license-file = "../LICENSE" readme = "../README.md" @@ -13,4 +13,4 @@ edition = "2018" [dependencies] http = "0.2" -http-signature-normalization = { version = "0.5.0", path = ".." } +http-signature-normalization = { version = "0.6.0", path = ".." } diff --git a/http-signature-normalization-reqwest/Cargo.toml b/http-signature-normalization-reqwest/Cargo.toml index 0c9d626..a2295a3 100644 --- a/http-signature-normalization-reqwest/Cargo.toml +++ b/http-signature-normalization-reqwest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-reqwest" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.4.0" +version = "0.5.0" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -24,9 +24,8 @@ required-features = ["sha-2"] [dependencies] base64 = { version = "0.13", optional = true } bytes = "1" -chrono = "0.4.10" http = "0.2.0" -http-signature-normalization = { version = "0.5.1", path = ".." } +http-signature-normalization = { version = "0.6.0", path = ".." } reqwest = { version = "0.11", default-features = false, features = ["json"] } reqwest-middleware = { version = "0.1.2", optional = true } sha2 = { version = "0.10", optional = true } diff --git a/http-signature-normalization-reqwest/src/lib.rs b/http-signature-normalization-reqwest/src/lib.rs index 81e4816..2279eeb 100644 --- a/http-signature-normalization-reqwest/src/lib.rs +++ b/http-signature-normalization-reqwest/src/lib.rs @@ -1,10 +1,9 @@ -use chrono::Duration; use http_signature_normalization::create::Signed; use reqwest::{ header::{InvalidHeaderValue, ToStrError}, Request, RequestBuilder, }; -use std::fmt::Display; +use std::{fmt::Display, time::Duration}; pub use http_signature_normalization::RequiredError; diff --git a/src/create.rs b/src/create.rs index 404987e..65e74ac 100644 --- a/src/create.rs +++ b/src/create.rs @@ -1,10 +1,9 @@ //! Types and logic for creating signature and authorization headers -use chrono::{DateTime, Utc}; - use crate::{ - ALGORITHM_FIELD, ALGORITHM_VALUE, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, - SIGNATURE_FIELD, + 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 @@ -13,8 +12,8 @@ use crate::{ pub struct Signed { signature: String, sig_headers: Vec, - created: Option>, - expires: Option>, + created: Option, + expires: Option, key_id: String, } @@ -26,8 +25,8 @@ pub struct Signed { pub struct Unsigned { pub(crate) signing_string: String, pub(crate) sig_headers: Vec, - pub(crate) created: Option>, - pub(crate) expires: Option>, + pub(crate) created: Option, + pub(crate) expires: Option, } impl Signed { @@ -51,8 +50,8 @@ impl Signed { vec![ (KEY_ID_FIELD, self.key_id), (ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()), - (CREATED_FIELD, created.timestamp().to_string()), - (EXPIRES_FIELD, expires.timestamp().to_string()), + (CREATED_FIELD, unix_timestamp(created).to_string()), + (EXPIRES_FIELD, unix_timestamp(expires).to_string()), (HEADERS_FIELD, self.sig_headers.join(" ")), (SIGNATURE_FIELD, self.signature), ] diff --git a/src/lib.rs b/src/lib.rs index 23c6130..5fb5741 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,11 @@ //! Http Signature Normalization is a minimal-dependency crate for producing HTTP Signatures with user-provided signing and verification. The API is simple; there's a series of steps for creation and verification with types that ensure reasonable usage. //! //! ```rust -//! use chrono::Duration; //! use http_signature_normalization::Config; -//! use std::collections::BTreeMap; +//! use std::{collections::BTreeMap, time::Duration}; //! //! fn main() -> Result<(), Box> { -//! let config = Config::default().set_expiration(Duration::seconds(5)); +//! let config = Config::default().set_expiration(Duration::from_secs(5)); //! //! let headers = BTreeMap::new(); //! @@ -42,8 +41,11 @@ //! } //! ``` -use chrono::{DateTime, Duration, Utc}; -use std::collections::{BTreeMap, HashSet}; +use std::{ + collections::{BTreeMap, HashSet}, + num::ParseIntError, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; pub mod create; pub mod verify; @@ -76,29 +78,69 @@ pub struct Config { required_headers: HashSet, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] /// Error preparing a header for validation /// /// This could be due to a missing header, and unparsable header, or an expired header pub enum PrepareVerifyError { - #[error("{0}")] /// Error validating the header - Validate(#[from] ValidateError), + Validate(ValidateError), - #[error("{0}")] /// Error parsing the header - Parse(#[from] ParseSignatureError), + Parse(ParseSignatureError), - #[error("{0}")] /// Missing required headers - Required(#[from] RequiredError), + Required(RequiredError), } -#[derive(Debug, thiserror::Error)] -#[error("Missing required headers {0:?}")] +impl std::fmt::Display for PrepareVerifyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Validate(ref e) => std::fmt::Display::fmt(e, f), + Self::Parse(ref e) => std::fmt::Display::fmt(e, f), + Self::Required(ref e) => std::fmt::Display::fmt(e, f), + } + } +} + +impl std::error::Error for PrepareVerifyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Validate(ref e) => Some(e), + Self::Parse(ref e) => Some(e), + Self::Required(ref e) => Some(e), + } + } +} + +impl From for PrepareVerifyError { + fn from(e: ValidateError) -> Self { + Self::Validate(e) + } +} +impl From for PrepareVerifyError { + fn from(e: ParseSignatureError) -> Self { + Self::Parse(e) + } +} +impl From for PrepareVerifyError { + fn from(e: RequiredError) -> Self { + Self::Required(e) + } +} + +#[derive(Debug)] /// Failed to build a signing string due to missing required headers pub struct RequiredError(HashSet); +impl std::fmt::Display for RequiredError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Missing required headers {:?}", self.0) + } +} + +impl std::error::Error for RequiredError {} + impl RequiredError { /// Retrieve the missing headers from the error pub fn headers(&self) -> &HashSet { @@ -169,7 +211,7 @@ impl Config { let sig_headers = self.build_headers_list(&headers); let (created, expires) = if self.use_created_field { - let created = Utc::now(); + let created = SystemTime::now(); let expires = created + self.expires_after; (Some(created), Some(expires)) @@ -246,8 +288,8 @@ impl Config { fn build_signing_string( method: &str, path_and_query: &str, - created: Option>, - expires: Option>, + created: Option, + expires: Option, sig_headers: &[String], btm: &mut BTreeMap, mut required_headers: HashSet, @@ -256,10 +298,10 @@ fn build_signing_string( btm.insert(REQUEST_TARGET.to_owned(), request_target); if let Some(created) = created { - btm.insert(CREATED.to_owned(), created.timestamp().to_string()); + btm.insert(CREATED.to_owned(), unix_timestamp(created).to_string()); } if let Some(expires) = expires { - btm.insert(EXPIRES.to_owned(), expires.timestamp().to_string()); + btm.insert(EXPIRES.to_owned(), unix_timestamp(expires).to_string()); } let signing_string = sig_headers @@ -284,13 +326,24 @@ fn build_signing_string( impl Default for Config { fn default() -> Self { Config { - expires_after: Duration::seconds(10), + expires_after: Duration::from_secs(10), use_created_field: true, required_headers: HashSet::new(), } } } +fn unix_timestamp(time: SystemTime) -> u64 { + time.duration_since(UNIX_EPOCH) + .expect("UNIX_EPOCH is never in the future") + .as_secs() +} + +fn parse_unix_timestamp(s: &str) -> Result { + let u: u64 = s.parse()?; + Ok(UNIX_EPOCH + Duration::from_secs(u)) +} + #[cfg(test)] mod tests { use super::Config; diff --git a/src/verify.rs b/src/verify.rs index 13ad2e6..99080a4 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,15 +1,15 @@ //! Types and methods to verify a signature or authorization header -use chrono::{DateTime, Duration, TimeZone, Utc}; +use crate::{ + build_signing_string, parse_unix_timestamp, RequiredError, ALGORITHM_FIELD, CREATED, + CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD, +}; +use httpdate::HttpDate; 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, + time::{Duration, SystemTime}, }; #[derive(Debug)] @@ -33,9 +33,9 @@ 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) created: Option, + pub(crate) expires: Option, + pub(crate) parsed_at: SystemTime, pub(crate) date: Option, pub(crate) signing_string: String, } @@ -47,9 +47,9 @@ pub struct ParsedHeader { key_id: String, headers: Vec, algorithm: Option, - created: Option>, - expires: Option>, - parsed_at: DateTime, + created: Option, + expires: Option, + parsed_at: SystemTime, } #[derive(Clone, Copy, Debug)] @@ -190,8 +190,9 @@ impl Unvalidated { } if let Some(date) = self.date { - if let Ok(datetime) = DateTime::parse_from_rfc2822(&date) { - let datetime: DateTime = datetime.into(); + if let Ok(datetime) = date.parse::() { + let system_time = SystemTime::from(datetime); + let datetime: SystemTime = system_time.into(); if datetime + expires_after < self.parsed_at { return Err(ValidateError::Expired); } @@ -274,7 +275,7 @@ impl FromStr for ParsedHeader { 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(), + parsed_at: SystemTime::now(), }) } } @@ -282,12 +283,10 @@ impl FromStr for ParsedHeader { fn parse_time( hm: &mut HashMap, key: &'static str, -) -> Result>, ParseSignatureError> { - let r = hm.remove(key).map(|s| { - s.parse() - .map(|timestamp| Utc.timestamp(timestamp, 0)) - .map_err(|_| ParseSignatureError(key)) - }); +) -> Result, ParseSignatureError> { + let r = hm + .remove(key) + .map(|s| parse_unix_timestamp(&s).map_err(|_| ParseSignatureError(key))); match r { Some(Ok(t)) => Ok(Some(t)), @@ -392,14 +391,14 @@ impl Error for ValidateError { #[cfg(test)] mod tests { - use chrono::Utc; - use super::ParsedHeader; + use crate::unix_timestamp; + use std::time::SystemTime; #[test] fn parses_header_succesfully_1() { - let time1 = Utc::now().timestamp(); - let time2 = Utc::now().timestamp(); + let time1 = unix_timestamp(SystemTime::now()); + let time2 = unix_timestamp(SystemTime::now()); 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""#, @@ -411,8 +410,8 @@ mod tests { #[test] fn parses_header_succesfully_2() { - let time1 = Utc::now().timestamp(); - let time2 = Utc::now().timestamp(); + let time1 = unix_timestamp(SystemTime::now()); + let time2 = unix_timestamp(SystemTime::now()); let h = format!( r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#, @@ -424,7 +423,7 @@ mod tests { #[test] fn parses_header_succesfully_3() { - let time1 = Utc::now().timestamp(); + let time1 = unix_timestamp(SystemTime::now()); let h = format!( r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#,