Include digest verification

This commit is contained in:
asonix 2020-01-17 16:31:44 -06:00
parent 029ada114a
commit 77ecf6eaa7
4 changed files with 165 additions and 2 deletions

View file

@ -5,9 +5,20 @@ authors = ["asonix <asonix@asonix.dog>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["sha-2"]
digest = ["base64", "serde", "serde_json", "serde_urlencoded", "thiserror"]
sha-2 = ["digest", "sha2"]
[dependencies]
base64 = { version = "0.11.0", optional = true }
bytes = "0.5.3"
futures = "0.3.1"
http = "0.2.0"
http-signature-normalization-http = { version = "0.2.0", path = "../http-signature-normalization-http" }
warp = { git = "https://github.com/seanmonstar/warp" }
futures = "0.3.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 }
warp = "0.2.0"

View file

@ -0,0 +1,119 @@
use bytes::Bytes;
use std::str::FromStr;
use warp::{body, header, Filter, Rejection};
#[cfg(feature = "sha-2")]
mod sha_2;
pub trait DigestVerify {
fn verify(&mut self, digests: &[DigestPart], payload: &[u8]) -> bool;
}
pub struct DigestPart {
pub algorithm: String,
pub digest: String,
}
struct Digest {
parts: Vec<DigestPart>,
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("Could not verify digest")]
pub struct VerifyError;
#[derive(Clone, Debug, thiserror::Error)]
pub enum ParseDigestError {
#[error("Missing algorithm")]
Algorithm,
#[error("Missing digest")]
Digest,
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("Could not parse request body")]
pub struct ParseBodyError;
pub fn verify_digest_bytes<D>(
verifier: impl Filter<Extract = (D,), Error = Rejection> + Clone,
) -> impl Filter<Extract = (Bytes,), Error = Rejection> + Clone
where
D: DigestVerify + Send,
{
verifier
.and(parse_digest_header())
.and(body::bytes())
.and_then(|mut verifier: D, parts: Vec<DigestPart>, bytes: Bytes| {
async move {
if verifier.verify(&parts, &bytes) {
Ok(bytes)
} else {
Err(warp::reject::custom(VerifyError))
}
}
})
}
pub fn verify_digest_json<D, T>(
verifier: impl Filter<Extract = (D,), Error = Rejection> + Clone,
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone
where
D: DigestVerify + Send,
T: serde::de::DeserializeOwned,
{
verify_digest_bytes(verifier).and_then(|bytes: Bytes| {
async move {
serde_json::from_slice(&bytes).map_err(|_| warp::reject::custom(ParseBodyError))
}
})
}
pub fn verify_digest_form<D, T>(
verifier: impl Filter<Extract = (D,), Error = Rejection> + Clone,
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone
where
D: DigestVerify + Send,
T: serde::de::DeserializeOwned,
{
verify_digest_bytes(verifier).and_then(|bytes: Bytes| {
async move {
serde_urlencoded::from_bytes(&bytes).map_err(|_| warp::reject::custom(ParseBodyError))
}
})
}
fn parse_digest_header() -> impl Filter<Extract = (Vec<DigestPart>,), Error = Rejection> + Clone {
header::header::<Digest>("Digest").map(|d: Digest| d.parts)
}
impl FromStr for DigestPart {
type Err = ParseDigestError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.splitn(2, "=");
let algorithm = iter.next().ok_or(ParseDigestError::Algorithm)?;
let digest = iter.next().ok_or(ParseDigestError::Digest)?;
Ok(DigestPart {
algorithm: algorithm.to_owned(),
digest: digest.to_owned(),
})
}
}
impl FromStr for Digest {
type Err = ParseDigestError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Result<Vec<DigestPart>, Self::Err> = s
.split(",")
.map(|s: &str| DigestPart::from_str(s))
.collect();
Ok(Digest { parts: parts? })
}
}
impl warp::reject::Reject for VerifyError {}
impl warp::reject::Reject for ParseBodyError {}

View file

@ -0,0 +1,30 @@
use super::{DigestPart, DigestVerify};
use sha2::Digest;
fn verify<D>(verifier: &mut D, parts: &[DigestPart], payload: &[u8], name: &str) -> bool
where
D: Digest,
{
let part = if let Some(part) = parts.iter().find(|p| p.algorithm == name) {
part
} else {
return false;
};
verifier.input(payload);
let computed = base64::encode(&verifier.result_reset());
computed == part.digest
}
impl DigestVerify for sha2::Sha256 {
fn verify(&mut self, digests: &[DigestPart], payload: &[u8]) -> bool {
verify(self, digests, payload, "sha-256")
}
}
impl DigestVerify for sha2::Sha512 {
fn verify(&mut self, digests: &[DigestPart], payload: &[u8]) -> bool {
verify(self, digests, payload, "sha-512")
}
}

View file

@ -6,6 +6,9 @@ pub use http_signature_normalization_http::{
use std::{future::Future, pin::Pin};
use warp::{path::FullPath, Filter, Rejection};
#[cfg(feature = "digest")]
pub mod digest;
pub fn prepare_unverified(
config: Config,
) -> impl Filter<Extract = (Unverified,), Error = Rejection> + Clone {