http-signature-normalization/http-signature-normalization-actix/src/lib.rs
2019-09-21 11:29:41 -05:00

333 lines
9.7 KiB
Rust

#![deny(missing_docs)]
//! # Integration of Http Signature Normalization with Actix Web
//!
//! This library provides middlewares for verifying HTTP Signature headers and, optionally, Digest
//! headers with the `digest` feature enabled. It also extends actix_web's ClientRequest type to
//! add signatures and digests to the request
//!
//! ### Use it in a server
//! ```rust,ignore
//! use actix::System;
//! use actix_web::{web, App, HttpResponse, HttpServer, ResponseError};
//! use failure::Fail;
//! use http_signature_normalization_actix::{prelude::*, verify::Algorithm};
//! use sha2::{Digest, Sha256};
//!
//! #[derive(Clone, Debug)]
//! struct MyVerify;
//!
//! impl SignatureVerify for MyVerify {
//! type Error = MyError;
//! type Future = Result<bool, Self::Error>;
//!
//! fn signature_verify(
//! &mut self,
//! algorithm: Option<Algorithm>,
//! key_id: &str,
//! signature: &str,
//! signing_string: &str,
//! ) -> Self::Future {
//! match algorithm {
//! Some(Algorithm::Hs2019) => (),
//! _ => return Err(MyError::Algorithm),
//! };
//!
//! if key_id != "my-key-id" {
//! return Err(MyError::Key);
//! }
//!
//! let decoded = base64::decode(signature).map_err(|_| MyError::Decode)?;
//!
//! // In a real system, you'd want to actually verify a signature, not just check for
//! // byte equality
//! Ok(decoded == signing_string.as_bytes())
//! }
//! }
//!
//! fn index(_: (DigestVerified, SignatureVerified)) -> &'static str {
//! "Eyyyyup"
//! }
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let sys = System::new("server-example");
//!
//! let config = Config::default();
//!
//! HttpServer::new(move || {
//! App::new()
//! .wrap(VerifyDigest::new(Sha256::new()).optional())
//! .wrap(
//! VerifySignature::new(MyVerify, config.clone())
//! .authorization()
//! .optional(),
//! )
//! .route("/", web::post().to(index))
//! })
//! .bind("127.0.0.1:8010")?
//! .start();
//!
//! sys.run()?;
//! Ok(())
//! }
//!
//! #[derive(Debug, Fail)]
//! enum MyError {
//! #[fail(display = "Failed to verify, {}", _0)]
//! Verify(#[cause] PrepareVerifyError),
//!
//! #[fail(display = "Unsupported algorithm")]
//! Algorithm,
//!
//! #[fail(display = "Couldn't decode signature")]
//! Decode,
//!
//! #[fail(display = "Invalid key")]
//! Key,
//! }
//!
//! impl ResponseError for MyError {
//! fn error_response(&self) -> HttpResponse {
//! HttpResponse::BadRequest().finish()
//! }
//!
//! fn render_response(&self) -> HttpResponse {
//! self.error_response()
//! }
//! }
//!
//! impl From<PrepareVerifyError> for MyError {
//! fn from(e: PrepareVerifyError) -> Self {
//! MyError::Verify(e)
//! }
//! }
//! ```
//!
//! ### Use it in a client
//! ```rust,ignore
//! use actix::System;
//! use actix_web::client::Client;
//! use failure::Fail;
//! use futures::future::{lazy, Future};
//! use http_signature_normalization_actix::prelude::*;
//! use sha2::{Digest, Sha256};
//!
//! fn main() {
//! System::new("client-example")
//! .block_on(lazy(|| {
//! let config = Config::default();
//! let mut digest = Sha256::new();
//!
//! Client::default()
//! .post("http://127.0.0.1:8010/")
//! .header("User-Agent", "Actix Web")
//! .authorization_signature_with_digest(
//! &config,
//! "my-key-id",
//! &mut digest,
//! "Hewwo-owo",
//! |s| {
//! // In a real-world system, you'd actually want to sign the string,
//! // not just base64 encode it
//! Ok(base64::encode(s)) as Result<_, MyError>
//! },
//! )
//! .unwrap()
//! .send()
//! .map_err(|_| ())
//! .and_then(|mut res| res.body().map_err(|_| ()))
//! .map(|body| {
//! println!("{:?}", body);
//! })
//! }))
//! .unwrap();
//! }
//!
//! #[derive(Debug, Fail)]
//! pub enum MyError {
//! #[fail(display = "Failed to read header, {}", _0)]
//! Convert(#[cause] ToStrError),
//!
//! #[fail(display = "Failed to create header, {}", _0)]
//! Header(#[cause] InvalidHeaderValue),
//! }
//!
//! impl From<ToStrError> for MyError {
//! fn from(e: ToStrError) -> Self {
//! MyError::Convert(e)
//! }
//! }
//!
//! impl From<InvalidHeaderValue> for MyError {
//! fn from(e: InvalidHeaderValue) -> Self {
//! MyError::Header(e)
//! }
//! }
//! ```
use actix_web::http::{
header::{HeaderMap, InvalidHeaderValue, ToStrError},
uri::PathAndQuery,
Method,
};
use failure::Fail;
use futures::future::IntoFuture;
use std::{collections::BTreeMap, fmt::Display};
mod sign;
#[cfg(feature = "digest")]
pub mod digest;
pub mod create;
pub mod middleware;
/// Useful types and traits for using this library in Actix Web
pub mod prelude {
pub use crate::{
middleware::{SignatureVerified, VerifySignature},
verify::Unverified,
Config, PrepareVerifyError, Sign, SignatureVerify,
};
#[cfg(feature = "digest")]
pub use crate::digest::{
middleware::{DigestVerified, VerifyDigest},
DigestClient, DigestCreate, DigestPart, DigestVerify, SignExt,
};
pub use actix_web::http::header::{InvalidHeaderValue, ToStrError};
}
/// Types for Verifying an HTTP Signature
pub mod verify {
pub use http_signature_normalization::verify::{
Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified,
ValidateError,
};
}
use self::{
create::Unsigned,
verify::{Algorithm, Unverified},
};
/// A trait for verifying signatures
pub trait SignatureVerify {
/// An error produced while attempting to verify the signature. This can be anything
/// implementing ResponseError
type Error: actix_web::ResponseError;
/// The future that resolves to the verification state of the signature
type Future: IntoFuture<Item = bool, Error = Self::Error>;
/// Given the algorithm, key_id, signature, and signing_string, produce a future that resulves
/// to a the verification status
fn signature_verify(
&mut self,
algorithm: Option<Algorithm>,
key_id: &str,
signature: &str,
signing_string: &str,
) -> Self::Future;
}
/// A trait implemented by the Actix Web ClientRequest type to add an HTTP signature to the request
pub trait Sign {
/// Add an Authorization Signature to the request
fn authorization_signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
K: Display,
Self: Sized;
/// Add a Signature to the request
fn signature<F, E, K>(self, config: &Config, key_id: K, f: F) -> Result<Self, E>
where
F: FnOnce(&str) -> Result<String, E>,
E: From<ToStrError> + From<InvalidHeaderValue>,
K: Display,
Self: Sized;
}
#[derive(Clone, Debug, Default)]
/// A thin wrapper around the underlying library's config type
pub struct Config {
/// The inner config type
pub config: http_signature_normalization::Config,
}
#[derive(Debug, Fail)]
/// An error when preparing to verify a request
pub enum PrepareVerifyError {
#[fail(display = "Signature error, {}", _0)]
/// An error validating the request
Sig(#[cause] http_signature_normalization::PrepareVerifyError),
#[fail(display = "Failed to read header, {}", _0)]
/// An error converting the header to a string for validation
Header(#[cause] ToStrError),
}
impl Config {
/// Begin the process of singing a request
pub fn begin_sign(
&self,
method: &Method,
path_and_query: Option<&PathAndQuery>,
headers: HeaderMap,
) -> Result<Unsigned, ToStrError> {
let headers = headers
.iter()
.map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
.collect::<Result<BTreeMap<_, _>, ToStrError>>()?;
let path_and_query = path_and_query
.map(|p| p.to_string())
.unwrap_or(String::from("/"));
let unsigned = self
.config
.begin_sign(&method.to_string(), &path_and_query, headers);
Ok(Unsigned { unsigned })
}
/// Begin the proess of verifying a request
pub fn begin_verify(
&self,
method: &Method,
path_and_query: Option<&PathAndQuery>,
headers: HeaderMap,
) -> Result<Unverified, PrepareVerifyError> {
let headers = headers
.iter()
.map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
.collect::<Result<BTreeMap<_, _>, ToStrError>>()?;
let path_and_query = path_and_query
.map(|p| p.to_string())
.unwrap_or(String::from("/"));
let unverified = self
.config
.begin_verify(&method.to_string(), &path_and_query, headers)?;
Ok(unverified)
}
}
impl From<http_signature_normalization::PrepareVerifyError> for PrepareVerifyError {
fn from(e: http_signature_normalization::PrepareVerifyError) -> Self {
PrepareVerifyError::Sig(e)
}
}
impl From<ToStrError> for PrepareVerifyError {
fn from(e: ToStrError) -> Self {
PrepareVerifyError::Header(e)
}
}