Add digest header as feature

This commit is contained in:
asonix 2019-09-12 20:12:35 -05:00
parent ef2a4e338f
commit 0fd087ced5
11 changed files with 816 additions and 206 deletions

View file

@ -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"

View file

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

View file

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

View 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)
}
}

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

View 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
}
}

View 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
}
}

View 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))
}
}

View file

@ -1,12 +1,7 @@
use actix_web::{
client::ClientRequest,
dev::ServiceRequest,
http::{
use actix_web::http::{
header::{HeaderMap, InvalidHeaderValue, ToStrError},
uri::PathAndQuery,
Method,
},
HttpRequest,
};
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 {

View 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)
}

View 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(),
)
}
}