From 7e5a85969b624887b10b664a8ab6e9ef2f9a0bcf Mon Sep 17 00:00:00 2001 From: Matthias Kraus Date: Tue, 27 Oct 2026 18:27:41 +0100 Subject: [PATCH 1/2] Simplify HTTP signature API by making sync default and retaining async compatibility. Drop trait constraints on Body as the traits do not depend on the Request/Response Body type. --- httpsig-hyper/src/hyper_http.rs | 215 ++++++++++++++------------------ 1 file changed, 94 insertions(+), 121 deletions(-) diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index cea0f08..96136a5 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -1,6 +1,5 @@ use crate::error::{HyperSigError, HyperSigResult}; use http::{HeaderMap, Request, Response}; -use http_body::Body; use httpsig::prelude::{ message_component::{ DerivedComponentName, HttpMessageComponent, HttpMessageComponentId, HttpMessageComponentName, HttpMessageComponentParam, @@ -133,17 +132,11 @@ pub trait MessageSignatureRes { ) -> Result, Self::Error>; } -/* --------------------------------------- */ #[cfg(feature = "blocking")] +/* --------------------------------------- */ /// 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 { + /// Set the http message signature from given http signature params and signing key fn set_message_signature_sync( &mut self, signature_params: &HttpSignatureParams, @@ -178,14 +171,8 @@ pub trait MessageSignatureReqSync: MessageSignatureReq { #[cfg(feature = "blocking")] /// 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 { + /// Set the http message signature from given http signature params and signing key fn set_message_signature_sync( &mut self, signature_params: &HttpSignatureParams, @@ -195,8 +182,7 @@ pub trait MessageSignatureResSync: MessageSignatureRes { ) -> Result<(), Self::Error> where Self: Sized, - T: SigningKey + Sync, - B: Sync; + T: SigningKey + Sync; fn set_message_signatures_sync( &mut self, @@ -205,8 +191,7 @@ pub trait MessageSignatureResSync: MessageSignatureRes { ) -> Result<(), Self::Error> where Self: Sized, - T: SigningKey + Sync, - B: Sync; + T: SigningKey + Sync; fn verify_message_signature_sync( &self, @@ -216,8 +201,7 @@ pub trait MessageSignatureResSync: MessageSignatureRes { ) -> Result where Self: Sized, - T: VerifyingKey + Sync, - B: Sync; + T: VerifyingKey + Sync; fn verify_message_signatures_sync( &self, @@ -226,14 +210,13 @@ pub trait MessageSignatureResSync: MessageSignatureRes { ) -> Result>, Self::Error> where Self: Sized, - T: VerifyingKey + Sync, - B: Sync; + T: VerifyingKey + Sync; } /* --------------------------------------- */ impl MessageSignature for Request where - D: Send + Body + Sync, + D: Send + Sync, { type Error = HyperSigError; @@ -255,29 +238,27 @@ where } } -impl MessageSignatureReq for Request +#[cfg(feature = "blocking")] +impl MessageSignatureReqSync for Request where - D: Send + Body + Sync, + D: Send + Sync, { - type Error = HyperSigError; - /// Set the http message signature from given http signature params and signing key - async fn set_message_signature( + fn set_message_signature_sync( &mut self, signature_params: &HttpSignatureParams, signing_key: &T, signature_name: Option<&str>, - ) -> HyperSigResult<()> + ) -> Result<(), Self::Error> where Self: Sized, T: SigningKey + Sync, { self - .set_message_signatures(&[(signature_params, signing_key, signature_name)]) - .await + .set_message_signatures_sync(&[(signature_params, signing_key, signature_name)]) } - async fn set_message_signatures( + fn set_message_signatures_sync( &mut self, params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], ) -> Result<(), Self::Error> @@ -292,13 +273,9 @@ where build_signature_base(&req_or_res, params, None as Option<&Request<()>>).map(|base| (base, *key, *name)) }) .collect::, _>>()?; - let vec_signature_headers = futures::future::join_all( - vec_signature_bases + let vec_signature_headers = vec_signature_bases .into_iter() - .map(|(base, key, name)| async move { base.build_signature_headers(key, name) }), - ) - .await - .into_iter() + .map(|(base, key, name)| base.build_signature_headers(key, name) ) .collect::, _>>()?; vec_signature_headers.iter().try_for_each(|headers| { self @@ -315,19 +292,17 @@ where /// Return Ok(()) if the signature is valid. /// If invalid for the given key or error occurs (like the case where the request does not have signature and/or signature-input headers), return Err. /// If key_id is given, it is used to match the key id in signature params - async fn verify_message_signature(&self, verifying_key: &T, key_id: Option<&str>) -> HyperSigResult + fn verify_message_signature_sync(&self, verifying_key: &T, key_id: Option<&str>) -> Result where Self: Sized, T: VerifyingKey + Sync, { - self - .verify_message_signatures(&[(verifying_key, key_id)]) - .await? + self.verify_message_signatures_sync(&[(verifying_key, key_id)])? .pop() .unwrap() } - async fn verify_message_signatures( + fn verify_message_signatures_sync( &self, key_and_id: &[(&T, Option<&str>)], ) -> Result>, Self::Error> @@ -341,20 +316,14 @@ where )); } let map_signature_with_base = self.extract_signatures()?; - verify_message_signatures_inner(&map_signature_with_base, key_and_id).await - } - - /// Extract all signature bases contained in the request headers - fn extract_signatures(&self) -> Result, Self::Error> { - let req_or_res = RequestOrResponse::Request(self); - extract_signatures_inner(&req_or_res, None as Option<&Request<()>>) + verify_message_signatures_inner(&map_signature_with_base, key_and_id) } } /* --------------------------------------- */ impl MessageSignature for Response where - D: Send + Body + Sync, + D: Send + Sync, { type Error = HyperSigError; @@ -376,14 +345,10 @@ where } } -impl MessageSignatureRes for Response -where - D: Send + Body + Sync, +#[cfg(feature = "blocking")] +impl MessageSignatureResSync for Response { - type Error = HyperSigError; - - /// Set the http message signature from given http signature params and signing key - async fn set_message_signature( + fn set_message_signature_sync( &mut self, signature_params: &HttpSignatureParams, signing_key: &T, @@ -393,14 +358,12 @@ where where Self: Sized, T: SigningKey + Sync, - B: Sync, { self - .set_message_signatures(&[(signature_params, signing_key, signature_name)], req_for_param) - .await + .set_message_signatures_sync(&[(signature_params, signing_key, signature_name)], req_for_param) } - async fn set_message_signatures( + fn set_message_signatures_sync( &mut self, params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], req_for_param: Option<&Request>, @@ -415,14 +378,10 @@ where .iter() .map(|(params, key, name)| build_signature_base(&req_or_res, params, req_for_param).map(|base| (base, *key, *name))) .collect::, _>>()?; - let vec_signature_headers = futures::future::join_all( - vec_signature_bases + let vec_signature_headers = vec_signature_bases .into_iter() - .map(|(base, key, name)| async move { base.build_signature_headers(key, name) }), - ) - .await - .into_iter() - .collect::, _>>()?; + .map(|(base, key, name)| base.build_signature_headers(key, name)) + .collect::, _>>()?; vec_signature_headers.iter().try_for_each(|headers| { self @@ -435,11 +394,7 @@ where }) } - /// Verify the http message signature with given verifying key if the response has signature and signature-input headers - /// Return Ok(()) if the signature is valid. - /// If invalid for the given key or error occurs (like the case where the request does not have signature and/or signature-input headers), return Err. - /// If key_id is given, it is used to match the key id in signature params - async fn verify_message_signature( + fn verify_message_signature_sync( &self, verifying_key: &T, key_id: Option<&str>, @@ -448,16 +403,14 @@ where where Self: Sized, T: VerifyingKey + Sync, - B: Sync, { self - .verify_message_signatures(&[(verifying_key, key_id)], req_for_param) - .await? + .verify_message_signatures_sync(&[(verifying_key, key_id)], req_for_param)? .pop() .unwrap() } - async fn verify_message_signatures( + fn verify_message_signatures_sync( &self, key_and_id: &[(&T, Option<&str>)], req_for_param: Option<&Request>, @@ -472,39 +425,34 @@ where )); } let map_signature_with_base = self.extract_signatures(req_for_param)?; - verify_message_signatures_inner(&map_signature_with_base, key_and_id).await - } - - /// Extract all signature bases contained in the response headers - fn extract_signatures( - &self, - req_for_param: Option<&Request>, - ) -> Result, Self::Error> { - let req_or_res = RequestOrResponse::Response(self); - extract_signatures_inner(&req_or_res, req_for_param) + verify_message_signatures_inner(&map_signature_with_base, key_and_id) } } /* --------------------------------------- */ -#[cfg(feature = "blocking")] -impl MessageSignatureReqSync for Request +impl MessageSignatureReq for Request where - D: Send + Body + Sync, + D: Send + Sync, { - fn set_message_signature_sync( + type Error = HyperSigError; + + /// Set the http message signature from given http signature params and signing key + async fn set_message_signature( &mut self, signature_params: &HttpSignatureParams, signing_key: &T, signature_name: Option<&str>, - ) -> Result<(), Self::Error> + ) -> HyperSigResult<()> where Self: Sized, T: SigningKey + Sync, { - futures::executor::block_on(self.set_message_signature(signature_params, signing_key, signature_name)) + self + .set_message_signatures(&[(signature_params, signing_key, signature_name)]) + .await } - fn set_message_signatures_sync( + async fn set_message_signatures( &mut self, params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], ) -> Result<(), Self::Error> @@ -512,18 +460,23 @@ where Self: Sized, T: SigningKey + Sync, { - futures::executor::block_on(self.set_message_signatures(params_key_name)) + self.set_message_signatures_sync(params_key_name) } - fn verify_message_signature_sync(&self, verifying_key: &T, key_id: Option<&str>) -> Result + /// Verify the http message signature with given verifying key if the request has signature and signature-input headers + /// Return Ok(()) if the signature is valid. + /// If invalid for the given key or error occurs (like the case where the request does not have signature and/or signature-input headers), return Err. + /// If key_id is given, it is used to match the key id in signature params + async fn verify_message_signature(&self, verifying_key: &T, key_id: Option<&str>) -> HyperSigResult where Self: Sized, T: VerifyingKey + Sync, { - futures::executor::block_on(self.verify_message_signature(verifying_key, key_id)) + self + .verify_message_signature_sync(verifying_key, key_id) } - fn verify_message_signatures_sync( + async fn verify_message_signatures( &self, key_and_id: &[(&T, Option<&str>)], ) -> Result>, Self::Error> @@ -531,16 +484,22 @@ where Self: Sized, T: VerifyingKey + Sync, { - futures::executor::block_on(self.verify_message_signatures(key_and_id)) + self.verify_message_signatures_sync(key_and_id) + } + + /// Extract all signature bases contained in the request headers + fn extract_signatures(&self) -> Result, Self::Error> { + let req_or_res = RequestOrResponse::Request(self); + extract_signatures_inner(&req_or_res, None as Option<&Request<()>>) } } -#[cfg(feature = "blocking")] -impl MessageSignatureResSync for Response -where - D: Send + Body + Sync, -{ - fn set_message_signature_sync( + +impl MessageSignatureRes for Response { + type Error = HyperSigError; + + /// Set the http message signature from given http signature params and signing key + async fn set_message_signature( &mut self, signature_params: &HttpSignatureParams, signing_key: &T, @@ -552,10 +511,10 @@ where T: SigningKey + Sync, B: Sync, { - futures::executor::block_on(self.set_message_signature(signature_params, signing_key, signature_name, req_for_param)) + self.set_message_signature_sync(signature_params, signing_key, signature_name, req_for_param) } - fn set_message_signatures_sync( + async fn set_message_signatures( &mut self, params_key_name: &[(&HttpSignatureParams, &T, Option<&str>)], req_for_param: Option<&Request>, @@ -565,10 +524,14 @@ where T: SigningKey + Sync, B: Sync, { - futures::executor::block_on(self.set_message_signatures(params_key_name, req_for_param)) + self.set_message_signatures_sync(params_key_name, req_for_param) } - fn verify_message_signature_sync( + /// Verify the http message signature with given verifying key if the response has signature and signature-input headers + /// Return Ok(()) if the signature is valid. + /// If invalid for the given key or error occurs (like the case where the request does not have signature and/or signature-input headers), return Err. + /// If key_id is given, it is used to match the key id in signature params + async fn verify_message_signature( &self, verifying_key: &T, key_id: Option<&str>, @@ -579,10 +542,13 @@ where T: VerifyingKey + Sync, B: Sync, { - futures::executor::block_on(self.verify_message_signature(verifying_key, key_id, req_for_param)) + self + .verify_message_signatures_sync(&[(verifying_key, key_id)], req_for_param)? + .pop() + .unwrap() } - fn verify_message_signatures_sync( + async fn verify_message_signatures( &self, key_and_id: &[(&T, Option<&str>)], req_for_param: Option<&Request>, @@ -592,10 +558,20 @@ where T: VerifyingKey + Sync, B: Sync, { - futures::executor::block_on(self.verify_message_signatures(key_and_id, req_for_param)) + self.verify_message_signatures_sync(key_and_id, req_for_param) + } + + /// Extract all signature bases contained in the request headers + fn extract_signatures( + &self, + req_for_param: Option<&Request>, + ) -> Result, Self::Error> { + let req_or_res = RequestOrResponse::Response(self); + extract_signatures_inner(&req_or_res, req_for_param) } } + /* --------------------------------------- */ // inner functions /// has message signature inner function @@ -657,7 +633,7 @@ fn extract_signatures_inner( } /// Verify multiple signatures inner function -async fn verify_message_signatures_inner( +fn verify_message_signatures_inner( map_signature_with_base: &IndexMap, key_and_id: &[(&T, Option<&str>)], ) -> HyperSigResult>> @@ -665,7 +641,7 @@ where T: VerifyingKey + Sync, { // verify for each key_and_id tuple - let res_fut = key_and_id.iter().map(|(key, key_id)| { + let res = key_and_id.iter().map(|(key, key_id)| { let filtered = if let Some(key_id) = key_id { map_signature_with_base .iter() @@ -676,7 +652,6 @@ where }; // check if any one of the signature headers is valid in async manner - async move { if filtered.is_empty() { return Err(HyperSigError::NoSignatureHeaders( "No signature as appropriate target for verification".to_string(), @@ -694,9 +669,7 @@ where "Invalid signature for the verifying key".to_string(), )) } - } - }); - let res = futures::future::join_all(res_fut).await; + }).collect(); Ok(res) } From 03af044d3e88a4538f5a8a4091cf54fb6d0691ad Mon Sep 17 00:00:00 2001 From: Matthias Kraus Date: Tue, 27 Oct 2026 21:06:49 +0100 Subject: [PATCH 2/2] Remove obsolete 'blocking' feature gate and update docs --- httpsig-hyper/Cargo.toml | 9 +++++++-- httpsig-hyper/src/hyper_http.rs | 4 ---- httpsig-hyper/src/hyper_http_tests.rs | 4 ---- httpsig-hyper/src/lib.rs | 22 ++++++++-------------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index 0c5b0f1..4800f88 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -13,8 +13,10 @@ 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"] +default = [] + +# obsolete feature gate for synchronous APIs. Kept for backward compatibilty +blocking = [] rsa-signature = ["httpsig/rsa-signature"] @@ -45,6 +47,9 @@ bytes = { version = "1.11.1" } [dev-dependencies] +futures = { version = "0.3.31", default-features = false, features = [ + "executor", +] } tokio = { version = "1.49.0", default-features = false, features = [ "macros", "rt-multi-thread", diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index 96136a5..22dd8d9 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -132,7 +132,6 @@ pub trait MessageSignatureRes { ) -> Result, Self::Error>; } -#[cfg(feature = "blocking")] /* --------------------------------------- */ /// Synchronous counterpart of [`MessageSignatureReq`]. pub trait MessageSignatureReqSync: MessageSignatureReq { @@ -169,7 +168,6 @@ pub trait MessageSignatureReqSync: MessageSignatureReq { T: VerifyingKey + Sync; } -#[cfg(feature = "blocking")] /// Synchronous counterpart of [`MessageSignatureRes`]. pub trait MessageSignatureResSync: MessageSignatureRes { /// Set the http message signature from given http signature params and signing key @@ -238,7 +236,6 @@ where } } -#[cfg(feature = "blocking")] impl MessageSignatureReqSync for Request where D: Send + Sync, @@ -345,7 +342,6 @@ where } } -#[cfg(feature = "blocking")] impl MessageSignatureResSync for Response { fn set_message_signature_sync( diff --git a/httpsig-hyper/src/hyper_http_tests.rs b/httpsig-hyper/src/hyper_http_tests.rs index ebee294..e3f5a9b 100644 --- a/httpsig-hyper/src/hyper_http_tests.rs +++ b/httpsig-hyper/src/hyper_http_tests.rs @@ -354,7 +354,6 @@ async fn test_set_verify_multiple_signatures() { // ---- Blocking (sync) ---- -#[cfg(feature = "blocking")] #[test] fn test_blocking_set_verify_message_signature_req() { let mut req = futures::executor::block_on(build_request()); @@ -369,7 +368,6 @@ fn test_blocking_set_verify_message_signature_req() { 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()); @@ -389,7 +387,6 @@ fn test_blocking_set_verify_message_signature_res() { // ---- Issue #17: @query-param;name="..." ---- /// Regression test for issue #17: @query-param;name="id" must produce signature headers (sync) -#[cfg(feature = "blocking")] #[test] fn test_query_param_sign_verify_sync() { let mut req = build_query_request(); @@ -510,7 +507,6 @@ async fn test_set_message_signature_propagates_build_error() { assert!(result.is_err(), "expected Err when using `@status` on request, got Ok"); } -#[cfg(feature = "blocking")] #[test] fn test_set_message_signature_sync_propagates_build_error() { // Same as above but for sync path diff --git a/httpsig-hyper/src/lib.rs b/httpsig-hyper/src/lib.rs index 691a392..4e406a5 100644 --- a/httpsig-hyper/src/lib.rs +++ b/httpsig-hyper/src/lib.rs @@ -4,22 +4,18 @@ //! 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 +//! ## Sync API //! -//! The primary API is fully async, allowing concurrent processing of multiple signatures via -//! [`MessageSignatureReq`] and [`MessageSignatureRes`]. +//! A synchronous API to sign and verify requuest and responses is provided via +//! [`MessageSignatureReqSync`] and [`MessageSignatureResSync`]. //! -//! ## Blocking API +//! ## Async 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**. +//! An asynchronous API is provided via +//! [`MessageSignatureReq`] and [`MessageSignatureRes`] for backward compatibility. +//! Signing and Verification do not require any IO and therefore do not benefit from +//! asynchronous processing. //! -//! # 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; mod hyper_http; @@ -211,7 +207,6 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= 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 @@ -235,7 +230,6 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= 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