From a13dfb3aec9ce63bae3bc4000022152f863418ed Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 10 Feb 2026 19:13:33 +0900 Subject: [PATCH 1/5] chore(deps): rand-0.10.0 --- httpsig-hyper/Cargo.toml | 12 ++++++------ httpsig/Cargo.toml | 12 ++++++------ httpsig/src/signature_params.rs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index 548a6e4..ebb786f 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -15,13 +15,13 @@ rust-version.workspace = true [dependencies] httpsig = { path = "../httpsig", version = "0.0.20" } -thiserror = { version = "2.0.16" } -tracing = { version = "0.1.41" } +thiserror = { version = "2.0.18" } +tracing = { version = "0.1.44" } futures = { version = "0.3.31", default-features = false, features = [ "std", "async-await", ] } -indexmap = { version = "2.11.1" } +indexmap = { version = "2.11.4" } # content digest with rfc8941 structured field values sha2 = { version = "0.10.9", default-features = false } @@ -31,14 +31,14 @@ sfv = { version = "0.14.0" } base64 = { version = "0.22.1" } # for request and response headers -http = { version = "1.3.1" } +http = { version = "1.4.0" } http-body = { version = "1.0.1" } http-body-util = { version = "0.1.3" } -bytes = { version = "1.10.1" } +bytes = { version = "1.11.1" } [dev-dependencies] -tokio = { version = "1.47.1", default-features = false, features = [ +tokio = { version = "1.49.0", default-features = false, features = [ "macros", "rt-multi-thread", ] } # testing only diff --git a/httpsig/Cargo.toml b/httpsig/Cargo.toml index e82a4f6..e798575 100644 --- a/httpsig/Cargo.toml +++ b/httpsig/Cargo.toml @@ -13,17 +13,17 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -thiserror = { version = "2.0.16" } -tracing = { version = "0.1.41" } +thiserror = { version = "2.0.18" } +tracing = { version = "0.1.44" } rustc-hash = { version = "2.1.1" } -indexmap = { version = "2.11.1" } -rand = { version = "0.9.2" } +indexmap = { version = "2.11.4" } +rand = { version = "0.10.0" } # crypto pkcs8 = { version = "0.10.2", default-features = false, features = ["pem"] } spki = { version = "0.7.3", default-features = false, features = ["pem"] } sec1 = { version = "0.7.3", default-features = false, features = ["der"] } -ed25519-compact = { version = "2.1.1", default-features = false, features = [ +ed25519-compact = { version = "2.2.0", default-features = false, features = [ "random", ] } ecdsa = { version = "0.16.9", default-features = false, features = [ @@ -39,7 +39,7 @@ p384 = { version = "0.13.1", default-features = false, features = [ ] } hmac = { version = "0.12.1" } sha2 = { version = "0.10.9", default-features = false } -bytes = { version = "1.10.1" } +bytes = { version = "1.11.1" } # encoding base64 = { version = "0.22.1" } diff --git a/httpsig/src/signature_params.rs b/httpsig/src/signature_params.rs index 892df06..788db68 100644 --- a/httpsig/src/signature_params.rs +++ b/httpsig/src/signature_params.rs @@ -6,7 +6,7 @@ use crate::{ util::has_unique_elements, }; use base64::{engine::general_purpose, Engine as _}; -use rand::Rng; +use rand::RngExt; use sfv::{FieldType, ListEntry, Parser}; use std::time::{SystemTime, UNIX_EPOCH}; From 7db212cb71be59571559bdc52485a4292343dfe1 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Thu, 12 Feb 2026 22:42:11 +0900 Subject: [PATCH 2/5] 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 3/5] 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 4/5] 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 5/5] 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" }