Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion httpsig-hyper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["blocking"]
default = ["blocking", "digest-sha256", "digest-sha512"]
digest-sha256 = []
digest-sha512 = []
blocking = ["futures/executor"]
rsa-signature = ["httpsig/rsa-signature"]

Expand Down Expand Up @@ -49,3 +51,12 @@ tokio = { version = "1.49.0", default-features = false, features = [
"macros",
"rt-multi-thread",
] } # testing only

[[example]]
name = "hyper-response"
required-features = ["digest-sha256"]


[[example]]
name = "hyper-request"
required-features = ["digest-sha256"]
35 changes: 27 additions & 8 deletions httpsig-hyper/src/hyper_content_digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ pub trait ContentDigest: http_body::Body {
/// Returns the digest of the given body in Vec<u8>
fn derive_digest(body_bytes: &Bytes, cd_type: &ContentDigestType) -> Vec<u8> {
match cd_type {
#[cfg(feature = "digest-sha256")]
ContentDigestType::Sha256 => {
let mut hasher = sha2::Sha256::new();
hasher.update(body_bytes);
hasher.finalize().to_vec()
}

#[cfg(feature = "digest-sha512")]
ContentDigestType::Sha512 => {
let mut hasher = sha2::Sha512::new();
hasher.update(body_bytes);
Expand Down Expand Up @@ -263,17 +265,30 @@ mod tests {
#[tokio::test]
async fn content_digest() {
let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
let (_body_bytes, digest) = body.into_bytes_with_digest(&ContentDigestType::Sha256).await.unwrap();

assert_eq!(digest, "X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=");
#[cfg(feature = "digest-sha256")]
{
let (_body_bytes, digest) = body
.into_bytes_with_digest(&ContentDigestType::Sha256)
.await
.unwrap();

assert_eq!(digest, "X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=");
}

let (_body_bytes, digest) = body.into_bytes_with_digest(&ContentDigestType::Sha512).await.unwrap();
assert_eq!(
digest,
"WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew=="
);
#[cfg(feature = "digest-sha512")]
{
let (_body_bytes, digest) = body
.into_bytes_with_digest(&ContentDigestType::Sha512)
.await
.unwrap();
assert_eq!(
digest,
"WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew=="
);
}
}

#[cfg(feature = "digest-sha256")]
#[tokio::test]
async fn hyper_request_test() {
let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
Expand All @@ -295,6 +310,7 @@ mod tests {
assert!(verified.is_ok());
}

#[cfg(feature = "digest-sha256")]
#[tokio::test]
async fn hyper_response_test() {
let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
Expand All @@ -315,6 +331,7 @@ mod tests {
assert!(verified.is_ok());
}

#[cfg(feature = "digest-sha256")]
#[tokio::test]
async fn hyper_request_digest_mismatch_by_body_tamper_should_fail() {
// 1) Create a request and set a correct Content-Digest for the original body
Expand Down Expand Up @@ -344,6 +361,7 @@ mod tests {
}
}

#[cfg(feature = "digest-sha256")]
#[tokio::test]
async fn hyper_response_digest_mismatch_by_header_tamper_should_fail() {
// 1) Create a response and set a correct Content-Digest
Expand Down Expand Up @@ -397,6 +415,7 @@ mod tests {
}
}

#[cfg(feature = "digest-sha256")]
#[tokio::test]
async fn hyper_request_digest_length_mismatch_should_fail() {
// 1) Create a request and attach a valid Content-Digest header
Expand Down
6 changes: 4 additions & 2 deletions httpsig-hyper/src/hyper_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,11 +935,13 @@ fn extract_http_message_component<B>(
) -> HyperSigResult<HttpMessageComponent> {
match &target_component_id.name {
HttpMessageComponentName::HttpField(_) => extract_http_field(req_or_res, target_component_id),
HttpMessageComponentName::Derived(_) => extract_derived_component(req_or_res, target_component_id),
HttpMessageComponentName::Derived(_) => {
extract_derived_component(req_or_res, target_component_id)
}
}
}

/* --------------------------------------- */
#[cfg(test)]
#[cfg(all(test, feature = "digest-sha256"))]
#[path = "hyper_http_tests.rs"]
mod tests;
15 changes: 14 additions & 1 deletion httpsig-hyper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,45 @@
//! will panic. If you are already in an async context, use the async methods directly.

mod error;
#[cfg(any(feature = "digest-sha256", feature = "digest-sha512"))]
mod hyper_content_digest;
mod hyper_http;

// hyper's http specific extension to generate and verify http signature

/// content-digest header name
#[cfg(any(feature = "digest-sha256", feature = "digest-sha512"))]
const CONTENT_DIGEST_HEADER: &str = "content-digest";

/// content-digest header type
#[cfg(any(feature = "digest-sha256", feature = "digest-sha512"))]
pub enum ContentDigestType {
#[cfg(feature = "digest-sha256")]
Sha256,
#[cfg(feature = "digest-sha512")]
Sha512,
}

#[cfg(any(feature = "digest-sha256", feature = "digest-sha512"))]
impl std::fmt::Display for ContentDigestType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "digest-sha256")]
ContentDigestType::Sha256 => write!(f, "sha-256"),
#[cfg(feature = "digest-sha512")]
ContentDigestType::Sha512 => write!(f, "sha-512"),
}
}
}

#[cfg(any(feature = "digest-sha256", feature = "digest-sha512"))]
impl std::str::FromStr for ContentDigestType {
type Err = error::HyperDigestError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#[cfg(feature = "digest-sha256")]
"sha-256" => Ok(ContentDigestType::Sha256),
#[cfg(feature = "digest-sha512")]
"sha-512" => Ok(ContentDigestType::Sha512),
_ => Err(error::HyperDigestError::InvalidContentDigestType(s.to_string())),
}
Expand All @@ -57,13 +68,14 @@ impl std::str::FromStr for ContentDigestType {

pub use error::{HyperDigestError, HyperDigestResult, HyperSigError, HyperSigResult};
pub use httpsig::prelude;
#[cfg(any(feature = "digest-sha256", feature = "digest-sha512"))]
pub use hyper_content_digest::{ContentDigest, RequestContentDigest, ResponseContentDigest};
pub use hyper_http::{
MessageSignature, MessageSignatureReq, MessageSignatureReqSync, MessageSignatureRes, MessageSignatureResSync,
};

/* ----------------------------------------------------------------- */
#[cfg(test)]
#[cfg(all(test, feature = "digest-sha256"))]
mod tests {
use super::{prelude::*, *};
use http::{Request, Response};
Expand Down Expand Up @@ -113,6 +125,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
#[test]
fn test_content_digest_type() {
assert_eq!(ContentDigestType::Sha256.to_string(), "sha-256");
#[cfg(feature = "digest-sha512")]
assert_eq!(ContentDigestType::Sha512.to_string(), "sha-512");
}

Expand Down