2019-09-11 22:05:58 +00:00
|
|
|
use actix_web::{
|
|
|
|
client::ClientRequest,
|
|
|
|
dev::ServiceRequest,
|
|
|
|
http::{
|
|
|
|
header::{HeaderMap, InvalidHeaderValue, ToStrError},
|
|
|
|
uri::PathAndQuery,
|
|
|
|
Method,
|
|
|
|
},
|
|
|
|
HttpRequest,
|
|
|
|
};
|
|
|
|
use std::{
|
|
|
|
collections::BTreeMap,
|
|
|
|
error::Error,
|
|
|
|
fmt::{self, Display},
|
|
|
|
};
|
|
|
|
|
2019-09-11 23:49:18 +00:00
|
|
|
#[cfg(feature = "digest")]
|
|
|
|
use actix_web::{client::ClientResponse, error::PayloadError, web::Bytes};
|
|
|
|
#[cfg(feature = "digest")]
|
|
|
|
use futures::{Future, Stream};
|
|
|
|
|
2019-09-11 22:05:58 +00:00
|
|
|
pub mod prelude {
|
|
|
|
pub use crate::{verify::Unverified, Config, Sign, Verify, VerifyError};
|
|
|
|
|
2019-09-11 23:49:18 +00:00
|
|
|
#[cfg(feature = "digest")]
|
|
|
|
pub use crate::Digest;
|
|
|
|
|
2019-09-11 22:05:58 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2019-09-11 23:49:18 +00:00
|
|
|
#[cfg(feature = "digest")]
|
|
|
|
pub trait Digest {
|
|
|
|
const NAME: &'static str;
|
|
|
|
|
|
|
|
fn compute(input: &[u8]) -> Vec<u8>;
|
|
|
|
}
|
|
|
|
|
2019-09-11 22:05:58 +00:00
|
|
|
pub trait Verify {
|
|
|
|
fn begin_verify(&self, config: &Config) -> Result<Unverified, VerifyError>;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Sign {
|
|
|
|
fn authorization_signature<F, E, K>(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,
|
|
|
|
Self: Sized;
|
|
|
|
|
|
|
|
fn signature<F, E, K>(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,
|
|
|
|
Self: Sized;
|
2019-09-11 23:49:18 +00:00
|
|
|
|
|
|
|
#[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;
|
2019-09-11 22:05:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
pub struct Config {
|
|
|
|
pub config: http_signature_normalization::Config,
|
|
|
|
}
|
|
|
|
|
2019-09-11 23:49:18 +00:00
|
|
|
#[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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-11 22:05:58 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum VerifyError {
|
|
|
|
Sig(http_signature_normalization::VerifyError),
|
|
|
|
Header(ToStrError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
|
|
|
pub fn begin_sign(
|
|
|
|
&self,
|
|
|
|
method: &Method,
|
|
|
|
path_and_query: Option<&PathAndQuery>,
|
|
|
|
headers: HeaderMap,
|
|
|
|
) -> Result<Unsigned, ToStrError> {
|
|
|
|
let headers = headers
|
|
|
|
.iter()
|
|
|
|
.map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
|
|
|
|
.collect::<Result<BTreeMap<_, _>, ToStrError>>()?;
|
|
|
|
|
|
|
|
let path_and_query = path_and_query
|
|
|
|
.map(|p| p.to_string())
|
|
|
|
.unwrap_or(String::from("/"));
|
|
|
|
|
|
|
|
let unsigned = self
|
|
|
|
.config
|
|
|
|
.begin_sign(&method.to_string(), &path_and_query, headers);
|
|
|
|
|
|
|
|
Ok(Unsigned { unsigned })
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn begin_verify(
|
|
|
|
&self,
|
|
|
|
method: &Method,
|
|
|
|
path_and_query: Option<&PathAndQuery>,
|
|
|
|
headers: HeaderMap,
|
|
|
|
) -> Result<Unverified, VerifyError> {
|
|
|
|
let headers = headers
|
|
|
|
.iter()
|
|
|
|
.map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
|
|
|
|
.collect::<Result<BTreeMap<_, _>, ToStrError>>()?;
|
|
|
|
|
|
|
|
let path_and_query = path_and_query
|
|
|
|
.map(|p| p.to_string())
|
|
|
|
.unwrap_or(String::from("/"));
|
|
|
|
|
|
|
|
let unverified = self
|
|
|
|
.config
|
|
|
|
.begin_verify(&method.to_string(), &path_and_query, headers)?;
|
|
|
|
|
|
|
|
Ok(unverified)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2019-09-11 23:49:18 +00:00
|
|
|
|
|
|
|
#[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))
|
|
|
|
}
|
2019-09-11 22:05:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
VerifyError::Sig(ref e) => write!(f, "Sig error, {}", e),
|
|
|
|
VerifyError::Header(ref e) => write!(f, "Header error, {}", e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Error for VerifyError {
|
|
|
|
fn description(&self) -> &str {
|
|
|
|
match *self {
|
|
|
|
VerifyError::Sig(ref e) => e.description(),
|
|
|
|
VerifyError::Header(ref e) => e.description(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
|
|
match *self {
|
|
|
|
VerifyError::Sig(ref e) => Some(e),
|
|
|
|
VerifyError::Header(ref e) => Some(e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<http_signature_normalization::VerifyError> for VerifyError {
|
|
|
|
fn from(e: http_signature_normalization::VerifyError) -> Self {
|
|
|
|
VerifyError::Sig(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ToStrError> for VerifyError {
|
|
|
|
fn from(e: ToStrError) -> Self {
|
|
|
|
VerifyError::Header(e)
|
|
|
|
}
|
|
|
|
}
|