diff --git a/Cargo.toml b/Cargo.toml index dc0ea02..fed1a40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.2.0" +version = "0.3.0" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -20,4 +20,5 @@ members = [ ] [dependencies] -chrono = "0.4" +chrono = "0.4" +thiserror = "1.0" diff --git a/README.md b/README.md index 6f66c50..8b99f04 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ _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) +- [Hit me up on Mastodon](https://asonix.dog/@asonix) 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. diff --git a/http-signature-normalization-actix/Cargo.toml b/http-signature-normalization-actix/Cargo.toml index d130820..7d7848f 100644 --- a/http-signature-normalization-actix/Cargo.toml +++ b/http-signature-normalization-actix/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "http-signature-normalization-actix" description = "An HTTP Signatures library that leaves the signing to you" -version = "0.2.0" +version = "0.3.0-alpha.1" authors = ["asonix "] license-file = "LICENSE" readme = "README.md" @@ -25,13 +25,16 @@ name = "client" required-features = ["sha-2"] [dependencies] -actix-web = "1.0" -base64 = { version = "0.10", optional = true } -failure = "0.1" -futures = "0.1" -http-signature-normalization = { version = "0.2.0", path = ".." } +actix-web = "3.0.0-alpha.1" +actix-http = "2.0.0-alpha.2" +base64 = { version = "0.11", optional = true } +bytes = "0.5.4" +futures = "0.3" +http-signature-normalization = { version = "0.3.0", path = ".." } sha2 = { version = "0.8", optional = true } sha3 = { version = "0.8", optional = true } +thiserror = "1.0" [dev-dependencies] -actix = "0.8" +actix = "0.10.0-alpha.1" +actix-rt = "1.0.0" diff --git a/http-signature-normalization-actix/README.md b/http-signature-normalization-actix/README.md index f93c437..5732ad6 100644 --- a/http-signature-normalization-actix/README.md +++ b/http-signature-normalization-actix/README.md @@ -3,7 +3,7 @@ _An HTTP Signatures library that leaves the signing to you_ - [crates.io](https://crates.io/crates/http-signature-normalization-actix) - [docs.rs](https://docs.rs/http-signature-normalization-actix) -- [Join the discussion on Matrix](https://matrix.to/#/!IRQaBCMWKbpBWKjQgx:asonix.dog?via=asonix.dog) +- [Hit me up on Mastodon](https://asonix.dog/@asonix) 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. @@ -13,79 +13,69 @@ This crate provides extensions the ClientRequest type from Actix Web, and provid #### First, add this crate to your dependencies ```toml -actix = "0.8" -actix-web = "1.0" -failure = "0.1" -http-signature-normalization-actix = { version = "0.1", default-features = false, features = ["sha2"] } +actix = "0.10.0-alpha.1" +actix-web = "3.0.0-alpha.1" +thiserror = "0.1" +http-signature-normalization-actix = { version = "0.3.0-alpha.0", default-features = false, features = ["sha-2"] } sha2 = "0.8" ``` #### Then, use it in your client ```rust -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(); +#[actix_rt::main] +async fn main() -> Result<(), Box> { + 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| Ok(base64::encode(s)) as Result<_, MyError>, - ) - .unwrap() - .send() - .map_err(|_| ()) - .and_then(|mut res| res.body().map_err(|_| ())) - .map(|body| { - println!("{:?}", body); - }) - })) - .unwrap(); + 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, Fail)] +#[derive(Debug, thiserror::Error)] pub enum MyError { - #[fail(display = "Failed to read header, {}", _0)] - Convert(#[cause] ToStrError), + #[error("Failed to read header, {0}")] + Convert(#[from] ToStrError), - #[fail(display = "Failed to create header, {}", _0)] - Header(#[cause] InvalidHeaderValue), -} + #[error("Failed to create header, {0}")] + Header(#[from] InvalidHeaderValue), -impl From for MyError { - fn from(e: ToStrError) -> Self { - MyError::Convert(e) - } -} + #[error("Failed to send request")] + SendRequest, -impl From for MyError { - fn from(e: InvalidHeaderValue) -> Self { - MyError::Header(e) - } + #[error("Failed to retrieve request body")] + Body, } ``` #### Or, use it in your server ```rust -use actix::System; -use actix_web::{web, App, HttpResponse, HttpServer, ResponseError}; -use failure::Fail; -use http_signature_normalization_actix::{prelude::*, verify::Algorithm}; +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)] @@ -93,7 +83,7 @@ struct MyVerify; impl SignatureVerify for MyVerify { type Error = MyError; - type Future = Result; + type Future = Ready>; fn signature_verify( &mut self, @@ -104,26 +94,28 @@ impl SignatureVerify for MyVerify { ) -> Self::Future { match algorithm { Some(Algorithm::Hs2019) => (), - _ => return Err(MyError::Algorithm), + _ => return err(MyError::Algorithm), }; if key_id != "my-key-id" { - return Err(MyError::Key); + return err(MyError::Key); } - let decoded = base64::decode(signature).map_err(|_| MyError::Decode)?; + let decoded = match base64::decode(signature) { + Ok(decoded) => decoded, + Err(_) => return err(MyError::Decode), + }; - Ok(decoded == signing_string.as_bytes()) + ok(decoded == signing_string.as_bytes()) } } -fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { +async fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { "Eyyyyup" } -fn main() -> Result<(), Box> { - let sys = System::new("server-example"); - +#[actix_rt::main] +async fn main() -> Result<(), Box> { let config = Config::default(); HttpServer::new(move || { @@ -137,41 +129,35 @@ fn main() -> Result<(), Box> { .route("/", web::post().to(index)) }) .bind("127.0.0.1:8010")? - .start(); + .run() + .await?; - sys.run()?; Ok(()) } -#[derive(Debug, Fail)] +#[derive(Debug, thiserror::Error)] enum MyError { - #[fail(display = "Failed to verify, {}", _0)] - Verify(#[cause] PrepareVerifyError), + #[error("Failed to verify, {}", _0)] + Verify(#[from] PrepareVerifyError), - #[fail(display = "Unsupported algorithm")] + #[error("Unsupported algorithm")] Algorithm, - #[fail(display = "Couldn't decode signature")] + #[error("Couldn't decode signature")] Decode, - #[fail(display = "Invalid key")] + #[error("Invalid key")] Key, } impl ResponseError for MyError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } + fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } - - fn render_response(&self) -> HttpResponse { - self.error_response() - } -} - -impl From for MyError { - fn from(e: PrepareVerifyError) -> Self { - MyError::Verify(e) - } } ``` diff --git a/http-signature-normalization-actix/examples/client.rs b/http-signature-normalization-actix/examples/client.rs index c90429f..5edc73f 100644 --- a/http-signature-normalization-actix/examples/client.rs +++ b/http-signature-normalization-actix/examples/client.rs @@ -1,54 +1,45 @@ -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(); +#[actix_rt::main] +async fn main() -> Result<(), Box> { + 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| Ok(base64::encode(s)) as Result<_, MyError>, - ) - .unwrap() - .send() - .map_err(|_| ()) - .and_then(|mut res| res.body().map_err(|_| ())) - .map(|body| { - println!("{:?}", body); - }) - })) - .unwrap(); + 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, Fail)] +#[derive(Debug, thiserror::Error)] pub enum MyError { - #[fail(display = "Failed to read header, {}", _0)] - Convert(#[cause] ToStrError), + #[error("Failed to read header, {0}")] + Convert(#[from] ToStrError), - #[fail(display = "Failed to create header, {}", _0)] - Header(#[cause] InvalidHeaderValue), -} + #[error("Failed to create header, {0}")] + Header(#[from] InvalidHeaderValue), -impl From for MyError { - fn from(e: ToStrError) -> Self { - MyError::Convert(e) - } -} + #[error("Failed to send request")] + SendRequest, -impl From for MyError { - fn from(e: InvalidHeaderValue) -> Self { - MyError::Header(e) - } + #[error("Failed to retrieve request body")] + Body, } diff --git a/http-signature-normalization-actix/examples/server.rs b/http-signature-normalization-actix/examples/server.rs index 5fae4e5..eca757c 100644 --- a/http-signature-normalization-actix/examples/server.rs +++ b/http-signature-normalization-actix/examples/server.rs @@ -1,7 +1,6 @@ -use actix::System; -use actix_web::{web, App, HttpResponse, HttpServer, ResponseError}; -use failure::Fail; -use http_signature_normalization_actix::{prelude::*, verify::Algorithm}; +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)] @@ -9,7 +8,7 @@ struct MyVerify; impl SignatureVerify for MyVerify { type Error = MyError; - type Future = Result; + type Future = Ready>; fn signature_verify( &mut self, @@ -20,26 +19,28 @@ impl SignatureVerify for MyVerify { ) -> Self::Future { match algorithm { Some(Algorithm::Hs2019) => (), - _ => return Err(MyError::Algorithm), + _ => return err(MyError::Algorithm), }; if key_id != "my-key-id" { - return Err(MyError::Key); + return err(MyError::Key); } - let decoded = base64::decode(signature).map_err(|_| MyError::Decode)?; + let decoded = match base64::decode(signature) { + Ok(decoded) => decoded, + Err(_) => return err(MyError::Decode), + }; - Ok(decoded == signing_string.as_bytes()) + ok(decoded == signing_string.as_bytes()) } } -fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { +async fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { "Eyyyyup" } -fn main() -> Result<(), Box> { - let sys = System::new("server-example"); - +#[actix_rt::main] +async fn main() -> Result<(), Box> { let config = Config::default(); HttpServer::new(move || { @@ -53,39 +54,33 @@ fn main() -> Result<(), Box> { .route("/", web::post().to(index)) }) .bind("127.0.0.1:8010")? - .start(); + .run() + .await?; - sys.run()?; Ok(()) } -#[derive(Debug, Fail)] +#[derive(Debug, thiserror::Error)] enum MyError { - #[fail(display = "Failed to verify, {}", _0)] - Verify(#[cause] PrepareVerifyError), + #[error("Failed to verify, {}", _0)] + Verify(#[from] PrepareVerifyError), - #[fail(display = "Unsupported algorithm")] + #[error("Unsupported algorithm")] Algorithm, - #[fail(display = "Couldn't decode signature")] + #[error("Couldn't decode signature")] Decode, - #[fail(display = "Invalid key")] + #[error("Invalid key")] Key, } impl ResponseError for MyError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } + fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } - - fn render_response(&self) -> HttpResponse { - self.error_response() - } -} - -impl From for MyError { - fn from(e: PrepareVerifyError) -> Self { - MyError::Verify(e) - } } diff --git a/http-signature-normalization-actix/src/digest/middleware.rs b/http-signature-normalization-actix/src/digest/middleware.rs index 57dddc4..1695aed 100644 --- a/http-signature-normalization-actix/src/digest/middleware.rs +++ b/http-signature-normalization-actix/src/digest/middleware.rs @@ -3,17 +3,22 @@ use actix_web::{ dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform}, error::PayloadError, - http::header::HeaderValue, - web::Bytes, + http::{header::HeaderValue, StatusCode}, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, }; -use failure::Fail; +use bytes::{Bytes, BytesMut}; use futures::{ - future::{err, ok, Either, FutureResult}, + future::{err, ok, ready, Ready}, stream::once, - Future, Poll, Stream, + Stream, StreamExt, +}; +use std::{ + cell::RefCell, + future::Future, + pin::Pin, + rc::Rc, + task::{Context, Poll}, }; -use std::{cell::RefCell, rc::Rc}; use super::{DigestPart, DigestVerify}; @@ -42,8 +47,8 @@ pub struct VerifyDigest(bool, T); #[doc(hidden)] pub struct VerifyMiddleware(Rc>, bool, T); -#[derive(Debug, Fail)] -#[fail(display = "Error verifying digest")] +#[derive(Debug, thiserror::Error)] +#[error("Error verifying digest")] #[doc(hidden)] pub struct VerifyError; @@ -67,14 +72,16 @@ where impl FromRequest for DigestVerified { type Error = VerifyError; - type Future = Result; + type Future = Ready>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - req.extensions() - .get::() - .map(|s| *s) - .ok_or(VerifyError) + ready( + req.extensions() + .get::() + .map(|s| *s) + .ok_or(VerifyError), + ) } } @@ -93,7 +100,7 @@ where type Error = actix_web::Error; type Transform = VerifyMiddleware; type InitError = (); - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(VerifyMiddleware( @@ -117,41 +124,48 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = actix_web::Error; - type Future = Box>; + type Future = Pin>>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.0.borrow_mut().poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.0.borrow_mut().poll_ready(cx) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { if let Some(digest) = req.headers().get("Digest") { let vec = match parse_digest(digest) { Some(vec) => vec, - None => return Box::new(err(VerifyError.into())), + None => return Box::pin(err(VerifyError.into())), }; - let payload = req.take_payload(); + let mut payload = req.take_payload(); let service = self.0.clone(); let mut verify_digest = self.2.clone(); - Box::new(payload.concat2().from_err().and_then(move |bytes| { + Box::pin(async move { + let mut output_bytes = BytesMut::new(); + while let Some(res) = payload.next().await { + let bytes = res?; + output_bytes.extend(bytes); + } + let bytes = output_bytes.freeze(); + if verify_digest.verify(&vec, &bytes.as_ref()) { req.set_payload( - (Box::new(once(Ok(bytes))) - as Box>) + (Box::pin(once(ok(bytes))) + as Pin> + 'static>>) .into(), ); req.extensions_mut().insert(DigestVerified); - Either::A(service.borrow_mut().call(req)) + service.borrow_mut().call(req).await } else { - Either::B(err(VerifyError.into())) + Err(VerifyError.into()) } - })) + }) } else if self.1 { - Box::new(err(VerifyError.into())) + Box::pin(err(VerifyError.into())) } else { - Box::new(self.0.borrow_mut().call(req)) + Box::pin(self.0.borrow_mut().call(req)) } } } @@ -179,11 +193,11 @@ fn parse_digest(h: &HeaderValue) -> Option> { } impl ResponseError for VerifyError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } + fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } - - fn render_response(&self) -> HttpResponse { - Self::error_response(self) - } } diff --git a/http-signature-normalization-actix/src/digest/mod.rs b/http-signature-normalization-actix/src/digest/mod.rs index 35d6a9f..0472193 100644 --- a/http-signature-normalization-actix/src/digest/mod.rs +++ b/http-signature-normalization-actix/src/digest/mod.rs @@ -3,14 +3,13 @@ //! Digest headers are commonly used in conjunction with HTTP Signatures to verify the whole //! request when request bodies are present +use actix_http::encoding::Decoder; use actix_web::{ - client::{ClientRequest, ClientResponse}, - error::PayloadError, + client::{ClientRequest, ClientResponse, SendRequestError}, + dev::Payload, http::header::{InvalidHeaderValue, ToStrError}, - web::Bytes, }; -use futures::{Future, Stream}; -use std::fmt::Display; +use std::{fmt::Display, future::Future}; use crate::{Config, Sign}; @@ -112,7 +111,7 @@ where /// the digest pub fn send( self, - ) -> impl Future>> { + ) -> impl Future>, SendRequestError>> { self.req.send_body(self.body.as_ref().to_vec()) } } diff --git a/http-signature-normalization-actix/src/lib.rs b/http-signature-normalization-actix/src/lib.rs index 72f8caf..06315b3 100644 --- a/http-signature-normalization-actix/src/lib.rs +++ b/http-signature-normalization-actix/src/lib.rs @@ -8,10 +8,9 @@ //! //! ### 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 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)] @@ -19,7 +18,7 @@ //! //! impl SignatureVerify for MyVerify { //! type Error = MyError; -//! type Future = Result; +//! type Future = Ready>; //! //! fn signature_verify( //! &mut self, @@ -30,28 +29,28 @@ //! ) -> Self::Future { //! match algorithm { //! Some(Algorithm::Hs2019) => (), -//! _ => return Err(MyError::Algorithm), +//! _ => return err(MyError::Algorithm), //! }; //! //! if key_id != "my-key-id" { -//! return Err(MyError::Key); +//! return err(MyError::Key); //! } //! -//! let decoded = base64::decode(signature).map_err(|_| MyError::Decode)?; +//! let decoded = match base64::decode(signature) { +//! Ok(decoded) => decoded, +//! Err(_) => return 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()) +//! ok(decoded == signing_string.as_bytes()) //! } //! } //! -//! fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { +//! async fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { //! "Eyyyyup" //! } //! -//! fn main() -> Result<(), Box> { -//! let sys = System::new("server-example"); -//! +//! #[actix_rt::main] +//! async fn main() -> Result<(), Box> { //! let config = Config::default(); //! //! HttpServer::new(move || { @@ -65,103 +64,84 @@ //! .route("/", web::post().to(index)) //! }) //! .bind("127.0.0.1:8010")? -//! .start(); +//! .run() +//! .await?; //! -//! sys.run()?; //! Ok(()) //! } //! -//! #[derive(Debug, Fail)] +//! #[derive(Debug, thiserror::Error)] //! enum MyError { -//! #[fail(display = "Failed to verify, {}", _0)] -//! Verify(#[cause] PrepareVerifyError), +//! #[error("Failed to verify, {}", _0)] +//! Verify(#[from] PrepareVerifyError), //! -//! #[fail(display = "Unsupported algorithm")] +//! #[error("Unsupported algorithm")] //! Algorithm, //! -//! #[fail(display = "Couldn't decode signature")] +//! #[error("Couldn't decode signature")] //! Decode, //! -//! #[fail(display = "Invalid key")] +//! #[error("Invalid key")] //! Key, //! } //! //! impl ResponseError for MyError { +//! fn status_code(&self) -> StatusCode { +//! StatusCode::BAD_REQUEST +//! } +//! //! fn error_response(&self) -> HttpResponse { //! HttpResponse::BadRequest().finish() //! } -//! -//! fn render_response(&self) -> HttpResponse { -//! self.error_response() -//! } -//! } -//! -//! impl From 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(); +//! #[actix_rt::main] +//! async fn main() -> Result<(), Box> { +//! 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(); +//! 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, Fail)] +//! #[derive(Debug, thiserror::Error)] //! pub enum MyError { -//! #[fail(display = "Failed to read header, {}", _0)] -//! Convert(#[cause] ToStrError), +//! #[error("Failed to read header, {0}")] +//! Convert(#[from] ToStrError), //! -//! #[fail(display = "Failed to create header, {}", _0)] -//! Header(#[cause] InvalidHeaderValue), -//! } +//! #[error("Failed to create header, {0}")] +//! Header(#[from] InvalidHeaderValue), //! -//! impl From for MyError { -//! fn from(e: ToStrError) -> Self { -//! MyError::Convert(e) -//! } -//! } +//! #[error("Failed to send request")] +//! SendRequest, //! -//! impl From for MyError { -//! fn from(e: InvalidHeaderValue) -> Self { -//! MyError::Header(e) -//! } +//! #[error("Failed to retrieve request body")] +//! Body, //! } //! ``` @@ -171,9 +151,7 @@ use actix_web::http::{ Method, }; -use failure::Fail; -use futures::future::IntoFuture; -use std::{collections::BTreeMap, fmt::Display}; +use std::{collections::BTreeMap, fmt::Display, future::Future}; mod sign; @@ -187,7 +165,7 @@ pub mod middleware; pub mod prelude { pub use crate::{ middleware::{SignatureVerified, VerifySignature}, - verify::Unverified, + verify::{Algorithm, Unverified}, Config, PrepareVerifyError, Sign, SignatureVerify, }; @@ -220,7 +198,7 @@ pub trait SignatureVerify { type Error: actix_web::ResponseError; /// The future that resolves to the verification state of the signature - type Future: IntoFuture; + type Future: Future>; /// Given the algorithm, key_id, signature, and signing_string, produce a future that resulves /// to a the verification status @@ -259,16 +237,16 @@ pub struct Config { pub config: http_signature_normalization::Config, } -#[derive(Debug, Fail)] +#[derive(Debug, thiserror::Error)] /// An error when preparing to verify a request pub enum PrepareVerifyError { - #[fail(display = "Signature error, {}", _0)] + #[error("Signature error, {0}")] /// An error validating the request - Sig(#[cause] http_signature_normalization::PrepareVerifyError), + Sig(#[from] http_signature_normalization::PrepareVerifyError), - #[fail(display = "Failed to read header, {}", _0)] + #[error("Failed to read header, {0}")] /// An error converting the header to a string for validation - Header(#[cause] ToStrError), + Header(#[from] ToStrError), } impl Config { @@ -318,15 +296,3 @@ impl Config { Ok(unverified) } } - -impl From for PrepareVerifyError { - fn from(e: http_signature_normalization::PrepareVerifyError) -> Self { - PrepareVerifyError::Sig(e) - } -} - -impl From for PrepareVerifyError { - fn from(e: ToStrError) -> Self { - PrepareVerifyError::Header(e) - } -} diff --git a/http-signature-normalization-actix/src/middleware.rs b/http-signature-normalization-actix/src/middleware.rs index 7b27978..e03752f 100644 --- a/http-signature-normalization-actix/src/middleware.rs +++ b/http-signature-normalization-actix/src/middleware.rs @@ -2,14 +2,17 @@ use actix_web::{ dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform}, - FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, + http::StatusCode, + Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, }; -use failure::Fail; -use futures::{ - future::{err, ok, Either, FutureResult, IntoFuture}, - Future, Poll, +use futures::future::{err, ok, ready, Ready}; +use std::{ + cell::RefCell, + future::Future, + pin::Pin, + rc::Rc, + task::{Context, Poll}, }; -use std::{cell::RefCell, rc::Rc}; use crate::{Config, SignatureVerify}; @@ -45,8 +48,8 @@ enum HeaderKind { Signature, } -#[derive(Clone, Debug, Fail)] -#[fail(display = "Failed to verify http signature")] +#[derive(Clone, Debug, thiserror::Error)] +#[error("Failed to verify http signature")] #[doc(hidden)] pub struct VerifyError; @@ -82,17 +85,12 @@ impl VerifyMiddleware where T: SignatureVerify + 'static, T::Future: 'static, - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = actix_web::Error, - > + 'static, - S::Error: 'static, + S: Service, Error = Error> + 'static, { fn handle( &mut self, req: ServiceRequest, - ) -> Box, Error = actix_web::Error>> { + ) -> Pin, Error>>>> { let res = self.1.begin_verify( req.method(), req.uri().path_and_query(), @@ -101,32 +99,29 @@ where let unverified = match res { Ok(unverified) => unverified, - Err(_) => return Box::new(err(VerifyError.into())), + Err(_) => return Box::pin(err(VerifyError.into())), }; let algorithm = unverified.algorithm().map(|a| a.clone()); let key_id = unverified.key_id().to_owned(); - let verified = unverified.verify(|signature, signing_string| { + let service = self.0.clone(); + + let fut = unverified.verify(|signature, signing_string| { self.4 .signature_verify(algorithm, &key_id, signature, signing_string) }); - let service = self.0.clone(); + Box::pin(async move { + let verified = fut.await?; - Box::new( - verified - .into_future() - .from_err::() - .and_then(move |verified| { - if verified { - req.extensions_mut().insert(SignatureVerified); - Either::A(service.borrow_mut().call(req)) - } else { - Either::B(err(VerifyError.into())) - } - }), - ) + if verified { + req.extensions_mut().insert(SignatureVerified); + service.borrow_mut().call(req).await + } else { + Err(VerifyError.into()) + } + }) } } @@ -142,14 +137,16 @@ impl HeaderKind { impl FromRequest for SignatureVerified { type Error = VerifyError; - type Future = Result; + type Future = Ready>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - req.extensions() - .get::() - .map(|s| *s) - .ok_or(VerifyError) + ready( + req.extensions() + .get::() + .map(|s| *s) + .ok_or(VerifyError), + ) } } @@ -168,7 +165,7 @@ where type Error = actix_web::Error; type Transform = VerifyMiddleware; type InitError = (); - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(VerifyMiddleware( @@ -194,10 +191,10 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = actix_web::Error; - type Future = Box>; + type Future = Pin>>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.0.borrow_mut().poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.0.borrow_mut().poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { @@ -213,21 +210,21 @@ where return self.handle(req); } - Box::new(err(VerifyError.into())) + Box::pin(err(VerifyError.into())) } else if self.3 { - Box::new(self.0.borrow_mut().call(req)) + Box::pin(self.0.borrow_mut().call(req)) } else { - Box::new(err(VerifyError.into())) + Box::pin(err(VerifyError.into())) } } } impl ResponseError for VerifyError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } + fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } - - fn render_response(&self) -> HttpResponse { - self.error_response() - } } diff --git a/http-signature-normalization-http/Cargo.toml b/http-signature-normalization-http/Cargo.toml index c50857c..76687b4 100644 --- a/http-signature-normalization-http/Cargo.toml +++ b/http-signature-normalization-http/Cargo.toml @@ -13,4 +13,4 @@ edition = "2018" [dependencies] http = "0.2" -http-signature-normalization = { version = "0.2.0", path = ".." } +http-signature-normalization = { version = "0.3.0", path = ".." } diff --git a/http-signature-normalization-reqwest/Cargo.toml b/http-signature-normalization-reqwest/Cargo.toml index 6723fd1..0efa5b0 100644 --- a/http-signature-normalization-reqwest/Cargo.toml +++ b/http-signature-normalization-reqwest/Cargo.toml @@ -5,9 +5,21 @@ authors = ["asonix "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["sha-2"] +digest = ["base64", "serde", "serde_json", "serde_urlencoded", "thiserror"] +sha-2 = ["digest", "sha2"] [dependencies] +base64 = { version = "0.11.0", optional = true } +bytes = "0.5.3" +futures = "0.3.1" chrono = "0.4.10" http = "0.2.0" -http-signature-normalization = { version = "0.2.0", path = ".." } +http-signature-normalization = { version = "0.3.0", path = ".." } reqwest = "0.10.1" +serde = { version = "1.0.104", features = ["derive"], optional = true } +serde_json = { version = "1.0.44", optional = true } +serde_urlencoded = { version = "0.6.1", optional = true } +sha2 = { version = "0.8.1", optional = true } +thiserror = { version = "1.0.9", optional = true } diff --git a/http-signature-normalization-reqwest/src/digest/mod.rs b/http-signature-normalization-reqwest/src/digest/mod.rs new file mode 100644 index 0000000..d99ee96 --- /dev/null +++ b/http-signature-normalization-reqwest/src/digest/mod.rs @@ -0,0 +1,16 @@ +use reqwest::Request; +use std::{future::Future, pin::Pin}; + +pub trait CreateDigest { + fn create_digest(&mut self, payload: &[u8]) -> String; +} + +pub trait WithDigest: Sized { + type Future: Future; + + fn with_digest(&mut self, creator: T) -> Self::Future; +} + +impl WithDigest for Request { + type Future = Pin + Send>>; +} diff --git a/http-signature-normalization-reqwest/src/lib.rs b/http-signature-normalization-reqwest/src/lib.rs index 3a9bea7..f3f7259 100644 --- a/http-signature-normalization-reqwest/src/lib.rs +++ b/http-signature-normalization-reqwest/src/lib.rs @@ -6,6 +6,8 @@ use reqwest::{ }; use std::fmt::Display; +pub mod digest; + pub struct Config(http_signature_normalization::Config); pub trait Sign { diff --git a/src/lib.rs b/src/lib.rs index 2e1b044..fdb8bf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ //! ``` use chrono::{DateTime, Duration, Utc}; -use std::{collections::BTreeMap, error::Error, fmt}; +use std::collections::BTreeMap; pub mod create; pub mod verify; @@ -77,15 +77,18 @@ pub struct Config { pub expires_after: Duration, } -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] /// 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(ValidateError), + Validate(#[from] ValidateError), + + #[error("{0}")] /// Error parsing the header - Parse(ParseSignatureError), + Parse(#[from] ParseSignatureError), } impl Config { @@ -189,43 +192,6 @@ fn build_signing_string( signing_string } -impl fmt::Display for PrepareVerifyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - PrepareVerifyError::Validate(ref e) => fmt::Display::fmt(e, f), - PrepareVerifyError::Parse(ref e) => fmt::Display::fmt(e, f), - } - } -} - -impl Error for PrepareVerifyError { - fn description(&self) -> &str { - match *self { - PrepareVerifyError::Validate(ref e) => e.description(), - PrepareVerifyError::Parse(ref e) => e.description(), - } - } - - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - PrepareVerifyError::Validate(ref e) => Some(e), - PrepareVerifyError::Parse(ref e) => Some(e), - } - } -} - -impl From for PrepareVerifyError { - fn from(v: ValidateError) -> Self { - PrepareVerifyError::Validate(v) - } -} - -impl From for PrepareVerifyError { - fn from(p: ParseSignatureError) -> Self { - PrepareVerifyError::Parse(p) - } -} - impl Default for Config { fn default() -> Self { Config {