mirror of
https://git.asonix.dog/asonix/http-signature-normalization.git
synced 2024-11-22 09:21:00 +00:00
Bump http-signature-normalization version, update actix to 3.0
This commit is contained in:
parent
ebeee051bf
commit
7f98235a37
15 changed files with 337 additions and 389 deletions
|
@ -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"
|
||||||
|
@ -21,3 +21,4 @@ members = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
|
||||||
"Hewwo-owo",
|
|
||||||
|s| Ok(base64::encode(s)) as Result<_, MyError>,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.send()
|
.send()
|
||||||
.map_err(|_| ())
|
.await
|
||||||
.and_then(|mut res| res.body().map_err(|_| ()))
|
.map_err(|e| {
|
||||||
.map(|body| {
|
eprintln!("Error, {}", e);
|
||||||
|
MyError::SendRequest
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let body = response.body().await.map_err(|e| {
|
||||||
|
eprintln!("Error, {}", e);
|
||||||
|
MyError::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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
"Hewwo-owo",
|
|
||||||
|s| Ok(base64::encode(s)) as Result<_, MyError>,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.send()
|
.send()
|
||||||
.map_err(|_| ())
|
.await
|
||||||
.and_then(|mut res| res.body().map_err(|_| ()))
|
.map_err(|e| {
|
||||||
.map(|body| {
|
eprintln!("Error, {}", e);
|
||||||
|
MyError::SendRequest
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let body = response.body().await.map_err(|e| {
|
||||||
|
eprintln!("Error, {}", e);
|
||||||
|
MyError::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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
ready(
|
||||||
req.extensions()
|
req.extensions()
|
||||||
.get::<Self>()
|
.get::<Self>()
|
||||||
.map(|s| *s)
|
.map(|s| *s)
|
||||||
.ok_or(VerifyError)
|
.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
//! "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>
|
//! Ok(base64::encode(s)) as Result<_, MyError>
|
||||||
//! },
|
//! })?
|
||||||
//! )
|
|
||||||
//! .unwrap()
|
|
||||||
//! .send()
|
//! .send()
|
||||||
//! .map_err(|_| ())
|
//! .await
|
||||||
//! .and_then(|mut res| res.body().map_err(|_| ()))
|
//! .map_err(|e| {
|
||||||
//! .map(|body| {
|
//! eprintln!("Error, {}", e);
|
||||||
|
//! MyError::SendRequest
|
||||||
|
//! })?;
|
||||||
|
//!
|
||||||
|
//! let body = response.body().await.map_err(|e| {
|
||||||
|
//! eprintln!("Error, {}", e);
|
||||||
|
//! MyError::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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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(
|
|
||||||
verified
|
|
||||||
.into_future()
|
|
||||||
.from_err::<actix_web::Error>()
|
|
||||||
.and_then(move |verified| {
|
|
||||||
if verified {
|
if verified {
|
||||||
req.extensions_mut().insert(SignatureVerified);
|
req.extensions_mut().insert(SignatureVerified);
|
||||||
Either::A(service.borrow_mut().call(req))
|
service.borrow_mut().call(req).await
|
||||||
} else {
|
} else {
|
||||||
Either::B(err(VerifyError.into()))
|
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 {
|
||||||
|
ready(
|
||||||
req.extensions()
|
req.extensions()
|
||||||
.get::<Self>()
|
.get::<Self>()
|
||||||
.map(|s| *s)
|
.map(|s| *s)
|
||||||
.ok_or(VerifyError)
|
.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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = ".." }
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
16
http-signature-normalization-reqwest/src/digest/mod.rs
Normal file
16
http-signature-normalization-reqwest/src/digest/mod.rs
Normal 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>>;
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
48
src/lib.rs
48
src/lib.rs
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue