2019-09-21 16:26:11 +00:00
#![ deny(missing_docs) ]
//! # HTTP Signature Normaliztion
//! _An HTTP Signatures library that leaves the signing to you_
//!
//! - [crates.io](https://crates.io/crates/http-signature-normalization)
//! - [docs.rs](https://docs.rs/http-signature-normalization)
//! - [Join the discussion on Matrix](https://matrix.to/#/!IRQaBCMWKbpBWKjQgx:asonix.dog?via=asonix.dog)
//!
//! 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;
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let config = Config {
//! expires_after: Duration::seconds(5),
//! };
//!
//! let headers = BTreeMap::new();
//!
//! let signature_header_value = config
//! .begin_sign("GET", "/foo?bar=baz", headers)
//! .sign("my-key-id".to_owned(), |signing_string| {
//! // sign the string here
//! Ok(signing_string.to_owned()) as Result<_, Box<dyn std::error::Error>>
//! })?
//! .signature_header();
//!
//! let mut headers = BTreeMap::new();
//! headers.insert("Signature".to_owned(), signature_header_value);
//!
//! let verified = config
//! .begin_verify("GET", "/foo?bar=baz", headers)?
//! .verify(|sig, signing_string| {
//! // Verify the signature here
//! sig == signing_string
//! });
//!
//! assert!(verified);
//! Ok(())
//! }
//! ```
2019-09-11 05:17:30 +00:00
use chrono ::{ DateTime , Duration , Utc } ;
2019-09-11 06:24:51 +00:00
use std ::{ collections ::BTreeMap , error ::Error , fmt } ;
2019-09-11 05:17:30 +00:00
pub mod create ;
pub mod verify ;
use self ::{
create ::Unsigned ,
2019-09-11 06:24:51 +00:00
verify ::{ ParseSignatureError , ParsedHeader , Unverified , ValidateError } ,
2019-09-11 05:17:30 +00:00
} ;
const REQUEST_TARGET : & 'static str = " (request-target) " ;
2019-09-11 06:24:51 +00:00
const CREATED : & 'static str = " (created) " ;
2019-09-11 05:17:30 +00:00
const EXPIRES : & 'static str = " (expires) " ;
const KEY_ID_FIELD : & 'static str = " keyId " ;
const ALGORITHM_FIELD : & 'static str = " algorithm " ;
const ALGORITHM_VALUE : & 'static str = " hs2019 " ;
const CREATED_FIELD : & 'static str = " created " ;
const EXPIRES_FIELD : & 'static str = " expires " ;
const HEADERS_FIELD : & 'static str = " headers " ;
const SIGNATURE_FIELD : & 'static str = " signature " ;
2019-09-11 06:24:51 +00:00
#[ derive(Clone, Debug) ]
2019-09-21 16:26:11 +00:00
/// Configuration for signing and verifying signatures
///
/// Currently, the only configuration provided is how long a signature should be considered valid
/// before it expires.
2019-09-11 05:17:30 +00:00
pub struct Config {
2019-09-21 16:26:11 +00:00
/// How long a singature is valid
2019-09-11 06:24:51 +00:00
pub expires_after : Duration ,
}
#[ derive(Debug) ]
2019-09-21 16:26:11 +00:00
/// 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 validating the header
2019-09-11 06:24:51 +00:00
Validate ( ValidateError ) ,
2019-09-21 16:26:11 +00:00
/// Error parsing the header
2019-09-11 06:24:51 +00:00
Parse ( ParseSignatureError ) ,
2019-09-11 05:17:30 +00:00
}
impl Config {
2019-09-21 16:26:11 +00:00
/// Perform the neccessary operations to produce an [`Unsigned`] type, which can be used to
/// sign the header
2019-09-11 06:24:51 +00:00
pub fn begin_sign (
2019-09-11 05:17:30 +00:00
& self ,
2019-09-11 05:24:04 +00:00
method : & str ,
path_and_query : & str ,
2019-09-11 06:24:51 +00:00
headers : BTreeMap < String , String > ,
2019-09-11 05:24:04 +00:00
) -> Unsigned {
2019-09-11 06:24:51 +00:00
let mut headers = headers
. into_iter ( )
. map ( | ( k , v ) | ( k . to_lowercase ( ) , v ) )
. collect ( ) ;
let sig_headers = build_headers_list ( & headers ) ;
2019-09-11 05:17:30 +00:00
let created = Utc ::now ( ) ;
2019-09-11 06:24:51 +00:00
let expires = created + self . expires_after ;
2019-09-11 05:17:30 +00:00
let signing_string = build_signing_string (
method ,
path_and_query ,
Some ( created ) ,
Some ( expires ) ,
& sig_headers ,
2019-09-11 06:24:51 +00:00
& mut headers ,
2019-09-11 05:17:30 +00:00
) ;
2019-09-11 05:24:04 +00:00
Unsigned {
2019-09-11 05:17:30 +00:00
signing_string ,
sig_headers ,
created ,
expires ,
2019-09-11 05:24:04 +00:00
}
2019-09-11 05:17:30 +00:00
}
2019-09-21 16:26:11 +00:00
/// Perform the neccessary operations to produce and [`Unerified`] type, which can be used to
/// verify the header
2019-09-11 06:24:51 +00:00
pub fn begin_verify (
& self ,
method : & str ,
path_and_query : & str ,
headers : BTreeMap < String , String > ,
2019-09-21 16:26:11 +00:00
) -> Result < Unverified , PrepareVerifyError > {
2019-09-11 06:24:51 +00:00
let mut headers : BTreeMap < String , String > = headers
. into_iter ( )
. map ( | ( k , v ) | ( k . to_lowercase ( ) . to_owned ( ) , v ) )
. collect ( ) ;
let header = headers
. remove ( " authorization " )
. or_else ( | | headers . remove ( " signature " ) )
. ok_or ( ValidateError ::Missing ) ? ;
let parsed_header : ParsedHeader = header . parse ( ) ? ;
let unvalidated = parsed_header . into_unvalidated ( method , path_and_query , & mut headers ) ;
Ok ( unvalidated . validate ( self . expires_after ) ? )
2019-09-11 05:17:30 +00:00
}
}
2019-09-11 05:24:04 +00:00
fn build_headers_list ( btm : & BTreeMap < String , String > ) -> Vec < String > {
2019-09-11 05:17:30 +00:00
let http_header_keys : Vec < String > = btm . keys ( ) . cloned ( ) . collect ( ) ;
let mut sig_headers = vec! [
REQUEST_TARGET . to_owned ( ) ,
CREATED . to_owned ( ) ,
EXPIRES . to_owned ( ) ,
] ;
sig_headers . extend ( http_header_keys ) ;
2019-09-11 05:24:04 +00:00
sig_headers
2019-09-11 05:17:30 +00:00
}
fn build_signing_string (
2019-09-11 05:24:04 +00:00
method : & str ,
path_and_query : & str ,
2019-09-11 05:17:30 +00:00
created : Option < DateTime < Utc > > ,
expires : Option < DateTime < Utc > > ,
sig_headers : & [ String ] ,
btm : & mut BTreeMap < String , String > ,
) -> String {
let request_target = format! ( " {} {} " , method . to_string ( ) . to_lowercase ( ) , path_and_query ) ;
btm . insert ( REQUEST_TARGET . to_owned ( ) , request_target . clone ( ) ) ;
if let Some ( created ) = created {
btm . insert ( CREATED . to_owned ( ) , created . timestamp ( ) . to_string ( ) ) ;
}
if let Some ( expires ) = expires {
btm . insert ( EXPIRES . to_owned ( ) , expires . timestamp ( ) . to_string ( ) ) ;
}
let signing_string = sig_headers
. iter ( )
. filter_map ( | h | btm . remove ( h ) . map ( | v | format! ( " {} : {} " , h , v ) ) )
. collect ::< Vec < _ > > ( )
. join ( " \n " ) ;
signing_string
}
2019-09-21 16:26:11 +00:00
impl fmt ::Display for PrepareVerifyError {
2019-09-11 06:24:51 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match * self {
2019-09-21 16:26:11 +00:00
PrepareVerifyError ::Validate ( ref e ) = > fmt ::Display ::fmt ( e , f ) ,
PrepareVerifyError ::Parse ( ref e ) = > fmt ::Display ::fmt ( e , f ) ,
2019-09-11 06:24:51 +00:00
}
}
}
2019-09-21 16:26:11 +00:00
impl Error for PrepareVerifyError {
2019-09-11 06:24:51 +00:00
fn description ( & self ) -> & str {
match * self {
2019-09-21 16:26:11 +00:00
PrepareVerifyError ::Validate ( ref e ) = > e . description ( ) ,
PrepareVerifyError ::Parse ( ref e ) = > e . description ( ) ,
2019-09-11 06:24:51 +00:00
}
}
fn source ( & self ) -> Option < & ( dyn Error + 'static ) > {
match * self {
2019-09-21 16:26:11 +00:00
PrepareVerifyError ::Validate ( ref e ) = > Some ( e ) ,
PrepareVerifyError ::Parse ( ref e ) = > Some ( e ) ,
2019-09-11 06:24:51 +00:00
}
}
}
2019-09-21 16:26:11 +00:00
impl From < ValidateError > for PrepareVerifyError {
2019-09-11 06:24:51 +00:00
fn from ( v : ValidateError ) -> Self {
2019-09-21 16:26:11 +00:00
PrepareVerifyError ::Validate ( v )
2019-09-11 06:24:51 +00:00
}
}
2019-09-21 16:26:11 +00:00
impl From < ParseSignatureError > for PrepareVerifyError {
2019-09-11 06:24:51 +00:00
fn from ( p : ParseSignatureError ) -> Self {
2019-09-21 16:26:11 +00:00
PrepareVerifyError ::Parse ( p )
2019-09-11 06:24:51 +00:00
}
}
2019-09-11 05:17:30 +00:00
impl Default for Config {
fn default ( ) -> Self {
Config {
2019-09-11 06:24:51 +00:00
expires_after : Duration ::seconds ( 10 ) ,
2019-09-11 05:17:30 +00:00
}
}
}
#[ cfg(test) ]
mod tests {
2019-09-11 06:24:51 +00:00
use super ::Config ;
use std ::collections ::BTreeMap ;
fn prepare_headers ( ) -> BTreeMap < String , String > {
let mut headers = BTreeMap ::new ( ) ;
headers . insert (
" Content-Type " . to_owned ( ) ,
" application/activity+json " . to_owned ( ) ,
) ;
headers
}
2019-09-11 05:17:30 +00:00
#[ test ]
2019-09-21 16:26:11 +00:00
fn round_trip_authorization ( ) {
2019-09-11 06:24:51 +00:00
let headers = prepare_headers ( ) ;
let config = Config ::default ( ) ;
let authorization_header = config
. begin_sign ( " GET " , " /foo?bar=baz " , headers )
. sign ( " hi " . to_owned ( ) , | s | {
2019-09-13 22:55:51 +00:00
Ok ( s . to_owned ( ) ) as Result < _ , std ::io ::Error >
2019-09-11 06:24:51 +00:00
} )
. unwrap ( )
. authorization_header ( ) ;
let mut headers = prepare_headers ( ) ;
headers . insert ( " Authorization " . to_owned ( ) , authorization_header ) ;
let verified = config
. begin_verify ( " GET " , " /foo?bar=baz " , headers )
. unwrap ( )
2019-09-13 22:55:51 +00:00
. verify ( | sig , signing_string | sig = = signing_string ) ;
2019-09-11 06:24:51 +00:00
assert! ( verified ) ;
2019-09-11 05:17:30 +00:00
}
2019-09-21 16:26:11 +00:00
#[ test ]
fn round_trip_signature ( ) {
let headers = prepare_headers ( ) ;
let config = Config ::default ( ) ;
let signature_header = config
. begin_sign ( " GET " , " /foo?bar=baz " , headers )
. sign ( " hi " . to_owned ( ) , | s | {
Ok ( s . to_owned ( ) ) as Result < _ , std ::io ::Error >
} )
. unwrap ( )
. signature_header ( ) ;
let mut headers = prepare_headers ( ) ;
headers . insert ( " Signature " . to_owned ( ) , signature_header ) ;
let verified = config
. begin_verify ( " GET " , " /foo?bar=baz " , headers )
. unwrap ( )
. verify ( | sig , signing_string | sig = = signing_string ) ;
assert! ( verified ) ;
}
2019-09-11 05:17:30 +00:00
}