mirror of
https://git.asonix.dog/asonix/http-signature-normalization.git
synced 2025-02-19 15:06:20 +00:00
Include digest verification
This commit is contained in:
parent
029ada114a
commit
77ecf6eaa7
4 changed files with 165 additions and 2 deletions
|
@ -5,9 +5,20 @@ 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"
|
||||||
http = "0.2.0"
|
http = "0.2.0"
|
||||||
http-signature-normalization-http = { version = "0.2.0", path = "../http-signature-normalization-http" }
|
http-signature-normalization-http = { version = "0.2.0", path = "../http-signature-normalization-http" }
|
||||||
warp = { git = "https://github.com/seanmonstar/warp" }
|
serde = { version = "1.0.104", features = ["derive"], optional = true }
|
||||||
futures = "0.3.1"
|
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"
|
||||||
|
|
119
http-signature-normalization-warp/src/digest/mod.rs
Normal file
119
http-signature-normalization-warp/src/digest/mod.rs
Normal 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 {}
|
30
http-signature-normalization-warp/src/digest/sha_2.rs
Normal file
30
http-signature-normalization-warp/src/digest/sha_2.rs
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,9 @@ pub use http_signature_normalization_http::{
|
||||||
use std::{future::Future, pin::Pin};
|
use std::{future::Future, pin::Pin};
|
||||||
use warp::{path::FullPath, Filter, Rejection};
|
use warp::{path::FullPath, Filter, Rejection};
|
||||||
|
|
||||||
|
#[cfg(feature = "digest")]
|
||||||
|
pub mod digest;
|
||||||
|
|
||||||
pub fn prepare_unverified(
|
pub fn prepare_unverified(
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> impl Filter<Extract = (Unverified,), Error = Rejection> + Clone {
|
) -> impl Filter<Extract = (Unverified,), Error = Rejection> + Clone {
|
||||||
|
|
Loading…
Reference in a new issue