mirror of
https://git.asonix.dog/asonix/http-signature-normalization.git
synced 2024-11-24 10:21:00 +00:00
Remove chrono, thiserror
This commit is contained in:
parent
d42c0464a4
commit
bc34e8e054
9 changed files with 122 additions and 74 deletions
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -159,7 +159,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
|
||||
use chrono::Duration;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(any(feature = "client", feature = "server"))]
|
||||
use actix_http::{
|
||||
|
|
|
@ -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 = ".." }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
|
|
93
src/lib.rs
93
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<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;
|
||||
|
|
|
@ -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""#,
|
||||
|
|
Loading…
Reference in a new issue