Remove chrono, thiserror

This commit is contained in:
Aode (Lion) 2022-01-17 16:49:01 -06:00
parent d42c0464a4
commit bc34e8e054
9 changed files with 122 additions and 74 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization" name = "http-signature-normalization"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.5.4" version = "0.6.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -20,5 +20,4 @@ members = [
] ]
[dependencies] [dependencies]
chrono = "0.4" httpdate = "1"
thiserror = "1.0"

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-actix" name = "http-signature-normalization-actix"
description = "An HTTP Signatures library that leaves the signing to you" 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 <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -27,14 +27,14 @@ name = "client"
required-features = ["client", "sha-2"] required-features = ["client", "sha-2"]
[dependencies] [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-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 } awc = { version = "3.0.0-beta.12", default-features = false, optional = true }
base64 = { version = "0.13", optional = true } base64 = { version = "0.13", optional = true }
chrono = "0.4.6"
futures-util = { version = "0.3", default-features = false } 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 } sha2 = { version = "0.10", optional = true }
sha3 = { version = "0.10", optional = true } sha3 = { version = "0.10", optional = true }
thiserror = "1.0" thiserror = "1.0"

View file

@ -159,7 +159,7 @@
//! } //! }
//! ``` //! ```
use chrono::Duration; use std::time::Duration;
#[cfg(any(feature = "client", feature = "server"))] #[cfg(any(feature = "client", feature = "server"))]
use actix_http::{ use actix_http::{

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-http" name = "http-signature-normalization-http"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.3.0" version = "0.4.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "../LICENSE" license-file = "../LICENSE"
readme = "../README.md" readme = "../README.md"
@ -13,4 +13,4 @@ edition = "2018"
[dependencies] [dependencies]
http = "0.2" http = "0.2"
http-signature-normalization = { version = "0.5.0", path = ".." } http-signature-normalization = { version = "0.6.0", path = ".." }

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-reqwest" name = "http-signature-normalization-reqwest"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.4.0" version = "0.5.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -24,9 +24,8 @@ required-features = ["sha-2"]
[dependencies] [dependencies]
base64 = { version = "0.13", optional = true } base64 = { version = "0.13", optional = true }
bytes = "1" bytes = "1"
chrono = "0.4.10"
http = "0.2.0" 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 = { version = "0.11", default-features = false, features = ["json"] }
reqwest-middleware = { version = "0.1.2", optional = true } reqwest-middleware = { version = "0.1.2", optional = true }
sha2 = { version = "0.10", optional = true } sha2 = { version = "0.10", optional = true }

View file

@ -1,10 +1,9 @@
use chrono::Duration;
use http_signature_normalization::create::Signed; use http_signature_normalization::create::Signed;
use reqwest::{ use reqwest::{
header::{InvalidHeaderValue, ToStrError}, header::{InvalidHeaderValue, ToStrError},
Request, RequestBuilder, Request, RequestBuilder,
}; };
use std::fmt::Display; use std::{fmt::Display, time::Duration};
pub use http_signature_normalization::RequiredError; pub use http_signature_normalization::RequiredError;

View file

@ -1,10 +1,9 @@
//! Types and logic for creating signature and authorization headers //! Types and logic for creating signature and authorization headers
use chrono::{DateTime, Utc};
use crate::{ use crate::{
ALGORITHM_FIELD, ALGORITHM_VALUE, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, unix_timestamp, ALGORITHM_FIELD, ALGORITHM_VALUE, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD,
SIGNATURE_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD,
}; };
use std::time::SystemTime;
#[derive(Debug)] #[derive(Debug)]
/// The signed stage of creating a signature /// The signed stage of creating a signature
@ -13,8 +12,8 @@ use crate::{
pub struct Signed { pub struct Signed {
signature: String, signature: String,
sig_headers: Vec<String>, sig_headers: Vec<String>,
created: Option<DateTime<Utc>>, created: Option<SystemTime>,
expires: Option<DateTime<Utc>>, expires: Option<SystemTime>,
key_id: String, key_id: String,
} }
@ -26,8 +25,8 @@ pub struct Signed {
pub struct Unsigned { pub struct Unsigned {
pub(crate) signing_string: String, pub(crate) signing_string: String,
pub(crate) sig_headers: Vec<String>, pub(crate) sig_headers: Vec<String>,
pub(crate) created: Option<DateTime<Utc>>, pub(crate) created: Option<SystemTime>,
pub(crate) expires: Option<DateTime<Utc>>, pub(crate) expires: Option<SystemTime>,
} }
impl Signed { impl Signed {
@ -51,8 +50,8 @@ impl Signed {
vec![ vec![
(KEY_ID_FIELD, self.key_id), (KEY_ID_FIELD, self.key_id),
(ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()), (ALGORITHM_FIELD, ALGORITHM_VALUE.to_owned()),
(CREATED_FIELD, created.timestamp().to_string()), (CREATED_FIELD, unix_timestamp(created).to_string()),
(EXPIRES_FIELD, expires.timestamp().to_string()), (EXPIRES_FIELD, unix_timestamp(expires).to_string()),
(HEADERS_FIELD, self.sig_headers.join(" ")), (HEADERS_FIELD, self.sig_headers.join(" ")),
(SIGNATURE_FIELD, self.signature), (SIGNATURE_FIELD, self.signature),
] ]

View file

@ -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. //! 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 //! ```rust
//! use chrono::Duration;
//! use http_signature_normalization::Config; //! use http_signature_normalization::Config;
//! use std::collections::BTreeMap; //! use std::{collections::BTreeMap, time::Duration};
//! //!
//! fn main() -> Result<(), Box<dyn std::error::Error>> { //! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let config = Config::default().set_expiration(Duration::seconds(5)); //! let config = Config::default().set_expiration(Duration::from_secs(5));
//! //!
//! let headers = BTreeMap::new(); //! let headers = BTreeMap::new();
//! //!
@ -42,8 +41,11 @@
//! } //! }
//! ``` //! ```
use chrono::{DateTime, Duration, Utc}; use std::{
use std::collections::{BTreeMap, HashSet}; collections::{BTreeMap, HashSet},
num::ParseIntError,
time::{Duration, SystemTime, UNIX_EPOCH},
};
pub mod create; pub mod create;
pub mod verify; pub mod verify;
@ -76,29 +78,69 @@ pub struct Config {
required_headers: HashSet<String>, required_headers: HashSet<String>,
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug)]
/// Error preparing a header for validation /// Error preparing a header for validation
/// ///
/// This could be due to a missing header, and unparsable header, or an expired header /// This could be due to a missing header, and unparsable header, or an expired header
pub enum PrepareVerifyError { pub enum PrepareVerifyError {
#[error("{0}")]
/// Error validating the header /// Error validating the header
Validate(#[from] ValidateError), Validate(ValidateError),
#[error("{0}")]
/// Error parsing the header /// Error parsing the header
Parse(#[from] ParseSignatureError), Parse(ParseSignatureError),
#[error("{0}")]
/// Missing required headers /// Missing required headers
Required(#[from] RequiredError), Required(RequiredError),
} }
#[derive(Debug, thiserror::Error)] impl std::fmt::Display for PrepareVerifyError {
#[error("Missing required headers {0:?}")] 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<ValidateError> for PrepareVerifyError {
fn from(e: ValidateError) -> Self {
Self::Validate(e)
}
}
impl From<ParseSignatureError> for PrepareVerifyError {
fn from(e: ParseSignatureError) -> Self {
Self::Parse(e)
}
}
impl From<RequiredError> for PrepareVerifyError {
fn from(e: RequiredError) -> Self {
Self::Required(e)
}
}
#[derive(Debug)]
/// Failed to build a signing string due to missing required headers /// Failed to build a signing string due to missing required headers
pub struct RequiredError(HashSet<String>); pub struct RequiredError(HashSet<String>);
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 { impl RequiredError {
/// Retrieve the missing headers from the error /// Retrieve the missing headers from the error
pub fn headers(&self) -> &HashSet<String> { pub fn headers(&self) -> &HashSet<String> {
@ -169,7 +211,7 @@ impl Config {
let sig_headers = self.build_headers_list(&headers); let sig_headers = self.build_headers_list(&headers);
let (created, expires) = if self.use_created_field { let (created, expires) = if self.use_created_field {
let created = Utc::now(); let created = SystemTime::now();
let expires = created + self.expires_after; let expires = created + self.expires_after;
(Some(created), Some(expires)) (Some(created), Some(expires))
@ -246,8 +288,8 @@ impl Config {
fn build_signing_string( fn build_signing_string(
method: &str, method: &str,
path_and_query: &str, path_and_query: &str,
created: Option<DateTime<Utc>>, created: Option<SystemTime>,
expires: Option<DateTime<Utc>>, expires: Option<SystemTime>,
sig_headers: &[String], sig_headers: &[String],
btm: &mut BTreeMap<String, String>, btm: &mut BTreeMap<String, String>,
mut required_headers: HashSet<String>, mut required_headers: HashSet<String>,
@ -256,10 +298,10 @@ fn build_signing_string(
btm.insert(REQUEST_TARGET.to_owned(), request_target); btm.insert(REQUEST_TARGET.to_owned(), request_target);
if let Some(created) = created { 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 { 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 let signing_string = sig_headers
@ -284,13 +326,24 @@ fn build_signing_string(
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
expires_after: Duration::seconds(10), expires_after: Duration::from_secs(10),
use_created_field: true, use_created_field: true,
required_headers: HashSet::new(), 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<SystemTime, ParseIntError> {
let u: u64 = s.parse()?;
Ok(UNIX_EPOCH + Duration::from_secs(u))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Config; use super::Config;

View file

@ -1,15 +1,15 @@
//! Types and methods to verify a signature or authorization header //! 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::{ use std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
error::Error, error::Error,
fmt, fmt,
str::FromStr, str::FromStr,
}; time::{Duration, SystemTime},
use crate::{
build_signing_string, RequiredError, ALGORITHM_FIELD, CREATED, CREATED_FIELD, EXPIRES_FIELD,
HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -33,9 +33,9 @@ pub struct Unvalidated {
pub(crate) key_id: String, pub(crate) key_id: String,
pub(crate) signature: String, pub(crate) signature: String,
pub(crate) algorithm: Option<Algorithm>, pub(crate) algorithm: Option<Algorithm>,
pub(crate) created: Option<DateTime<Utc>>, pub(crate) created: Option<SystemTime>,
pub(crate) expires: Option<DateTime<Utc>>, pub(crate) expires: Option<SystemTime>,
pub(crate) parsed_at: DateTime<Utc>, pub(crate) parsed_at: SystemTime,
pub(crate) date: Option<String>, pub(crate) date: Option<String>,
pub(crate) signing_string: String, pub(crate) signing_string: String,
} }
@ -47,9 +47,9 @@ pub struct ParsedHeader {
key_id: String, key_id: String,
headers: Vec<String>, headers: Vec<String>,
algorithm: Option<Algorithm>, algorithm: Option<Algorithm>,
created: Option<DateTime<Utc>>, created: Option<SystemTime>,
expires: Option<DateTime<Utc>>, expires: Option<SystemTime>,
parsed_at: DateTime<Utc>, parsed_at: SystemTime,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -190,8 +190,9 @@ impl Unvalidated {
} }
if let Some(date) = self.date { if let Some(date) = self.date {
if let Ok(datetime) = DateTime::parse_from_rfc2822(&date) { if let Ok(datetime) = date.parse::<HttpDate>() {
let datetime: DateTime<Utc> = datetime.into(); let system_time = SystemTime::from(datetime);
let datetime: SystemTime = system_time.into();
if datetime + expires_after < self.parsed_at { if datetime + expires_after < self.parsed_at {
return Err(ValidateError::Expired); return Err(ValidateError::Expired);
} }
@ -274,7 +275,7 @@ impl FromStr for ParsedHeader {
algorithm: hm.remove(ALGORITHM_FIELD).map(Algorithm::from), algorithm: hm.remove(ALGORITHM_FIELD).map(Algorithm::from),
created: parse_time(&mut hm, CREATED_FIELD)?, created: parse_time(&mut hm, CREATED_FIELD)?,
expires: parse_time(&mut hm, EXPIRES_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( fn parse_time(
hm: &mut HashMap<String, String>, hm: &mut HashMap<String, String>,
key: &'static str, key: &'static str,
) -> Result<Option<DateTime<Utc>>, ParseSignatureError> { ) -> Result<Option<SystemTime>, ParseSignatureError> {
let r = hm.remove(key).map(|s| { let r = hm
s.parse() .remove(key)
.map(|timestamp| Utc.timestamp(timestamp, 0)) .map(|s| parse_unix_timestamp(&s).map_err(|_| ParseSignatureError(key)));
.map_err(|_| ParseSignatureError(key))
});
match r { match r {
Some(Ok(t)) => Ok(Some(t)), Some(Ok(t)) => Ok(Some(t)),
@ -392,14 +391,14 @@ impl Error for ValidateError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use chrono::Utc;
use super::ParsedHeader; use super::ParsedHeader;
use crate::unix_timestamp;
use std::time::SystemTime;
#[test] #[test]
fn parses_header_succesfully_1() { fn parses_header_succesfully_1() {
let time1 = Utc::now().timestamp(); let time1 = unix_timestamp(SystemTime::now());
let time2 = Utc::now().timestamp(); let time2 = unix_timestamp(SystemTime::now());
let h = format!( 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""#, 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] #[test]
fn parses_header_succesfully_2() { fn parses_header_succesfully_2() {
let time1 = Utc::now().timestamp(); let time1 = unix_timestamp(SystemTime::now());
let time2 = Utc::now().timestamp(); let time2 = unix_timestamp(SystemTime::now());
let h = format!( let h = format!(
r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#, r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#,
@ -424,7 +423,7 @@ mod tests {
#[test] #[test]
fn parses_header_succesfully_3() { fn parses_header_succesfully_3() {
let time1 = Utc::now().timestamp(); let time1 = unix_timestamp(SystemTime::now());
let h = format!( let h = format!(
r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#, r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#,