Bump http-signature-normalization version, update actix to 3.0

This commit is contained in:
asonix 2020-03-15 19:29:47 -05:00
parent ebeee051bf
commit 7f98235a37
15 changed files with 337 additions and 389 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization" name = "http-signature-normalization"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.2.0" version = "0.3.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -20,4 +20,5 @@ members = [
] ]
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4"
thiserror = "1.0"

View file

@ -3,7 +3,7 @@ _An HTTP Signatures library that leaves the signing to you_
- [crates.io](https://crates.io/crates/http-signature-normalization) - [crates.io](https://crates.io/crates/http-signature-normalization)
- [docs.rs](https://docs.rs/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. 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.

View file

@ -1,7 +1,7 @@
[package] [package]
name = "http-signature-normalization-actix" name = "http-signature-normalization-actix"
description = "An HTTP Signatures library that leaves the signing to you" description = "An HTTP Signatures library that leaves the signing to you"
version = "0.2.0" version = "0.3.0-alpha.1"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license-file = "LICENSE" license-file = "LICENSE"
readme = "README.md" readme = "README.md"
@ -25,13 +25,16 @@ name = "client"
required-features = ["sha-2"] required-features = ["sha-2"]
[dependencies] [dependencies]
actix-web = "1.0" actix-web = "3.0.0-alpha.1"
base64 = { version = "0.10", optional = true } actix-http = "2.0.0-alpha.2"
failure = "0.1" base64 = { version = "0.11", optional = true }
futures = "0.1" bytes = "0.5.4"
http-signature-normalization = { version = "0.2.0", path = ".." } futures = "0.3"
http-signature-normalization = { version = "0.3.0", path = ".." }
sha2 = { version = "0.8", optional = true } sha2 = { version = "0.8", optional = true }
sha3 = { version = "0.8", optional = true } sha3 = { version = "0.8", optional = true }
thiserror = "1.0"
[dev-dependencies] [dev-dependencies]
actix = "0.8" actix = "0.10.0-alpha.1"
actix-rt = "1.0.0"

View file

@ -3,7 +3,7 @@ _An HTTP Signatures library that leaves the signing to you_
- [crates.io](https://crates.io/crates/http-signature-normalization-actix) - [crates.io](https://crates.io/crates/http-signature-normalization-actix)
- [docs.rs](https://docs.rs/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. 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 #### First, add this crate to your dependencies
```toml ```toml
actix = "0.8" actix = "0.10.0-alpha.1"
actix-web = "1.0" actix-web = "3.0.0-alpha.1"
failure = "0.1" thiserror = "0.1"
http-signature-normalization-actix = { version = "0.1", default-features = false, features = ["sha2"] } http-signature-normalization-actix = { version = "0.3.0-alpha.0", default-features = false, features = ["sha-2"] }
sha2 = "0.8" sha2 = "0.8"
``` ```
#### Then, use it in your client #### Then, use it in your client
```rust ```rust
use actix::System;
use actix_web::client::Client; use actix_web::client::Client;
use failure::Fail;
use futures::future::{lazy, Future};
use http_signature_normalization_actix::prelude::*; use http_signature_normalization_actix::prelude::*;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
fn main() { #[actix_rt::main]
System::new("client-example") async fn main() -> Result<(), Box<dyn std::error::Error>> {
.block_on(lazy(|| { let config = Config::default();
let config = Config::default(); let mut digest = Sha256::new();
let mut digest = Sha256::new();
Client::default() let mut response = Client::default()
.post("http://127.0.0.1:8010/") .post("http://127.0.0.1:8010/")
.header("User-Agent", "Actix Web") .header("User-Agent", "Actix Web")
.authorization_signature_with_digest( .authorization_signature_with_digest(&config, "my-key-id", &mut digest, "Hewwo-owo", |s| {
&config, Ok(base64::encode(s)) as Result<_, MyError>
"my-key-id", })?
&mut digest, .send()
"Hewwo-owo", .await
|s| Ok(base64::encode(s)) as Result<_, MyError>, .map_err(|e| {
) eprintln!("Error, {}", e);
.unwrap() MyError::SendRequest
.send() })?;
.map_err(|_| ())
.and_then(|mut res| res.body().map_err(|_| ())) let body = response.body().await.map_err(|e| {
.map(|body| { eprintln!("Error, {}", e);
println!("{:?}", body); MyError::Body
}) })?;
}))
.unwrap(); println!("{:?}", body);
Ok(())
} }
#[derive(Debug, Fail)] #[derive(Debug, thiserror::Error)]
pub enum MyError { pub enum MyError {
#[fail(display = "Failed to read header, {}", _0)] #[error("Failed to read header, {0}")]
Convert(#[cause] ToStrError), Convert(#[from] ToStrError),
#[fail(display = "Failed to create header, {}", _0)] #[error("Failed to create header, {0}")]
Header(#[cause] InvalidHeaderValue), Header(#[from] InvalidHeaderValue),
}
impl From<ToStrError> for MyError { #[error("Failed to send request")]
fn from(e: ToStrError) -> Self { SendRequest,
MyError::Convert(e)
}
}
impl From<InvalidHeaderValue> for MyError { #[error("Failed to retrieve request body")]
fn from(e: InvalidHeaderValue) -> Self { Body,
MyError::Header(e)
}
} }
``` ```
#### Or, use it in your server #### Or, use it in your server
```rust ```rust
use actix::System; use actix_web::{http::StatusCode, web, App, HttpResponse, HttpServer, ResponseError};
use actix_web::{web, App, HttpResponse, HttpServer, ResponseError}; use futures::future::{err, ok, Ready};
use failure::Fail; use http_signature_normalization_actix::prelude::*;
use http_signature_normalization_actix::{prelude::*, verify::Algorithm};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -93,7 +83,7 @@ struct MyVerify;
impl SignatureVerify for MyVerify { impl SignatureVerify for MyVerify {
type Error = MyError; type Error = MyError;
type Future = Result<bool, Self::Error>; type Future = Ready<Result<bool, Self::Error>>;
fn signature_verify( fn signature_verify(
&mut self, &mut self,
@ -104,26 +94,28 @@ impl SignatureVerify for MyVerify {
) -> Self::Future { ) -> Self::Future {
match algorithm { match algorithm {
Some(Algorithm::Hs2019) => (), Some(Algorithm::Hs2019) => (),
_ => return Err(MyError::Algorithm), _ => return err(MyError::Algorithm),
}; };
if key_id != "my-key-id" { 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" "Eyyyyup"
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { #[actix_rt::main]
let sys = System::new("server-example"); async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::default(); let config = Config::default();
HttpServer::new(move || { HttpServer::new(move || {
@ -137,41 +129,35 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.route("/", web::post().to(index)) .route("/", web::post().to(index))
}) })
.bind("127.0.0.1:8010")? .bind("127.0.0.1:8010")?
.start(); .run()
.await?;
sys.run()?;
Ok(()) Ok(())
} }
#[derive(Debug, Fail)] #[derive(Debug, thiserror::Error)]
enum MyError { enum MyError {
#[fail(display = "Failed to verify, {}", _0)] #[error("Failed to verify, {}", _0)]
Verify(#[cause] PrepareVerifyError), Verify(#[from] PrepareVerifyError),
#[fail(display = "Unsupported algorithm")] #[error("Unsupported algorithm")]
Algorithm, Algorithm,
#[fail(display = "Couldn't decode signature")] #[error("Couldn't decode signature")]
Decode, Decode,
#[fail(display = "Invalid key")] #[error("Invalid key")]
Key, Key,
} }
impl ResponseError for MyError { impl ResponseError for MyError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest().finish() HttpResponse::BadRequest().finish()
} }
fn render_response(&self) -> HttpResponse {
self.error_response()
}
}
impl From<PrepareVerifyError> for MyError {
fn from(e: PrepareVerifyError) -> Self {
MyError::Verify(e)
}
} }
``` ```

View file

@ -1,54 +1,45 @@
use actix::System;
use actix_web::client::Client; use actix_web::client::Client;
use failure::Fail;
use futures::future::{lazy, Future};
use http_signature_normalization_actix::prelude::*; use http_signature_normalization_actix::prelude::*;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
fn main() { #[actix_rt::main]
System::new("client-example") async fn main() -> Result<(), Box<dyn std::error::Error>> {
.block_on(lazy(|| { let config = Config::default();
let config = Config::default(); let mut digest = Sha256::new();
let mut digest = Sha256::new();
Client::default() let mut response = Client::default()
.post("http://127.0.0.1:8010/") .post("http://127.0.0.1:8010/")
.header("User-Agent", "Actix Web") .header("User-Agent", "Actix Web")
.authorization_signature_with_digest( .authorization_signature_with_digest(&config, "my-key-id", &mut digest, "Hewwo-owo", |s| {
&config, Ok(base64::encode(s)) as Result<_, MyError>
"my-key-id", })?
&mut digest, .send()
"Hewwo-owo", .await
|s| Ok(base64::encode(s)) as Result<_, MyError>, .map_err(|e| {
) eprintln!("Error, {}", e);
.unwrap() MyError::SendRequest
.send() })?;
.map_err(|_| ())
.and_then(|mut res| res.body().map_err(|_| ())) let body = response.body().await.map_err(|e| {
.map(|body| { eprintln!("Error, {}", e);
println!("{:?}", body); MyError::Body
}) })?;
}))
.unwrap(); println!("{:?}", body);
Ok(())
} }
#[derive(Debug, Fail)] #[derive(Debug, thiserror::Error)]
pub enum MyError { pub enum MyError {
#[fail(display = "Failed to read header, {}", _0)] #[error("Failed to read header, {0}")]
Convert(#[cause] ToStrError), Convert(#[from] ToStrError),
#[fail(display = "Failed to create header, {}", _0)] #[error("Failed to create header, {0}")]
Header(#[cause] InvalidHeaderValue), Header(#[from] InvalidHeaderValue),
}
impl From<ToStrError> for MyError { #[error("Failed to send request")]
fn from(e: ToStrError) -> Self { SendRequest,
MyError::Convert(e)
}
}
impl From<InvalidHeaderValue> for MyError { #[error("Failed to retrieve request body")]
fn from(e: InvalidHeaderValue) -> Self { Body,
MyError::Header(e)
}
} }

View file

@ -1,7 +1,6 @@
use actix::System; use actix_web::{http::StatusCode, web, App, HttpResponse, HttpServer, ResponseError};
use actix_web::{web, App, HttpResponse, HttpServer, ResponseError}; use futures::future::{err, ok, Ready};
use failure::Fail; use http_signature_normalization_actix::prelude::*;
use http_signature_normalization_actix::{prelude::*, verify::Algorithm};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -9,7 +8,7 @@ struct MyVerify;
impl SignatureVerify for MyVerify { impl SignatureVerify for MyVerify {
type Error = MyError; type Error = MyError;
type Future = Result<bool, Self::Error>; type Future = Ready<Result<bool, Self::Error>>;
fn signature_verify( fn signature_verify(
&mut self, &mut self,
@ -20,26 +19,28 @@ impl SignatureVerify for MyVerify {
) -> Self::Future { ) -> Self::Future {
match algorithm { match algorithm {
Some(Algorithm::Hs2019) => (), Some(Algorithm::Hs2019) => (),
_ => return Err(MyError::Algorithm), _ => return err(MyError::Algorithm),
}; };
if key_id != "my-key-id" { 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" "Eyyyyup"
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { #[actix_rt::main]
let sys = System::new("server-example"); async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::default(); let config = Config::default();
HttpServer::new(move || { HttpServer::new(move || {
@ -53,39 +54,33 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.route("/", web::post().to(index)) .route("/", web::post().to(index))
}) })
.bind("127.0.0.1:8010")? .bind("127.0.0.1:8010")?
.start(); .run()
.await?;
sys.run()?;
Ok(()) Ok(())
} }
#[derive(Debug, Fail)] #[derive(Debug, thiserror::Error)]
enum MyError { enum MyError {
#[fail(display = "Failed to verify, {}", _0)] #[error("Failed to verify, {}", _0)]
Verify(#[cause] PrepareVerifyError), Verify(#[from] PrepareVerifyError),
#[fail(display = "Unsupported algorithm")] #[error("Unsupported algorithm")]
Algorithm, Algorithm,
#[fail(display = "Couldn't decode signature")] #[error("Couldn't decode signature")]
Decode, Decode,
#[fail(display = "Invalid key")] #[error("Invalid key")]
Key, Key,
} }
impl ResponseError for MyError { impl ResponseError for MyError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest().finish() HttpResponse::BadRequest().finish()
} }
fn render_response(&self) -> HttpResponse {
self.error_response()
}
}
impl From<PrepareVerifyError> for MyError {
fn from(e: PrepareVerifyError) -> Self {
MyError::Verify(e)
}
} }

View file

@ -3,17 +3,22 @@
use actix_web::{ use actix_web::{
dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform}, dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform},
error::PayloadError, error::PayloadError,
http::header::HeaderValue, http::{header::HeaderValue, StatusCode},
web::Bytes,
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
}; };
use failure::Fail; use bytes::{Bytes, BytesMut};
use futures::{ use futures::{
future::{err, ok, Either, FutureResult}, future::{err, ok, ready, Ready},
stream::once, 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}; use super::{DigestPart, DigestVerify};
@ -42,8 +47,8 @@ pub struct VerifyDigest<T>(bool, T);
#[doc(hidden)] #[doc(hidden)]
pub struct VerifyMiddleware<T, S>(Rc<RefCell<S>>, bool, T); pub struct VerifyMiddleware<T, S>(Rc<RefCell<S>>, bool, T);
#[derive(Debug, Fail)] #[derive(Debug, thiserror::Error)]
#[fail(display = "Error verifying digest")] #[error("Error verifying digest")]
#[doc(hidden)] #[doc(hidden)]
pub struct VerifyError; pub struct VerifyError;
@ -67,14 +72,16 @@ where
impl FromRequest for DigestVerified { impl FromRequest for DigestVerified {
type Error = VerifyError; type Error = VerifyError;
type Future = Result<Self, Self::Error>; type Future = Ready<Result<Self, Self::Error>>;
type Config = (); type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
req.extensions() ready(
.get::<Self>() req.extensions()
.map(|s| *s) .get::<Self>()
.ok_or(VerifyError) .map(|s| *s)
.ok_or(VerifyError),
)
} }
} }
@ -93,7 +100,7 @@ where
type Error = actix_web::Error; type Error = actix_web::Error;
type Transform = VerifyMiddleware<T, S>; type Transform = VerifyMiddleware<T, S>;
type InitError = (); type InitError = ();
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(VerifyMiddleware( ok(VerifyMiddleware(
@ -117,41 +124,48 @@ where
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse<Body>; type Response = ServiceResponse<Body>;
type Error = actix_web::Error; type Error = actix_web::Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.0.borrow_mut().poll_ready() self.0.borrow_mut().poll_ready(cx)
} }
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
if let Some(digest) = req.headers().get("Digest") { if let Some(digest) = req.headers().get("Digest") {
let vec = match parse_digest(digest) { let vec = match parse_digest(digest) {
Some(vec) => vec, 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 service = self.0.clone();
let mut verify_digest = self.2.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()) { if verify_digest.verify(&vec, &bytes.as_ref()) {
req.set_payload( req.set_payload(
(Box::new(once(Ok(bytes))) (Box::pin(once(ok(bytes)))
as Box<dyn Stream<Item = Bytes, Error = PayloadError>>) as Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>> + 'static>>)
.into(), .into(),
); );
req.extensions_mut().insert(DigestVerified); req.extensions_mut().insert(DigestVerified);
Either::A(service.borrow_mut().call(req)) service.borrow_mut().call(req).await
} else { } else {
Either::B(err(VerifyError.into())) Err(VerifyError.into())
} }
})) })
} else if self.1 { } else if self.1 {
Box::new(err(VerifyError.into())) Box::pin(err(VerifyError.into()))
} else { } 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<Vec<DigestPart>> {
} }
impl ResponseError for VerifyError { impl ResponseError for VerifyError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest().finish() HttpResponse::BadRequest().finish()
} }
fn render_response(&self) -> HttpResponse {
Self::error_response(self)
}
} }

View file

@ -3,14 +3,13 @@
//! Digest headers are commonly used in conjunction with HTTP Signatures to verify the whole //! Digest headers are commonly used in conjunction with HTTP Signatures to verify the whole
//! request when request bodies are present //! request when request bodies are present
use actix_http::encoding::Decoder;
use actix_web::{ use actix_web::{
client::{ClientRequest, ClientResponse}, client::{ClientRequest, ClientResponse, SendRequestError},
error::PayloadError, dev::Payload,
http::header::{InvalidHeaderValue, ToStrError}, http::header::{InvalidHeaderValue, ToStrError},
web::Bytes,
}; };
use futures::{Future, Stream}; use std::{fmt::Display, future::Future};
use std::fmt::Display;
use crate::{Config, Sign}; use crate::{Config, Sign};
@ -112,7 +111,7 @@ where
/// the digest /// the digest
pub fn send( pub fn send(
self, self,
) -> impl Future<Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>> { ) -> impl Future<Output = Result<ClientResponse<Decoder<Payload>>, SendRequestError>> {
self.req.send_body(self.body.as_ref().to_vec()) self.req.send_body(self.body.as_ref().to_vec())
} }
} }

View file

@ -8,10 +8,9 @@
//! //!
//! ### Use it in a server //! ### Use it in a server
//! ```rust,ignore //! ```rust,ignore
//! use actix::System; //! use actix_web::{http::StatusCode, web, App, HttpResponse, HttpServer, ResponseError};
//! use actix_web::{web, App, HttpResponse, HttpServer, ResponseError}; //! use futures::future::{err, ok, Ready};
//! use failure::Fail; //! use http_signature_normalization_actix::prelude::*;
//! use http_signature_normalization_actix::{prelude::*, verify::Algorithm};
//! use sha2::{Digest, Sha256}; //! use sha2::{Digest, Sha256};
//! //!
//! #[derive(Clone, Debug)] //! #[derive(Clone, Debug)]
@ -19,7 +18,7 @@
//! //!
//! impl SignatureVerify for MyVerify { //! impl SignatureVerify for MyVerify {
//! type Error = MyError; //! type Error = MyError;
//! type Future = Result<bool, Self::Error>; //! type Future = Ready<Result<bool, Self::Error>>;
//! //!
//! fn signature_verify( //! fn signature_verify(
//! &mut self, //! &mut self,
@ -30,28 +29,28 @@
//! ) -> Self::Future { //! ) -> Self::Future {
//! match algorithm { //! match algorithm {
//! Some(Algorithm::Hs2019) => (), //! Some(Algorithm::Hs2019) => (),
//! _ => return Err(MyError::Algorithm), //! _ => return err(MyError::Algorithm),
//! }; //! };
//! //!
//! if key_id != "my-key-id" { //! 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 //! ok(decoded == signing_string.as_bytes())
//! // byte equality
//! Ok(decoded == signing_string.as_bytes())
//! } //! }
//! } //! }
//! //!
//! fn index(_: (DigestVerified, SignatureVerified)) -> &'static str { //! async fn index(_: (DigestVerified, SignatureVerified)) -> &'static str {
//! "Eyyyyup" //! "Eyyyyup"
//! } //! }
//! //!
//! fn main() -> Result<(), Box<dyn std::error::Error>> { //! #[actix_rt::main]
//! let sys = System::new("server-example"); //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!
//! let config = Config::default(); //! let config = Config::default();
//! //!
//! HttpServer::new(move || { //! HttpServer::new(move || {
@ -65,103 +64,84 @@
//! .route("/", web::post().to(index)) //! .route("/", web::post().to(index))
//! }) //! })
//! .bind("127.0.0.1:8010")? //! .bind("127.0.0.1:8010")?
//! .start(); //! .run()
//! .await?;
//! //!
//! sys.run()?;
//! Ok(()) //! Ok(())
//! } //! }
//! //!
//! #[derive(Debug, Fail)] //! #[derive(Debug, thiserror::Error)]
//! enum MyError { //! enum MyError {
//! #[fail(display = "Failed to verify, {}", _0)] //! #[error("Failed to verify, {}", _0)]
//! Verify(#[cause] PrepareVerifyError), //! Verify(#[from] PrepareVerifyError),
//! //!
//! #[fail(display = "Unsupported algorithm")] //! #[error("Unsupported algorithm")]
//! Algorithm, //! Algorithm,
//! //!
//! #[fail(display = "Couldn't decode signature")] //! #[error("Couldn't decode signature")]
//! Decode, //! Decode,
//! //!
//! #[fail(display = "Invalid key")] //! #[error("Invalid key")]
//! Key, //! Key,
//! } //! }
//! //!
//! impl ResponseError for MyError { //! impl ResponseError for MyError {
//! fn status_code(&self) -> StatusCode {
//! StatusCode::BAD_REQUEST
//! }
//!
//! fn error_response(&self) -> HttpResponse { //! fn error_response(&self) -> HttpResponse {
//! HttpResponse::BadRequest().finish() //! 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 //! ### Use it in a client
//! ```rust,ignore //! ```rust,ignore
//! use actix::System;
//! use actix_web::client::Client; //! use actix_web::client::Client;
//! use failure::Fail;
//! use futures::future::{lazy, Future};
//! use http_signature_normalization_actix::prelude::*; //! use http_signature_normalization_actix::prelude::*;
//! use sha2::{Digest, Sha256}; //! use sha2::{Digest, Sha256};
//! //!
//! fn main() { //! #[actix_rt::main]
//! System::new("client-example") //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! .block_on(lazy(|| { //! let config = Config::default();
//! let config = Config::default(); //! let mut digest = Sha256::new();
//! let mut digest = Sha256::new();
//! //!
//! Client::default() //! let mut response = Client::default()
//! .post("http://127.0.0.1:8010/") //! .post("http://127.0.0.1:8010/")
//! .header("User-Agent", "Actix Web") //! .header("User-Agent", "Actix Web")
//! .authorization_signature_with_digest( //! .authorization_signature_with_digest(&config, "my-key-id", &mut digest, "Hewwo-owo", |s| {
//! &config, //! Ok(base64::encode(s)) as Result<_, MyError>
//! "my-key-id", //! })?
//! &mut digest, //! .send()
//! "Hewwo-owo", //! .await
//! |s| { //! .map_err(|e| {
//! // In a real-world system, you'd actually want to sign the string, //! eprintln!("Error, {}", e);
//! // not just base64 encode it //! MyError::SendRequest
//! Ok(base64::encode(s)) as Result<_, MyError> //! })?;
//! }, //!
//! ) //! let body = response.body().await.map_err(|e| {
//! .unwrap() //! eprintln!("Error, {}", e);
//! .send() //! MyError::Body
//! .map_err(|_| ()) //! })?;
//! .and_then(|mut res| res.body().map_err(|_| ())) //!
//! .map(|body| { //! println!("{:?}", body);
//! println!("{:?}", body); //! Ok(())
//! })
//! }))
//! .unwrap();
//! } //! }
//! //!
//! #[derive(Debug, Fail)] //! #[derive(Debug, thiserror::Error)]
//! pub enum MyError { //! pub enum MyError {
//! #[fail(display = "Failed to read header, {}", _0)] //! #[error("Failed to read header, {0}")]
//! Convert(#[cause] ToStrError), //! Convert(#[from] ToStrError),
//! //!
//! #[fail(display = "Failed to create header, {}", _0)] //! #[error("Failed to create header, {0}")]
//! Header(#[cause] InvalidHeaderValue), //! Header(#[from] InvalidHeaderValue),
//! }
//! //!
//! impl From<ToStrError> for MyError { //! #[error("Failed to send request")]
//! fn from(e: ToStrError) -> Self { //! SendRequest,
//! MyError::Convert(e)
//! }
//! }
//! //!
//! impl From<InvalidHeaderValue> for MyError { //! #[error("Failed to retrieve request body")]
//! fn from(e: InvalidHeaderValue) -> Self { //! Body,
//! MyError::Header(e)
//! }
//! } //! }
//! ``` //! ```
@ -171,9 +151,7 @@ use actix_web::http::{
Method, Method,
}; };
use failure::Fail; use std::{collections::BTreeMap, fmt::Display, future::Future};
use futures::future::IntoFuture;
use std::{collections::BTreeMap, fmt::Display};
mod sign; mod sign;
@ -187,7 +165,7 @@ pub mod middleware;
pub mod prelude { pub mod prelude {
pub use crate::{ pub use crate::{
middleware::{SignatureVerified, VerifySignature}, middleware::{SignatureVerified, VerifySignature},
verify::Unverified, verify::{Algorithm, Unverified},
Config, PrepareVerifyError, Sign, SignatureVerify, Config, PrepareVerifyError, Sign, SignatureVerify,
}; };
@ -220,7 +198,7 @@ pub trait SignatureVerify {
type Error: actix_web::ResponseError; type Error: actix_web::ResponseError;
/// The future that resolves to the verification state of the signature /// The future that resolves to the verification state of the signature
type Future: IntoFuture<Item = bool, Error = Self::Error>; type Future: Future<Output = Result<bool, Self::Error>>;
/// Given the algorithm, key_id, signature, and signing_string, produce a future that resulves /// Given the algorithm, key_id, signature, and signing_string, produce a future that resulves
/// to a the verification status /// to a the verification status
@ -259,16 +237,16 @@ pub struct Config {
pub config: http_signature_normalization::Config, pub config: http_signature_normalization::Config,
} }
#[derive(Debug, Fail)] #[derive(Debug, thiserror::Error)]
/// An error when preparing to verify a request /// An error when preparing to verify a request
pub enum PrepareVerifyError { pub enum PrepareVerifyError {
#[fail(display = "Signature error, {}", _0)] #[error("Signature error, {0}")]
/// An error validating the request /// 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 /// An error converting the header to a string for validation
Header(#[cause] ToStrError), Header(#[from] ToStrError),
} }
impl Config { impl Config {
@ -318,15 +296,3 @@ impl Config {
Ok(unverified) 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)
}
}

View file

@ -2,14 +2,17 @@
use actix_web::{ use actix_web::{
dev::{Body, Payload, Service, ServiceRequest, ServiceResponse, Transform}, 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, ready, Ready};
use futures::{ use std::{
future::{err, ok, Either, FutureResult, IntoFuture}, cell::RefCell,
Future, Poll, future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll},
}; };
use std::{cell::RefCell, rc::Rc};
use crate::{Config, SignatureVerify}; use crate::{Config, SignatureVerify};
@ -45,8 +48,8 @@ enum HeaderKind {
Signature, Signature,
} }
#[derive(Clone, Debug, Fail)] #[derive(Clone, Debug, thiserror::Error)]
#[fail(display = "Failed to verify http signature")] #[error("Failed to verify http signature")]
#[doc(hidden)] #[doc(hidden)]
pub struct VerifyError; pub struct VerifyError;
@ -82,17 +85,12 @@ impl<T, S> VerifyMiddleware<T, S>
where where
T: SignatureVerify + 'static, T: SignatureVerify + 'static,
T::Future: 'static, T::Future: 'static,
S: Service< S: Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error> + 'static,
Request = ServiceRequest,
Response = ServiceResponse<Body>,
Error = actix_web::Error,
> + 'static,
S::Error: 'static,
{ {
fn handle( fn handle(
&mut self, &mut self,
req: ServiceRequest, req: ServiceRequest,
) -> Box<dyn Future<Item = ServiceResponse<Body>, Error = actix_web::Error>> { ) -> Pin<Box<dyn Future<Output = Result<ServiceResponse<Body>, Error>>>> {
let res = self.1.begin_verify( let res = self.1.begin_verify(
req.method(), req.method(),
req.uri().path_and_query(), req.uri().path_and_query(),
@ -101,32 +99,29 @@ where
let unverified = match res { let unverified = match res {
Ok(unverified) => unverified, 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 algorithm = unverified.algorithm().map(|a| a.clone());
let key_id = unverified.key_id().to_owned(); 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 self.4
.signature_verify(algorithm, &key_id, signature, signing_string) .signature_verify(algorithm, &key_id, signature, signing_string)
}); });
let service = self.0.clone(); Box::pin(async move {
let verified = fut.await?;
Box::new( if verified {
verified req.extensions_mut().insert(SignatureVerified);
.into_future() service.borrow_mut().call(req).await
.from_err::<actix_web::Error>() } else {
.and_then(move |verified| { Err(VerifyError.into())
if verified { }
req.extensions_mut().insert(SignatureVerified); })
Either::A(service.borrow_mut().call(req))
} else {
Either::B(err(VerifyError.into()))
}
}),
)
} }
} }
@ -142,14 +137,16 @@ impl HeaderKind {
impl FromRequest for SignatureVerified { impl FromRequest for SignatureVerified {
type Error = VerifyError; type Error = VerifyError;
type Future = Result<Self, Self::Error>; type Future = Ready<Result<Self, Self::Error>>;
type Config = (); type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
req.extensions() ready(
.get::<Self>() req.extensions()
.map(|s| *s) .get::<Self>()
.ok_or(VerifyError) .map(|s| *s)
.ok_or(VerifyError),
)
} }
} }
@ -168,7 +165,7 @@ where
type Error = actix_web::Error; type Error = actix_web::Error;
type Transform = VerifyMiddleware<T, S>; type Transform = VerifyMiddleware<T, S>;
type InitError = (); type InitError = ();
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(VerifyMiddleware( ok(VerifyMiddleware(
@ -194,10 +191,10 @@ where
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse<Body>; type Response = ServiceResponse<Body>;
type Error = actix_web::Error; type Error = actix_web::Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.0.borrow_mut().poll_ready() self.0.borrow_mut().poll_ready(cx)
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -213,21 +210,21 @@ where
return self.handle(req); return self.handle(req);
} }
Box::new(err(VerifyError.into())) Box::pin(err(VerifyError.into()))
} else if self.3 { } else if self.3 {
Box::new(self.0.borrow_mut().call(req)) Box::pin(self.0.borrow_mut().call(req))
} else { } else {
Box::new(err(VerifyError.into())) Box::pin(err(VerifyError.into()))
} }
} }
} }
impl ResponseError for VerifyError { impl ResponseError for VerifyError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest().finish() HttpResponse::BadRequest().finish()
} }
fn render_response(&self) -> HttpResponse {
self.error_response()
}
} }

View file

@ -13,4 +13,4 @@ edition = "2018"
[dependencies] [dependencies]
http = "0.2" http = "0.2"
http-signature-normalization = { version = "0.2.0", path = ".." } http-signature-normalization = { version = "0.3.0", path = ".." }

View file

@ -5,9 +5,21 @@ authors = ["asonix <asonix@asonix.dog>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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] [dependencies]
base64 = { version = "0.11.0", optional = true }
bytes = "0.5.3"
futures = "0.3.1"
chrono = "0.4.10" chrono = "0.4.10"
http = "0.2.0" http = "0.2.0"
http-signature-normalization = { version = "0.2.0", path = ".." } http-signature-normalization = { version = "0.3.0", path = ".." }
reqwest = "0.10.1" 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 }

View file

@ -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<Output = Self>;
fn with_digest<T>(&mut self, creator: T) -> Self::Future;
}
impl WithDigest for Request {
type Future = Pin<Box<dyn Future<Output = Self> + Send>>;
}

View file

@ -6,6 +6,8 @@ use reqwest::{
}; };
use std::fmt::Display; use std::fmt::Display;
pub mod digest;
pub struct Config(http_signature_normalization::Config); pub struct Config(http_signature_normalization::Config);
pub trait Sign { pub trait Sign {

View file

@ -45,7 +45,7 @@
//! ``` //! ```
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use std::{collections::BTreeMap, error::Error, fmt}; use std::collections::BTreeMap;
pub mod create; pub mod create;
pub mod verify; pub mod verify;
@ -77,15 +77,18 @@ pub struct Config {
pub expires_after: Duration, pub expires_after: Duration,
} }
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
/// Error preparing a header for validation /// Error preparing a header for validation
/// ///
/// This could be due to a missing header, and unparsable header, or an expired header /// This could be due to a missing header, and unparsable header, or an expired header
pub enum PrepareVerifyError { pub enum PrepareVerifyError {
#[error("{0}")]
/// Error validating the header /// Error validating the header
Validate(ValidateError), Validate(#[from] ValidateError),
#[error("{0}")]
/// Error parsing the header /// Error parsing the header
Parse(ParseSignatureError), Parse(#[from] ParseSignatureError),
} }
impl Config { impl Config {
@ -189,43 +192,6 @@ fn build_signing_string(
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<ValidateError> for PrepareVerifyError {
fn from(v: ValidateError) -> Self {
PrepareVerifyError::Validate(v)
}
}
impl From<ParseSignatureError> for PrepareVerifyError {
fn from(p: ParseSignatureError) -> Self {
PrepareVerifyError::Parse(p)
}
}
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {