From 7db212cb71be59571559bdc52485a4292343dfe1 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Thu, 12 Feb 2026 22:42:11 +0900 Subject: [PATCH 1/4] feat: support blocking apis --- httpsig-hyper/Cargo.toml | 5 + httpsig-hyper/src/hyper_http.rs | 226 ++++++++++++++++++++++++++++++++ httpsig-hyper/src/lib.rs | 50 ++++++- 3 files changed, 280 insertions(+), 1 deletion(-) diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index ebb786f..4185701 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -12,6 +12,11 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["blocking"] +blocking = ["futures/executor"] + + [dependencies] httpsig = { path = "../httpsig", version = "0.0.20" } diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index dd96418..9421b31 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -133,6 +133,89 @@ pub trait MessageSignatureRes { ) -> Result, Self::Error>; } +/* --------------------------------------- */ +#[cfg(feature = "blocking")] +/// A trait about http message signature for request with synchronous signing/verifying key +pub trait MessageSignatureReqSync: MessageSignatureReq { + fn set_message_signature_sync( + &mut self, + signature_params: &HttpSignatureParams, + signing_key: &T, + signature_name: Option<&str>, + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync; + + fn set_message_signatures_sync( + &mut self, + params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync; + + fn verify_message_signature_sync(&self, verifying_key: &T, key_id: Option<&str>) -> Result + where + Self: Sized, + T: VerifyingKey + Sync; + + fn verify_message_signatures_sync( + &self, + key_and_id: &[(&T, Option<&str>)], + ) -> Result>, Self::Error> + where + Self: Sized, + T: VerifyingKey + Sync; +} + +#[cfg(feature = "blocking")] +/// A trait about http message signature for response with synchronous signing/verifying key +pub trait MessageSignatureResSync: MessageSignatureRes { + fn set_message_signature_sync( + &mut self, + signature_params: &HttpSignatureParams, + signing_key: &T, + signature_name: Option<&str>, + req_for_param: Option<&Request>, + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync, + B: Sync; + + fn set_message_signatures_sync( + &mut self, + params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], + req_for_param: Option<&Request>, + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync, + B: Sync; + + fn verify_message_signature_sync( + &self, + verifying_key: &T, + key_id: Option<&str>, + req_for_param: Option<&Request>, + ) -> Result + where + Self: Sized, + T: VerifyingKey + Sync, + B: Sync; + + fn verify_message_signatures_sync( + &self, + key_and_id: &[(&T, Option<&str>)], + req_for_param: Option<&Request>, + ) -> Result>, Self::Error> + where + Self: Sized, + T: VerifyingKey + Sync, + B: Sync; +} + /* --------------------------------------- */ impl MessageSignature for Request where @@ -378,6 +461,117 @@ where } } +/* --------------------------------------- */ +#[cfg(feature = "blocking")] +impl MessageSignatureReqSync for Request +where + D: Send + Body + Sync, +{ + fn set_message_signature_sync( + &mut self, + signature_params: &HttpSignatureParams, + signing_key: &T, + signature_name: Option<&str>, + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync, + { + futures::executor::block_on(self.set_message_signature(signature_params, signing_key, signature_name)) + } + + fn set_message_signatures_sync( + &mut self, + params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync, + { + futures::executor::block_on(self.set_message_signatures(params_key_name)) + } + + fn verify_message_signature_sync(&self, verifying_key: &T, key_id: Option<&str>) -> Result + where + Self: Sized, + T: VerifyingKey + Sync, + { + futures::executor::block_on(self.verify_message_signature(verifying_key, key_id)) + } + + fn verify_message_signatures_sync( + &self, + key_and_id: &[(&T, Option<&str>)], + ) -> Result>, Self::Error> + where + Self: Sized, + T: VerifyingKey + Sync, + { + futures::executor::block_on(self.verify_message_signatures(key_and_id)) + } +} + +#[cfg(feature = "blocking")] +impl MessageSignatureResSync for Response +where + D: Send + Body + Sync, +{ + fn set_message_signature_sync( + &mut self, + signature_params: &HttpSignatureParams, + signing_key: &T, + signature_name: Option<&str>, + req_for_param: Option<&Request>, + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync, + B: Sync, + { + futures::executor::block_on(self.set_message_signature(signature_params, signing_key, signature_name, req_for_param)) + } + + fn set_message_signatures_sync( + &mut self, + params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], + req_for_param: Option<&Request>, + ) -> Result<(), Self::Error> + where + Self: Sized, + T: SigningKey + Sync, + B: Sync, + { + futures::executor::block_on(self.set_message_signatures(params_key_name, req_for_param)) + } + + fn verify_message_signature_sync( + &self, + verifying_key: &T, + key_id: Option<&str>, + req_for_param: Option<&Request>, + ) -> Result + where + Self: Sized, + T: VerifyingKey + Sync, + B: Sync, + { + futures::executor::block_on(self.verify_message_signature(verifying_key, key_id, req_for_param)) + } + + fn verify_message_signatures_sync( + &self, + key_and_id: &[(&T, Option<&str>)], + req_for_param: Option<&Request>, + ) -> Result>, Self::Error> + where + Self: Sized, + T: VerifyingKey + Sync, + B: Sync, + { + futures::executor::block_on(self.verify_message_signatures(key_and_id, req_for_param)) + } +} + /* --------------------------------------- */ // inner functions /// has message signature inner function @@ -1026,4 +1220,36 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== assert!(verification_res[0].as_ref().unwrap() == "eddsa_sig"); assert!(verification_res[1].as_ref().unwrap() == "p256_sig"); } + + #[cfg(feature = "blocking")] + #[test] + fn test_blocking_set_verify_message_signature_req() { + let mut req = futures::executor::block_on(build_request()); + let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); + signature_params.set_key_info(&secret_key); + + req.set_message_signature_sync(&signature_params, &secret_key, None).unwrap(); + + let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let verification_res = req.verify_message_signature_sync(&public_key, None); + assert!(verification_res.is_ok()); + } + + #[cfg(feature = "blocking")] + #[test] + fn test_blocking_set_verify_message_signature_res() { + let req = futures::executor::block_on(build_request()); + let mut res = futures::executor::block_on(build_response()); + let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_res()).unwrap(); + signature_params.set_key_info(&secret_key); + res + .set_message_signature_sync(&signature_params, &secret_key, None, Some(&req)) + .unwrap(); + + let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let verification_res = res.verify_message_signature_sync(&public_key, None, Some(&req)); + assert!(verification_res.is_ok()); + } } diff --git a/httpsig-hyper/src/lib.rs b/httpsig-hyper/src/lib.rs index a3fb303..249db5b 100644 --- a/httpsig-hyper/src/lib.rs +++ b/httpsig-hyper/src/lib.rs @@ -42,7 +42,9 @@ impl std::str::FromStr for ContentDigestType { pub use error::{HyperDigestError, HyperDigestResult, HyperSigError, HyperSigResult}; pub use httpsig::prelude; pub use hyper_content_digest::{ContentDigest, RequestContentDigest, ResponseContentDigest}; -pub use hyper_http::{MessageSignature, MessageSignatureReq, MessageSignatureRes}; +pub use hyper_http::{ + MessageSignature, MessageSignatureReq, MessageSignatureReqSync, MessageSignatureRes, MessageSignatureResSync, +}; /* ----------------------------------------------------------------- */ #[cfg(test)] @@ -188,4 +190,50 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= .await; assert!(verification_res.is_err()); } + + #[cfg(feature = "blocking")] + #[test] + fn test_set_verify_request_sync() { + // show usage of set_message_signature_sync and verify_message_signature_sync + + let mut req = futures::executor::block_on(build_request()); + let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let covered_components = COVERED_COMPONENTS_REQ + .iter() + .map(|v| message_component::HttpMessageComponentId::try_from(*v)) + .collect::, _>>() + .unwrap(); + let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap(); + // set key information, alg and keyid + signature_params.set_key_info(&secret_key); + // set signature + req.set_message_signature_sync(&signature_params, &secret_key, None).unwrap(); + let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let verification_res = req.verify_message_signature_sync(&public_key, None); + assert!(verification_res.is_ok()); + } + + #[cfg(feature = "blocking")] + #[test] + fn test_set_verify_response_sync() { + // show usage of set_message_signature_sync and verify_message_signature_sync + let req = futures::executor::block_on(build_request()); + let mut res = futures::executor::block_on(build_response()); + let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let covered_components = COVERED_COMPONENTS_RES + .iter() + .map(|v| message_component::HttpMessageComponentId::try_from(*v)) + .collect::, _>>() + .unwrap(); + let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap(); + // set key information, alg and keyid + signature_params.set_key_info(&secret_key); + // set signature + res + .set_message_signature_sync(&signature_params, &secret_key, None, Some(&req)) + .unwrap(); + let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let verification_res = res.verify_message_signature_sync(&public_key, None, Some(&req)); + assert!(verification_res.is_ok()); + } } From 6384e86ed6ed2e3b38c31703dfc825cfd63764ab Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Thu, 12 Feb 2026 22:49:48 +0900 Subject: [PATCH 2/4] chore(docs): update api docs --- httpsig-hyper/src/hyper_http.rs | 18 ++++++++++++++++-- httpsig-hyper/src/lib.rs | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index 9421b31..7869bf7 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -135,7 +135,14 @@ pub trait MessageSignatureRes { /* --------------------------------------- */ #[cfg(feature = "blocking")] -/// A trait about http message signature for request with synchronous signing/verifying key +/// Synchronous counterpart of [`MessageSignatureReq`]. +/// +/// Every method delegates to the corresponding async method via `futures::executor::block_on`. +/// +/// # Panics +/// +/// All methods will panic if called from within an async runtime (e.g. a `tokio` task). +/// Use the async [`MessageSignatureReq`] methods instead when you are already in an async context. pub trait MessageSignatureReqSync: MessageSignatureReq { fn set_message_signature_sync( &mut self, @@ -170,7 +177,14 @@ pub trait MessageSignatureReqSync: MessageSignatureReq { } #[cfg(feature = "blocking")] -/// A trait about http message signature for response with synchronous signing/verifying key +/// Synchronous counterpart of [`MessageSignatureRes`]. +/// +/// Every method delegates to the corresponding async method via `futures::executor::block_on`. +/// +/// # Panics +/// +/// All methods will panic if called from within an async runtime (e.g. a `tokio` task). +/// Use the async [`MessageSignatureRes`] methods instead when you are already in an async context. pub trait MessageSignatureResSync: MessageSignatureRes { fn set_message_signature_sync( &mut self, diff --git a/httpsig-hyper/src/lib.rs b/httpsig-hyper/src/lib.rs index 249db5b..b040c21 100644 --- a/httpsig-hyper/src/lib.rs +++ b/httpsig-hyper/src/lib.rs @@ -1,8 +1,24 @@ //! # httpsig-hyper //! //! `httpsig-hyper` is a crate that provides a convenient API for `Hyper` users to handle HTTP signatures. -//! This crate extends hyper's https request and response messages with the ability to generate and verify HTTP signatures. +//! This crate extends hyper's http request and response messages with the ability to generate and verify HTTP signatures. //! Additionally it also provides a way to set and verify content-digest header. +//! +//! ## Async-first design +//! +//! The primary API is fully async, allowing concurrent processing of multiple signatures via +//! [`MessageSignatureReq`] and [`MessageSignatureRes`]. +//! +//! ## Blocking API +//! +//! When the `blocking` feature is enabled (on by default), synchronous wrappers are provided via +//! [`MessageSignatureReqSync`] and [`MessageSignatureResSync`]. These use `futures::executor::block_on` +//! internally and are intended **exclusively for non-async contexts**. +//! +//! # Panics +//! +//! Calling any `*_sync` method from within an async runtime (e.g. inside a `tokio::spawn` task) +//! will panic. If you are already in an async context, use the async methods directly. mod error; mod hyper_content_digest; From cfe86c97f6e56102f94ab651df2d957a3458cd64 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Thu, 12 Feb 2026 22:50:07 +0900 Subject: [PATCH 3/4] chore: bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d7c24ab..808636f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.0.20" +version = "0.0.21" authors = ["Jun Kurihara"] homepage = "https://github.com/junkurihara/httpsig-rs" repository = "https://github.com/junkurihara/httpsig-rs" From d310194f45246da40cced77611f7f1e400f9f4b4 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Thu, 12 Feb 2026 22:52:01 +0900 Subject: [PATCH 4/4] fix: version --- httpsig-hyper/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index 4185701..e58f8a1 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -18,7 +18,7 @@ blocking = ["futures/executor"] [dependencies] -httpsig = { path = "../httpsig", version = "0.0.20" } +httpsig = { path = "../httpsig", version = "0.0.21" } thiserror = { version = "2.0.18" } tracing = { version = "0.1.44" }