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]
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 <asonix@asonix.dog>"]
license-file = "LICENSE"
readme = "README.md"
@ -20,4 +20,5 @@ members = [
]
[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)
- [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.

View file

@ -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 <asonix@asonix.dog>"]
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"

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)
- [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<dyn std::error::Error>> {
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<ToStrError> for MyError {
fn from(e: ToStrError) -> Self {
MyError::Convert(e)
}
}
#[error("Failed to send request")]
SendRequest,
impl From<InvalidHeaderValue> 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<bool, Self::Error>;
type Future = Ready<Result<bool, Self::Error>>;
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<dyn std::error::Error>> {
let sys = System::new("server-example");
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::default();
HttpServer::new(move || {
@ -137,41 +129,35 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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<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 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<dyn std::error::Error>> {
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<ToStrError> for MyError {
fn from(e: ToStrError) -> Self {
MyError::Convert(e)
}
}
#[error("Failed to send request")]
SendRequest,
impl From<InvalidHeaderValue> for MyError {
fn from(e: InvalidHeaderValue) -> Self {
MyError::Header(e)
}
#[error("Failed to retrieve request body")]
Body,
}

View file

@ -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<bool, Self::Error>;
type Future = Ready<Result<bool, Self::Error>>;
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<dyn std::error::Error>> {
let sys = System::new("server-example");
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::default();
HttpServer::new(move || {
@ -53,39 +54,33 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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<PrepareVerifyError> for MyError {
fn from(e: PrepareVerifyError) -> Self {
MyError::Verify(e)
}
}

View file

@ -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<T>(bool, T);
#[doc(hidden)]
pub struct VerifyMiddleware<T, S>(Rc<RefCell<S>>, 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<Self, Self::Error>;
type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
req.extensions()
.get::<Self>()
.map(|s| *s)
.ok_or(VerifyError)
ready(
req.extensions()
.get::<Self>()
.map(|s| *s)
.ok_or(VerifyError),
)
}
}
@ -93,7 +100,7 @@ where
type Error = actix_web::Error;
type Transform = VerifyMiddleware<T, S>;
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 {
ok(VerifyMiddleware(
@ -117,41 +124,48 @@ where
type Request = ServiceRequest;
type Response = ServiceResponse<Body>;
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> {
self.0.borrow_mut().poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
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<dyn Stream<Item = Bytes, Error = PayloadError>>)
(Box::pin(once(ok(bytes)))
as Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>> + '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<Vec<DigestPart>> {
}
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)
}
}

View file

@ -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<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())
}
}

View file

@ -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<bool, Self::Error>;
//! type Future = Ready<Result<bool, Self::Error>>;
//!
//! 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<dyn std::error::Error>> {
//! let sys = System::new("server-example");
//!
//! #[actix_rt::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! 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<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();
//! #[actix_rt::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! 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<ToStrError> for MyError {
//! fn from(e: ToStrError) -> Self {
//! MyError::Convert(e)
//! }
//! }
//! #[error("Failed to send request")]
//! SendRequest,
//!
//! impl From<InvalidHeaderValue> 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<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
/// 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<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::{
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<T, S> VerifyMiddleware<T, S>
where
T: SignatureVerify + 'static,
T::Future: 'static,
S: Service<
Request = ServiceRequest,
Response = ServiceResponse<Body>,
Error = actix_web::Error,
> + 'static,
S::Error: 'static,
S: Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error> + 'static,
{
fn handle(
&mut self,
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(
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::<actix_web::Error>()
.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<Self, Self::Error>;
type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
req.extensions()
.get::<Self>()
.map(|s| *s)
.ok_or(VerifyError)
ready(
req.extensions()
.get::<Self>()
.map(|s| *s)
.ok_or(VerifyError),
)
}
}
@ -168,7 +165,7 @@ where
type Error = actix_web::Error;
type Transform = VerifyMiddleware<T, S>;
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 {
ok(VerifyMiddleware(
@ -194,10 +191,10 @@ where
type Request = ServiceRequest;
type Response = ServiceResponse<Body>;
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> {
self.0.borrow_mut().poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
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()
}
}

View file

@ -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 = ".." }

View file

@ -5,9 +5,21 @@ authors = ["asonix <asonix@asonix.dog>"]
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 }

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;
pub mod digest;
pub struct Config(http_signature_normalization::Config);
pub trait Sign {

View file

@ -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<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 {
fn default() -> Self {
Config {