mirror of
https://git.asonix.dog/asonix/http-signature-normalization.git
synced 2024-11-25 10:51:01 +00:00
Add digest header as feature
This commit is contained in:
parent
ef2a4e338f
commit
0fd087ced5
11 changed files with 816 additions and 206 deletions
|
@ -11,12 +11,25 @@ edition = "2018"
|
|||
[features]
|
||||
default = []
|
||||
digest = ["base64", "futures"]
|
||||
sha-2 = ["digest", "sha2"]
|
||||
sha-3 = ["digest", "sha3"]
|
||||
|
||||
[[example]]
|
||||
name = "server"
|
||||
required-features = ["sha-2"]
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["sha-2"]
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0"
|
||||
base64 = { version = "0.10", optional = true }
|
||||
http-signature-normalization = { version = "0.1.0", path = ".." }
|
||||
failure = "0.1"
|
||||
futures = { version = "0.1", optional = true }
|
||||
http-signature-normalization = { version = "0.1.0", path = ".." }
|
||||
sha2 = { version = "0.8", optional = true }
|
||||
sha3 = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix = "0.8"
|
||||
|
|
|
@ -2,18 +2,24 @@ use actix::System;
|
|||
use actix_web::client::Client;
|
||||
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();
|
||||
|
||||
Client::default()
|
||||
.get("http://127.0.0.1:8010/")
|
||||
.post("http://127.0.0.1:8010/")
|
||||
.header("User-Agent", "Actix Web")
|
||||
.authorization_signature::<_, MyError, _>(&config, "my-key-id", |s| {
|
||||
Ok(s.as_bytes().to_vec())
|
||||
})
|
||||
.authorization_signature_with_digest::<_, MyError, _, _, _>(
|
||||
&config,
|
||||
"my-key-id",
|
||||
&mut digest,
|
||||
"Hewwo owo",
|
||||
|s| Ok(s.as_bytes().to_vec()),
|
||||
)
|
||||
.unwrap()
|
||||
.send()
|
||||
.map_err(|_| ())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use actix::System;
|
||||
use actix_web::{web, App, HttpRequest, HttpServer, ResponseError};
|
||||
use http_signature_normalization_actix::{prelude::*, verify::Algorithm};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt;
|
||||
|
||||
fn index((req, config): (HttpRequest, web::Data<Config>)) -> Result<&'static str, MyError> {
|
||||
|
@ -26,7 +27,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.data(Config::default())
|
||||
.route("/", web::get().to(index))
|
||||
.wrap(VerifyDigest::new(Sha256::new()))
|
||||
.route("/", web::post().to(index))
|
||||
})
|
||||
.bind("127.0.0.1:8010")?
|
||||
.start();
|
||||
|
|
165
http-signature-normalization-actix/src/digest/middleware.rs
Normal file
165
http-signature-normalization-actix/src/digest/middleware.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use actix_web::{
|
||||
dev::{Body, Service, ServiceRequest, ServiceResponse, Transform},
|
||||
error::PayloadError,
|
||||
http::header::HeaderValue,
|
||||
web::Bytes,
|
||||
HttpMessage, HttpResponse, ResponseError,
|
||||
};
|
||||
use failure::Fail;
|
||||
use futures::{
|
||||
future::{err, ok, Either, FutureResult},
|
||||
stream::once,
|
||||
Future, Poll, Stream,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use super::{DigestPart, DigestVerify};
|
||||
|
||||
pub struct VerifyDigest<T>(bool, T);
|
||||
pub struct VerifyMiddleware<T, S>(Rc<RefCell<S>>, bool, T);
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "Error in upstream middleware")]
|
||||
pub struct UpstreamError;
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "Error verifying digest")]
|
||||
pub struct VerifyError;
|
||||
|
||||
impl<T> VerifyDigest<T>
|
||||
where
|
||||
T: DigestVerify + Clone,
|
||||
{
|
||||
pub fn new(verify_digest: T) -> Self {
|
||||
VerifyDigest(true, verify_digest)
|
||||
}
|
||||
|
||||
pub fn optional(self) -> Self {
|
||||
VerifyDigest(false, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Transform<S> for VerifyDigest<T>
|
||||
where
|
||||
T: DigestVerify + Clone + 'static,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<Body>> + 'static,
|
||||
S::Error: 'static,
|
||||
{
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<Body>;
|
||||
type Error = actix_web::Error;
|
||||
type Transform = VerifyMiddleware<T, S>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<Self::Transform, Self::InitError>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(VerifyMiddleware(
|
||||
Rc::new(RefCell::new(service)),
|
||||
self.0,
|
||||
self.1.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Service for VerifyMiddleware<T, S>
|
||||
where
|
||||
T: DigestVerify + Clone + 'static,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<Body>> + 'static,
|
||||
S::Error: 'static,
|
||||
{
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<Body>;
|
||||
type Error = actix_web::Error;
|
||||
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.poll_ready()
|
||||
.map_err(|_| UpstreamError.into())
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let mut verify_digest = self.2.clone();
|
||||
if let Some(digest) = req.headers().get("Digest") {
|
||||
let vec = match parse_digest(digest) {
|
||||
Some(vec) => vec,
|
||||
None => return Box::new(err(VerifyError.into())),
|
||||
};
|
||||
let payload = req.take_payload();
|
||||
let service = self.0.clone();
|
||||
|
||||
Box::new(payload.concat2().from_err().and_then(move |bytes| {
|
||||
if verify_digest.verify(&vec, &bytes.as_ref()) {
|
||||
req.set_payload(
|
||||
(Box::new(once(Ok(bytes)))
|
||||
as Box<dyn Stream<Item = Bytes, Error = PayloadError>>)
|
||||
.into(),
|
||||
);
|
||||
|
||||
Either::A(
|
||||
service
|
||||
.borrow_mut()
|
||||
.call(req)
|
||||
.map_err(|_| UpstreamError.into()),
|
||||
)
|
||||
} else {
|
||||
Either::B(err(VerifyError.into()))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
if self.1 {
|
||||
Box::new(err(VerifyError.into()))
|
||||
} else {
|
||||
Box::new(
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.call(req)
|
||||
.map_err(|_| UpstreamError.into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_digest(h: &HeaderValue) -> Option<Vec<DigestPart>> {
|
||||
let h = h.to_str().ok()?.split(";").next()?;
|
||||
let v: Vec<_> = h
|
||||
.split(",")
|
||||
.filter_map(|p| {
|
||||
let mut iter = p.splitn(2, "=");
|
||||
iter.next()
|
||||
.and_then(|alg| iter.next().map(|value| (alg, value)))
|
||||
})
|
||||
.map(|(alg, value)| DigestPart {
|
||||
algorithm: alg.to_owned(),
|
||||
digest: value.to_owned(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for UpstreamError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
|
||||
fn render_response(&self) -> HttpResponse {
|
||||
Self::error_response(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for VerifyError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::BadRequest().finish()
|
||||
}
|
||||
|
||||
fn render_response(&self) -> HttpResponse {
|
||||
Self::error_response(self)
|
||||
}
|
||||
}
|
87
http-signature-normalization-actix/src/digest/mod.rs
Normal file
87
http-signature-normalization-actix/src/digest/mod.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use actix_web::{
|
||||
client::{ClientRequest, ClientResponse},
|
||||
error::PayloadError,
|
||||
http::header::{InvalidHeaderValue, ToStrError},
|
||||
web::Bytes,
|
||||
};
|
||||
use futures::{Future, Stream};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{Config, Sign};
|
||||
|
||||
pub mod middleware;
|
||||
#[cfg(feature = "sha-2")]
|
||||
pub mod sha2;
|
||||
#[cfg(feature = "sha-3")]
|
||||
pub mod sha3;
|
||||
|
||||
mod sign;
|
||||
|
||||
pub trait DigestCreate {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String;
|
||||
}
|
||||
|
||||
pub trait DigestVerify {
|
||||
fn verify(&mut self, digests: &[DigestPart], payload: &[u8]) -> bool;
|
||||
}
|
||||
|
||||
pub trait SignExt: Sign {
|
||||
fn authorization_signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
digest: &mut D,
|
||||
v: V,
|
||||
f: F,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: DigestCreate,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized;
|
||||
|
||||
fn signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
digest: &mut D,
|
||||
v: V,
|
||||
f: F,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: DigestCreate,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub struct DigestPart {
|
||||
pub algorithm: String,
|
||||
pub digest: String,
|
||||
}
|
||||
|
||||
pub struct DigestClient<V> {
|
||||
req: ClientRequest,
|
||||
body: V,
|
||||
}
|
||||
|
||||
impl<V> DigestClient<V>
|
||||
where
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
pub fn new(req: ClientRequest, body: V) -> Self {
|
||||
DigestClient { req, body }
|
||||
}
|
||||
|
||||
pub fn send(
|
||||
self,
|
||||
) -> impl Future<Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>> {
|
||||
self.req.send_body(self.body.as_ref().to_vec())
|
||||
}
|
||||
}
|
153
http-signature-normalization-actix/src/digest/sha2.rs
Normal file
153
http-signature-normalization-actix/src/digest/sha2.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use sha2::{Digest as _, Sha224, Sha256, Sha384, Sha512, Sha512Trunc224, Sha512Trunc256};
|
||||
|
||||
use super::{DigestCreate, DigestPart, DigestVerify};
|
||||
|
||||
impl DigestCreate for Sha224 {
|
||||
const NAME: &'static str = "sha-224";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha224 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha256 {
|
||||
const NAME: &'static str = "sha-256";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha256 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha384 {
|
||||
const NAME: &'static str = "sha-384";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha384 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha512 {
|
||||
const NAME: &'static str = "sha-512";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha512 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha512Trunc224 {
|
||||
const NAME: &'static str = "sha-512-224";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha512Trunc224 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha512Trunc256 {
|
||||
const NAME: &'static str = "sha-512-256";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha512Trunc256 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
231
http-signature-normalization-actix/src/digest/sha3.rs
Normal file
231
http-signature-normalization-actix/src/digest/sha3.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
use sha3::{
|
||||
Digest as _, Keccak224, Keccak256, Keccak256Full, Keccak384, Keccak512, Sha3_224, Sha3_256,
|
||||
Sha3_384, Sha3_512,
|
||||
};
|
||||
|
||||
use super::{DigestCreate, DigestPart, DigestVerify};
|
||||
|
||||
impl DigestCreate for Sha3_224 {
|
||||
const NAME: &'static str = "sha3-224";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha3_224 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha3_256 {
|
||||
const NAME: &'static str = "sha3-256";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha3_256 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha3_384 {
|
||||
const NAME: &'static str = "sha3-384";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha3_384 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Sha3_512 {
|
||||
const NAME: &'static str = "sha3-512";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Sha3_512 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Keccak224 {
|
||||
const NAME: &'static str = "keccak-224";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Keccak224 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Keccak256 {
|
||||
const NAME: &'static str = "keccak-256";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Keccak256 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Keccak256Full {
|
||||
const NAME: &'static str = "keccak-256-full";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Keccak256Full {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Keccak384 {
|
||||
const NAME: &'static str = "keccak-384";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Keccak384 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestCreate for Keccak512 {
|
||||
const NAME: &'static str = "keccak-512";
|
||||
|
||||
fn compute(&mut self, input: &[u8]) -> String {
|
||||
self.input(input);
|
||||
base64::encode(&self.result_reset())
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestVerify for Keccak512 {
|
||||
fn verify(&mut self, parts: &[DigestPart], bytes: &[u8]) -> bool {
|
||||
if let Some(part) = parts
|
||||
.iter()
|
||||
.find(|p| p.algorithm == <Self as DigestCreate>::NAME)
|
||||
{
|
||||
self.input(bytes);
|
||||
let digest = base64::encode(&self.result_reset());
|
||||
|
||||
return part.digest == digest;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
58
http-signature-normalization-actix/src/digest/sign.rs
Normal file
58
http-signature-normalization-actix/src/digest/sign.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use actix_web::{
|
||||
client::ClientRequest,
|
||||
http::header::{InvalidHeaderValue, ToStrError},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
digest::{DigestClient, DigestCreate, SignExt},
|
||||
Config, Sign,
|
||||
};
|
||||
|
||||
impl SignExt for ClientRequest {
|
||||
fn authorization_signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
digest: &mut D,
|
||||
v: V,
|
||||
f: F,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: DigestCreate,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized,
|
||||
{
|
||||
let digest = digest.compute(v.as_ref());
|
||||
|
||||
self.set_header("Digest", format!("{}={}", D::NAME, digest))
|
||||
.authorization_signature(config, key_id, f)
|
||||
.map(|c| DigestClient::new(c, v))
|
||||
}
|
||||
|
||||
fn signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
digest: &mut D,
|
||||
v: V,
|
||||
f: F,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: DigestCreate,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized,
|
||||
{
|
||||
let digest = digest.compute(v.as_ref());
|
||||
|
||||
self.set_header("Digest", format!("{}={}", D::NAME, digest))
|
||||
.signature(config, key_id, f)
|
||||
.map(|c| DigestClient::new(c, v))
|
||||
}
|
||||
}
|
|
@ -1,12 +1,7 @@
|
|||
use actix_web::{
|
||||
client::ClientRequest,
|
||||
dev::ServiceRequest,
|
||||
http::{
|
||||
header::{HeaderMap, InvalidHeaderValue, ToStrError},
|
||||
uri::PathAndQuery,
|
||||
Method,
|
||||
},
|
||||
HttpRequest,
|
||||
use actix_web::http::{
|
||||
header::{HeaderMap, InvalidHeaderValue, ToStrError},
|
||||
uri::PathAndQuery,
|
||||
Method,
|
||||
};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
|
@ -14,40 +9,26 @@ use std::{
|
|||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
use actix_web::{client::ClientResponse, error::PayloadError, web::Bytes};
|
||||
#[cfg(feature = "digest")]
|
||||
use futures::{Future, Stream};
|
||||
mod sign;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub mod digest;
|
||||
|
||||
pub mod verify;
|
||||
pub mod prelude {
|
||||
pub use crate::{verify::Unverified, Config, Sign, Verify, VerifyError};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub use crate::Digest;
|
||||
pub use crate::digest::{
|
||||
middleware::VerifyDigest, DigestClient, DigestCreate, DigestPart, DigestVerify, SignExt,
|
||||
};
|
||||
|
||||
pub use actix_web::http::header::{InvalidHeaderValue, ToStrError};
|
||||
}
|
||||
|
||||
pub mod create;
|
||||
|
||||
pub mod verify {
|
||||
pub use http_signature_normalization::verify::{
|
||||
Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified,
|
||||
ValidateError,
|
||||
};
|
||||
}
|
||||
|
||||
use self::{
|
||||
create::{Signed, Unsigned},
|
||||
verify::Unverified,
|
||||
};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub trait Digest {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn compute(input: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
use self::{create::Unsigned, verify::Unverified};
|
||||
|
||||
pub trait Verify {
|
||||
fn begin_verify(&self, config: &Config) -> Result<Unverified, VerifyError>;
|
||||
|
@ -67,38 +48,6 @@ pub trait Sign {
|
|||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
Self: Sized;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
fn authorization_signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
f: F,
|
||||
v: V,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: Digest,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
fn signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
f: F,
|
||||
v: V,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: Digest,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -106,28 +55,6 @@ pub struct Config {
|
|||
pub config: http_signature_normalization::Config,
|
||||
}
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub struct DigestClient<V> {
|
||||
req: ClientRequest,
|
||||
body: V,
|
||||
}
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
impl<V> DigestClient<V>
|
||||
where
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
pub fn new(req: ClientRequest, body: V) -> Self {
|
||||
DigestClient { req, body }
|
||||
}
|
||||
|
||||
pub fn send(
|
||||
self,
|
||||
) -> impl Future<Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>> {
|
||||
self.req.send_body(self.body.as_ref().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VerifyError {
|
||||
Sig(http_signature_normalization::VerifyError),
|
||||
|
@ -180,120 +107,6 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
impl Verify for HttpRequest {
|
||||
fn begin_verify(&self, config: &Config) -> Result<Unverified, VerifyError> {
|
||||
config.begin_verify(
|
||||
self.method(),
|
||||
self.uri().path_and_query(),
|
||||
self.headers().clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Verify for ServiceRequest {
|
||||
fn begin_verify(&self, config: &Config) -> Result<Unverified, VerifyError> {
|
||||
config.begin_verify(
|
||||
self.method(),
|
||||
self.uri().path_and_query(),
|
||||
self.headers().clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sign for ClientRequest {
|
||||
fn authorization_signature<F, E, K>(
|
||||
mut self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
f: F,
|
||||
) -> Result<Self, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
{
|
||||
let signed = prepare(&self, config, key_id, f)?;
|
||||
signed.authorization_header(self.headers_mut())?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn signature<F, E, K>(mut self, config: &Config, key_id: K, f: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
{
|
||||
let signed = prepare(&self, config, key_id, f)?;
|
||||
signed.signature_header(self.headers_mut())?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
fn authorization_signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
f: F,
|
||||
v: V,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: Digest,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized,
|
||||
{
|
||||
let digest = base64::encode(D::compute(v.as_ref()).as_slice());
|
||||
|
||||
self.set_header("Digest", format!("{}={}", D::NAME, digest))
|
||||
.authorization_signature(config, key_id, f)
|
||||
.map(|c| DigestClient::new(c, v))
|
||||
}
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
fn signature_with_digest<F, E, K, D, V>(
|
||||
self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
f: F,
|
||||
v: V,
|
||||
) -> Result<DigestClient<V>, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
D: Digest,
|
||||
V: AsRef<[u8]>,
|
||||
Self: Sized,
|
||||
{
|
||||
let digest = base64::encode(D::compute(v.as_ref()).as_slice());
|
||||
|
||||
self.set_header("Digest", format!("{}={}", D::NAME, digest))
|
||||
.signature(config, key_id, f)
|
||||
.map(|c| DigestClient::new(c, v))
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare<F, E, K>(request: &ClientRequest, config: &Config, key_id: K, f: F) -> Result<Signed, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError>,
|
||||
K: Display,
|
||||
{
|
||||
let unsigned = config.begin_sign(
|
||||
request.get_method(),
|
||||
request.get_uri().path_and_query(),
|
||||
request.headers().clone(),
|
||||
)?;
|
||||
|
||||
let key_id = key_id.to_string();
|
||||
|
||||
let signed = unsigned.sign(key_id, f)?;
|
||||
|
||||
Ok(signed)
|
||||
}
|
||||
|
||||
impl fmt::Display for VerifyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
|
|
55
http-signature-normalization-actix/src/sign.rs
Normal file
55
http-signature-normalization-actix/src/sign.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use actix_web::{
|
||||
client::ClientRequest,
|
||||
http::header::{InvalidHeaderValue, ToStrError},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{create::Signed, Config, Sign};
|
||||
|
||||
impl Sign for ClientRequest {
|
||||
fn authorization_signature<F, E, K>(
|
||||
mut self,
|
||||
config: &Config,
|
||||
key_id: K,
|
||||
f: F,
|
||||
) -> Result<Self, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
{
|
||||
let signed = prepare(&self, config, key_id, f)?;
|
||||
signed.authorization_header(self.headers_mut())?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn signature<F, E, K>(mut self, config: &Config, key_id: K, f: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError> + From<InvalidHeaderValue>,
|
||||
K: Display,
|
||||
{
|
||||
let signed = prepare(&self, config, key_id, f)?;
|
||||
signed.signature_header(self.headers_mut())?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare<F, E, K>(request: &ClientRequest, config: &Config, key_id: K, f: F) -> Result<Signed, E>
|
||||
where
|
||||
F: FnOnce(&str) -> Result<Vec<u8>, E>,
|
||||
E: From<ToStrError>,
|
||||
K: Display,
|
||||
{
|
||||
let unsigned = config.begin_sign(
|
||||
request.get_method(),
|
||||
request.get_uri().path_and_query(),
|
||||
request.headers().clone(),
|
||||
)?;
|
||||
|
||||
let key_id = key_id.to_string();
|
||||
|
||||
let signed = unsigned.sign(key_id, f)?;
|
||||
|
||||
Ok(signed)
|
||||
}
|
27
http-signature-normalization-actix/src/verify.rs
Normal file
27
http-signature-normalization-actix/src/verify.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use actix_web::{dev::ServiceRequest, HttpRequest};
|
||||
pub use http_signature_normalization::verify::{
|
||||
Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified,
|
||||
ValidateError,
|
||||
};
|
||||
|
||||
use crate::{Config, Verify, VerifyError};
|
||||
|
||||
impl Verify for HttpRequest {
|
||||
fn begin_verify(&self, config: &Config) -> Result<Unverified, VerifyError> {
|
||||
config.begin_verify(
|
||||
self.method(),
|
||||
self.uri().path_and_query(),
|
||||
self.headers().clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Verify for ServiceRequest {
|
||||
fn begin_verify(&self, config: &Config) -> Result<Unverified, VerifyError> {
|
||||
config.begin_verify(
|
||||
self.method(),
|
||||
self.uri().path_and_query(),
|
||||
self.headers().clone(),
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue