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]
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 <asonix@asonix.dog>"]
license-file = "LICENSE"
readme = "README.md"
@ -20,5 +20,4 @@ members = [
]
[dependencies]
chrono = "0.4"
thiserror = "1.0"
httpdate = "1"

View file

@ -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 <asonix@asonix.dog>"]
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"

View file

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

View file

@ -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 <asonix@asonix.dog>"]
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 = ".." }

View file

@ -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 <asonix@asonix.dog>"]
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 }

View file

@ -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;

View file

@ -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<String>,
created: Option<DateTime<Utc>>,
expires: Option<DateTime<Utc>>,
created: Option<SystemTime>,
expires: Option<SystemTime>,
key_id: String,
}
@ -26,8 +25,8 @@ pub struct Signed {
pub struct Unsigned {
pub(crate) signing_string: String,
pub(crate) sig_headers: Vec<String>,
pub(crate) created: Option<DateTime<Utc>>,
pub(crate) expires: Option<DateTime<Utc>>,
pub(crate) created: Option<SystemTime>,
pub(crate) expires: Option<SystemTime>,
}
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),
]

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.
//!
//! ```rust
//! use chrono::Duration;
//! use http_signature_normalization::Config;
//! use std::collections::BTreeMap;
//! use std::{collections::BTreeMap, time::Duration};
//!
//! 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();
//!
@ -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<String>,
}
#[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<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
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 {
/// Retrieve the missing headers from the error
pub fn headers(&self) -> &HashSet<String> {
@ -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<DateTime<Utc>>,
expires: Option<DateTime<Utc>>,
created: Option<SystemTime>,
expires: Option<SystemTime>,
sig_headers: &[String],
btm: &mut BTreeMap<String, String>,
mut required_headers: HashSet<String>,
@ -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<SystemTime, ParseIntError> {
let u: u64 = s.parse()?;
Ok(UNIX_EPOCH + Duration::from_secs(u))
}
#[cfg(test)]
mod tests {
use super::Config;

View file

@ -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<Algorithm>,
pub(crate) created: Option<DateTime<Utc>>,
pub(crate) expires: Option<DateTime<Utc>>,
pub(crate) parsed_at: DateTime<Utc>,
pub(crate) created: Option<SystemTime>,
pub(crate) expires: Option<SystemTime>,
pub(crate) parsed_at: SystemTime,
pub(crate) date: Option<String>,
pub(crate) signing_string: String,
}
@ -47,9 +47,9 @@ pub struct ParsedHeader {
key_id: String,
headers: Vec<String>,
algorithm: Option<Algorithm>,
created: Option<DateTime<Utc>>,
expires: Option<DateTime<Utc>>,
parsed_at: DateTime<Utc>,
created: Option<SystemTime>,
expires: Option<SystemTime>,
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<Utc> = datetime.into();
if let Ok(datetime) = date.parse::<HttpDate>() {
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<String, String>,
key: &'static str,
) -> Result<Option<DateTime<Utc>>, ParseSignatureError> {
let r = hm.remove(key).map(|s| {
s.parse()
.map(|timestamp| Utc.timestamp(timestamp, 0))
.map_err(|_| ParseSignatureError(key))
});
) -> Result<Option<SystemTime>, 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""#,