#![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_web::{http::StatusCode, web, App, HttpResponse, HttpServer, ResponseError}; //! use futures::future::{err, ok, Ready}; //! use http_signature_normalization_actix::prelude::*; //! use sha2::{Digest, Sha256}; //! //! #[derive(Clone, Debug)] //! struct MyVerify; //! //! impl SignatureVerify for MyVerify { //! type Error = MyError; //! type Future = Ready>; //! //! fn signature_verify( //! &mut self, //! algorithm: Option, //! 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 = match base64::decode(signature) { //! Ok(decoded) => decoded, //! Err(_) => return err(MyError::Decode), //! }; //! //! ok(decoded == signing_string.as_bytes()) //! } //! } //! //! async fn index((_, sig_verified): (DigestVerified, SignatureVerified)) -> &'static str { //! println!("Signature verified for {}", sig_verified.key_id()); //! "Eyyyyup" //! } //! //! #[actix_rt::main] //! async fn main() -> Result<(), Box> { //! 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")? //! .run() //! .await?; //! //! Ok(()) //! } //! //! #[derive(Debug, thiserror::Error)] //! enum MyError { //! #[error("Failed to verify, {}", _0)] //! Verify(#[from] PrepareVerifyError), //! //! #[error("Unsupported algorithm")] //! Algorithm, //! //! #[error("Couldn't decode signature")] //! Decode, //! //! #[error("Invalid key")] //! Key, //! } //! //! impl ResponseError for MyError { //! fn status_code(&self) -> StatusCode { //! StatusCode::BAD_REQUEST //! } //! //! fn error_response(&self) -> HttpResponse { //! HttpResponse::BadRequest().finish() //! } //! } //! ``` //! //! ### Use it in a client //! ```rust,ignore //! use actix_web::client::Client; //! use http_signature_normalization_actix::prelude::*; //! use sha2::{Digest, Sha256}; //! //! #[actix_rt::main] //! async fn main() -> Result<(), Box> { //! let config = Config::default(); //! let mut digest = Sha256::new(); //! //! let mut response = 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| { //! Ok(base64::encode(s)) as Result<_, MyError> //! })? //! .send() //! .await //! .map_err(|e| { //! eprintln!("Error, {}", e); //! MyError::SendRequest //! })?; //! //! let body = response.body().await.map_err(|e| { //! eprintln!("Error, {}", e); //! MyError::Body //! })?; //! //! println!("{:?}", body); //! Ok(()) //! } //! //! #[derive(Debug, thiserror::Error)] //! pub enum MyError { //! #[error("Failed to read header, {0}")] //! Convert(#[from] ToStrError), //! //! #[error("Failed to create header, {0}")] //! Header(#[from] InvalidHeaderValue), //! //! #[error("Failed to send request")] //! SendRequest, //! //! #[error("Failed to retrieve request body")] //! Body, //! } //! ``` use actix_web::http::{ header::{HeaderMap, InvalidHeaderValue, ToStrError}, uri::PathAndQuery, Method, }; use std::{collections::BTreeMap, fmt::Display, future::Future}; 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::{Algorithm, 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: Future>; /// 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, 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(self, config: &Config, key_id: K, f: F) -> Result where F: FnOnce(&str) -> Result, E: From + From, K: Display, Self: Sized; /// Add a Signature to the request fn signature(self, config: &Config, key_id: K, f: F) -> Result where F: FnOnce(&str) -> Result, E: From + From, 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, thiserror::Error)] /// An error when preparing to verify a request pub enum PrepareVerifyError { #[error("Signature error, {0}")] /// An error validating the request Sig(#[from] http_signature_normalization::PrepareVerifyError), #[error("Failed to read header, {0}")] /// An error converting the header to a string for validation Header(#[from] 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 { let headers = headers .iter() .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) .collect::, 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 { let headers = headers .iter() .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string()))) .collect::, 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) } }